diff --git a/src/facets/AllocateFacet.sol b/src/facets/AllocateFacet.sol index 584d961..dc3362b 100644 --- a/src/facets/AllocateFacet.sol +++ b/src/facets/AllocateFacet.sol @@ -49,12 +49,16 @@ contract AllocateFacet is IFacet, Modifiers, PausableUpgradeable { if (allocReq.length * replicaSize < appConfig.minRequiredStorageProviders) { revert ErrorLib.NotEnoughAllocationData(); } - uint256 requiredCollateral = appConfig.collateralPerCID * allocReq.length * replicaSize; - if (msg.value < requiredCollateral) { - revert ErrorLib.InsufficientCollateral(requiredCollateral); + { + uint256 requiredCollateral = appConfig.collateralPerCID * allocReq.length * replicaSize; + if (msg.value < requiredCollateral) { + revert ErrorLib.InsufficientCollateral(requiredCollateral); + } } - - uint64[] memory providers = StorageEntityPicker._pickStorageProviders(appConfig.minRequiredStorageProviders); + uint256 maxSpacePerProvider = + _calculateMaxAllocationSizePerProvider(allocReq, replicaSize, appConfig.minRequiredStorageProviders); + uint64[] memory providers = + StorageEntityPicker._pickStorageProviders(appConfig.minRequiredStorageProviders, maxSpacePerProvider); int64 termMin = FilecoinEpochCalculator.getTermMin(); int64 termMax = FilecoinEpochCalculator.calcTermMax(); @@ -99,4 +103,21 @@ contract AllocateFacet is IFacet, Modifiers, PausableUpgradeable { return packageId; } + + /** + * @dev Calculate the worst case (maximum) allocation size based on the request. + * cost: approx 20 gas per allocReq entry. + */ + function _calculateMaxAllocationSizePerProvider( + Types.AllocationRequest[] calldata allocReq, + uint256 replicaSize, + uint256 minProviders + ) internal pure returns (uint256) { + uint256 totalBytes = 0; + for (uint256 i = 0; i < allocReq.length; i++) { + totalBytes += uint256(allocReq[i].size) * replicaSize; + } + // ceil(a/b) == (a + b - 1) / b + return (totalBytes + minProviders - 1) / minProviders; + } } diff --git a/src/facets/StorageEntityManagerFacet.sol b/src/facets/StorageEntityManagerFacet.sol index 3b22229..6f70a68 100644 --- a/src/facets/StorageEntityManagerFacet.sol +++ b/src/facets/StorageEntityManagerFacet.sol @@ -20,14 +20,13 @@ import {Events} from "../libraries/Events.sol"; contract StorageEntityManagerFacet is IFacet, Modifiers { // get the function selectors for this facet for deployment and update scripts function selectors() external pure returns (bytes4[] memory selectors_) { - selectors_ = new bytes4[](7); + selectors_ = new bytes4[](6); selectors_[0] = this.createStorageEntity.selector; selectors_[1] = this.addStorageProviders.selector; selectors_[2] = this.removeStorageProviders.selector; - selectors_[3] = this.changeStorageEntityActiveStatus.selector; - selectors_[4] = this.getStorageEntity.selector; - selectors_[5] = this.isStorageProviderUsed.selector; - selectors_[6] = this.getStorageEntities.selector; + selectors_[3] = this.setStorageEntityActiveStatus.selector; + selectors_[4] = this.isStorageProviderUsed.selector; + selectors_[5] = this.setStorageProviderDetails.selector; } function createStorageEntity(address entityOwner, uint64[] calldata storageProviders) @@ -63,7 +62,7 @@ contract StorageEntityManagerFacet is IFacet, Modifiers { Storage.StorageEntity storage se = Storage.s().storageEntities[entityOwner]; - _ensureStorageEntityNotExists(se); + _ensureStorageEntityExists(se); for (uint256 i = 0; i < storageProviders.length; i++) { se.storageProviders.push(storageProviders[i]); @@ -79,16 +78,21 @@ contract StorageEntityManagerFacet is IFacet, Modifiers { { Storage.StorageEntity storage se = Storage.s().storageEntities[entityOwner]; - _ensureStorageEntityNotExists(se); + _ensureStorageEntityExists(se); for (uint256 j = 0; j < storageProviders.length; j++) { uint64 sp = storageProviders[j]; + + _ensureStorageProviderIsAssignedToStorageEntity(se, sp); + Storage.s().usedStorageProviders[sp] = false; for (uint256 i = 0; i < se.storageProviders.length; i++) { if (se.storageProviders[i] == sp) { se.storageProviders[i] = se.storageProviders[se.storageProviders.length - 1]; se.storageProviders.pop(); Storage.s().usedStorageProviders[sp] = false; + + se.providerDetails[sp] = Storage.ProviderDetails({isActive: false, spaceLeft: 0}); break; } } @@ -97,13 +101,13 @@ contract StorageEntityManagerFacet is IFacet, Modifiers { emit Events.StorageProviderRemoved(msg.sender, entityOwner, storageProviders); } - function changeStorageEntityActiveStatus(address entityOwner, bool isActive) + function setStorageEntityActiveStatus(address entityOwner, bool isActive) external onlyOwnerOrStorageEntity(entityOwner) { Storage.StorageEntity storage se = Storage.s().storageEntities[entityOwner]; - _ensureStorageEntityNotExists(se); + _ensureStorageEntityExists(se); se.isActive = isActive; @@ -118,26 +122,44 @@ contract StorageEntityManagerFacet is IFacet, Modifiers { } } - function _ensureStorageEntityNotExists(Storage.StorageEntity storage se) internal view { + function _ensureStorageEntityExists(Storage.StorageEntity storage se) internal view { if (se.owner == address(0)) { revert ErrorLib.StorageEntityDoesNotExist(); } } - function getStorageEntity(address entityOwner) external view returns (Storage.StorageEntity memory) { - return Storage.s().storageEntities[entityOwner]; - } - function isStorageProviderUsed(uint64 storageProvider) external view returns (bool) { return Storage.s().usedStorageProviders[storageProvider]; } - function getStorageEntities() external view returns (Storage.StorageEntity[] memory) { - address[] storage entityAddresses = Storage.s().entityAddresses; - Storage.StorageEntity[] memory storageEntities = new Storage.StorageEntity[](entityAddresses.length); - for (uint256 i = 0; i < entityAddresses.length; i++) { - storageEntities[i] = Storage.s().storageEntities[entityAddresses[i]]; + function setStorageProviderDetails( + address entityOwner, + uint64 storageProvider, + Storage.ProviderDetails calldata details + ) external onlyOwnerOrStorageEntity(entityOwner) { + Storage.StorageEntity storage se = Storage.s().storageEntities[entityOwner]; + + _ensureStorageEntityExists(se); + + _ensureStorageProviderIsAssignedToStorageEntity(se, storageProvider); + + se.providerDetails[storageProvider] = details; + } + + function _ensureStorageProviderIsAssignedToStorageEntity(Storage.StorageEntity storage se, uint64 storageProvider) + internal + view + { + // Check if sp is assigned to the entity + bool isAssigned = false; + for (uint256 i = 0; i < se.storageProviders.length; i++) { + if (se.storageProviders[i] == storageProvider) { + isAssigned = true; + break; + } + } + if (!isAssigned) { + revert ErrorLib.StorageProviderNotAssignedToEntity(); } - return storageEntities; } } diff --git a/src/facets/ViewFacet.sol b/src/facets/ViewFacet.sol index c784a57..32c3c29 100644 --- a/src/facets/ViewFacet.sol +++ b/src/facets/ViewFacet.sol @@ -14,12 +14,14 @@ import {FilecoinConverter} from "../libraries/FilecoinConverter.sol"; contract ViewFacet is IFacet { // get the function selectors for this facet for deployment and update scripts function selectors() external pure returns (bytes4[] memory selectors_) { - selectors_ = new bytes4[](5); + selectors_ = new bytes4[](7); selectors_[0] = this.getAllocationPackage.selector; selectors_[1] = this.getClientPackagesWithClaimStatus.selector; selectors_[2] = this.getPackageWithClaimStatus.selector; selectors_[3] = this.checkProviderClaims.selector; selectors_[4] = this.getAppConfig.selector; + selectors_[5] = this.getStorageEntity.selector; + selectors_[6] = this.getStorageEntities.selector; } function getAppConfig() external view returns (Storage.AppConfig memory appConfig) { @@ -163,4 +165,41 @@ contract ViewFacet is IFacet { return result.batch_info.success_count == allocationIds.length; } + + function getStorageEntity(address entityOwner) external view returns (Types.StorageEntityView memory) { + if (Storage.s().storageEntities[entityOwner].owner == address(0)) { + revert ErrorLib.StorageEntityDoesNotExist(); + } + return _storageEntityToView(Storage.s().storageEntities[entityOwner]); + } + + function getStorageEntities() external view returns (Types.StorageEntityView[] memory) { + address[] storage entityAddresses = Storage.s().entityAddresses; + Types.StorageEntityView[] memory storageEntities = new Types.StorageEntityView[](entityAddresses.length); + for (uint256 i = 0; i < entityAddresses.length; i++) { + storageEntities[i] = _storageEntityToView(Storage.s().storageEntities[entityAddresses[i]]); + } + return storageEntities; + } + + function _storageEntityToView(Storage.StorageEntity storage se) + internal + view + returns (Types.StorageEntityView memory) + { + Types.StorageEntityView memory entityView; + entityView.isActive = se.isActive; + entityView.owner = se.owner; + entityView.storageProviders = se.storageProviders; + entityView.providerDetails = new Types.ProviderDetailsView[](se.storageProviders.length); + for (uint256 i = 0; i < se.storageProviders.length; i++) { + uint64 providerId = se.storageProviders[i]; + entityView.providerDetails[i] = Types.ProviderDetailsView({ + providerId: providerId, + spaceLeft: se.providerDetails[providerId].spaceLeft, + isActive: se.providerDetails[providerId].isActive + }); + } + return entityView; + } } diff --git a/src/libraries/Errors.sol b/src/libraries/Errors.sol index e230b59..1c69bad 100644 --- a/src/libraries/Errors.sol +++ b/src/libraries/Errors.sol @@ -119,4 +119,7 @@ library ErrorLib { // 0x35278d12 error Overflow(); + + // 0x8865bd93 + error StorageProviderNotAssignedToEntity(); } diff --git a/src/libraries/Storage.sol b/src/libraries/Storage.sol index ec97f59..92722d9 100644 --- a/src/libraries/Storage.sol +++ b/src/libraries/Storage.sol @@ -40,10 +40,16 @@ library Storage { uint256 collateral; // Collateral amount } + struct ProviderDetails { + bool isActive; // Whether the provider is active + uint256 spaceLeft; // Space left for the provider + } + struct StorageEntity { bool isActive; // Whether the storage entity is active address owner; // Owner address, used to verify ownership uint64[] storageProviders; // List of storage providers + mapping(uint64 => ProviderDetails) providerDetails; // Mapping by provider ID } /** diff --git a/src/libraries/StorageEntityPicker.sol b/src/libraries/StorageEntityPicker.sol index 035c47a..d0a79e2 100644 --- a/src/libraries/StorageEntityPicker.sol +++ b/src/libraries/StorageEntityPicker.sol @@ -41,7 +41,10 @@ library StorageEntityPicker { * @param numEntities The number of storage providers to pick */ // slither-disable-next-line weak-prng - function _pickStorageProviders(uint256 numEntities) internal returns (uint64[] memory) { + function _pickStorageProviders(uint256 numEntities, uint256 maxSpacePerProvider) + internal + returns (uint64[] memory) + { address[] storage entityAddresses = Storage.s().entityAddresses; uint256 entityLength = entityAddresses.length; @@ -59,10 +62,25 @@ library StorageEntityPicker { address entityAddress = entityAddresses[index]; Storage.StorageEntity storage se = Storage.s().storageEntities[entityAddress]; - // SE is active, and has at least one storage provider - if (se.isActive && se.storageProviders.length > 0) { - storageProviders[pickedCount] = Storage.s().storageEntities[entityAddress].storageProviders[0]; - pickedCount++; + + if (!se.isActive || se.storageProviders.length == 0) { + // If the SE is not active + // or doesn't have at least one provider + // then we can skip it + continue; + } + // Select one storage provider from the active storage entity that has enough space left + for (uint256 j = 0; j < se.storageProviders.length; j++) { + uint64 provider = se.storageProviders[j]; + + if ( + se.providerDetails[provider].isActive + && se.providerDetails[provider].spaceLeft >= maxSpacePerProvider + ) { + storageProviders[pickedCount] = provider; + pickedCount++; + break; // Break the loop after picking one provider + } } } diff --git a/src/libraries/Types.sol b/src/libraries/Types.sol index acf58b2..5e18da1 100644 --- a/src/libraries/Types.sol +++ b/src/libraries/Types.sol @@ -132,4 +132,17 @@ library Types { // 15301680 -> 1A 00E9A4A0 -> 1 + 4 bytes int64 expiration; } + + struct StorageEntityView { + bool isActive; + address owner; + uint64[] storageProviders; + ProviderDetailsView[] providerDetails; + } + + struct ProviderDetailsView { + bool isActive; // Whether the provider is active + uint64 providerId; // Provider ID + uint256 spaceLeft; // Space left for the provider + } }