diff --git a/service_contracts/abi/ServiceProviderRegistry.abi.json b/service_contracts/abi/ServiceProviderRegistry.abi.json index de911f8e..865401f0 100644 --- a/service_contracts/abi/ServiceProviderRegistry.abi.json +++ b/service_contracts/abi/ServiceProviderRegistry.abi.json @@ -426,6 +426,11 @@ "name": "paymentTokenAddress", "type": "address", "internalType": "contract IERC20" + }, + { + "name": "ipniPeerId", + "type": "bytes", + "internalType": "bytes" } ] }, @@ -1252,6 +1257,11 @@ "name": "paymentTokenAddress", "type": "address", "internalType": "contract IERC20" + }, + { + "name": "ipniPeerId", + "type": "bytes", + "internalType": "bytes" } ] }, diff --git a/service_contracts/src/ServiceProviderRegistry.sol b/service_contracts/src/ServiceProviderRegistry.sol index 0f20318c..7fc571e9 100644 --- a/service_contracts/src/ServiceProviderRegistry.sol +++ b/service_contracts/src/ServiceProviderRegistry.sol @@ -47,6 +47,9 @@ contract ServiceProviderRegistry is /// @notice Maximum length for location field uint256 private constant MAX_LOCATION_LENGTH = 128; + /// @notice Maximum length for IPNI peer ID + uint256 private constant MAX_IPNI_PEER_ID_LENGTH = 128; + /// @notice Burn actor address for burning FIL address public constant BURN_ACTOR = 0xff00000000000000000000000000000000000063; @@ -821,6 +824,7 @@ contract ServiceProviderRegistry is require(pdpOffering.minProvingPeriodInEpochs > 0, "Min proving period must be greater than 0"); require(bytes(pdpOffering.location).length > 0, "Location cannot be empty"); require(bytes(pdpOffering.location).length <= MAX_LOCATION_LENGTH, "Location too long"); + require(pdpOffering.ipniPeerId.length <= MAX_IPNI_PEER_ID_LENGTH, "IPNI peer ID too long"); } /// @notice Validate capability key-value pairs diff --git a/service_contracts/src/ServiceProviderRegistryStorage.sol b/service_contracts/src/ServiceProviderRegistryStorage.sol index f5c5a16c..d3e58ce5 100644 --- a/service_contracts/src/ServiceProviderRegistryStorage.sol +++ b/service_contracts/src/ServiceProviderRegistryStorage.sol @@ -44,6 +44,7 @@ contract ServiceProviderRegistryStorage { uint256 minProvingPeriodInEpochs; // Minimum proving period in epochs string location; // Geographic location of the service provider IERC20 paymentTokenAddress; // Token contract for payment (IERC20(address(0)) for FIL) + bytes ipniPeerId; // IPNI peer ID (max 128 bytes, can be empty) } /// @notice Combined provider and product information for detailed queries diff --git a/service_contracts/test/FilecoinWarmStorageService.t.sol b/service_contracts/test/FilecoinWarmStorageService.t.sol index 90dfd2e2..eae86add 100644 --- a/service_contracts/test/FilecoinWarmStorageService.t.sol +++ b/service_contracts/test/FilecoinWarmStorageService.t.sol @@ -149,7 +149,8 @@ contract FilecoinWarmStorageServiceTest is Test { storagePricePerTibPerMonth: 1 ether, minProvingPeriodInEpochs: 2880, location: "US-Central", - paymentTokenAddress: IERC20(address(0)) // Payment in FIL + paymentTokenAddress: IERC20(address(0)), // Payment in FIL + ipniPeerId: hex"" }) ), new string[](0), @@ -172,7 +173,8 @@ contract FilecoinWarmStorageServiceTest is Test { storagePricePerTibPerMonth: 1 ether, minProvingPeriodInEpochs: 2880, location: "US-Central", - paymentTokenAddress: IERC20(address(0)) // Payment in FIL + paymentTokenAddress: IERC20(address(0)), // Payment in FIL + ipniPeerId: hex"" }) ), new string[](0), @@ -195,7 +197,8 @@ contract FilecoinWarmStorageServiceTest is Test { storagePricePerTibPerMonth: 1 ether, minProvingPeriodInEpochs: 2880, location: "US-Central", - paymentTokenAddress: IERC20(address(0)) // Payment in FIL + paymentTokenAddress: IERC20(address(0)), // Payment in FIL + ipniPeerId: hex"" }) ), new string[](0), @@ -218,7 +221,8 @@ contract FilecoinWarmStorageServiceTest is Test { storagePricePerTibPerMonth: 1 ether, minProvingPeriodInEpochs: 2880, location: "US-Central", - paymentTokenAddress: IERC20(address(0)) // Payment in FIL + paymentTokenAddress: IERC20(address(0)), // Payment in FIL + ipniPeerId: hex"" }) ), new string[](0), diff --git a/service_contracts/test/FilecoinWarmStorageServiceOwner.t.sol b/service_contracts/test/FilecoinWarmStorageServiceOwner.t.sol index 85c21c0f..95d87696 100644 --- a/service_contracts/test/FilecoinWarmStorageServiceOwner.t.sol +++ b/service_contracts/test/FilecoinWarmStorageServiceOwner.t.sol @@ -149,7 +149,8 @@ contract FilecoinWarmStorageServiceOwnerTest is Test { storagePricePerTibPerMonth: 5 * 10 ** 6, // 5 USDFC per TiB per month minProvingPeriodInEpochs: 2880, location: "US", - paymentTokenAddress: IERC20(address(0)) + paymentTokenAddress: IERC20(address(0)), + ipniPeerId: hex"" }) ), capabilityKeys, diff --git a/service_contracts/test/ProviderValidation.t.sol b/service_contracts/test/ProviderValidation.t.sol index e0f2d2e0..ee6d9854 100644 --- a/service_contracts/test/ProviderValidation.t.sol +++ b/service_contracts/test/ProviderValidation.t.sol @@ -125,7 +125,8 @@ contract ProviderValidationTest is Test { storagePricePerTibPerMonth: 1 ether, minProvingPeriodInEpochs: 2880, location: "US-West", - paymentTokenAddress: IERC20(address(0)) // Payment in FIL + paymentTokenAddress: IERC20(address(0)), // Payment in FIL + ipniPeerId: hex"" }) ), new string[](0), @@ -163,7 +164,8 @@ contract ProviderValidationTest is Test { storagePricePerTibPerMonth: 1 ether, minProvingPeriodInEpochs: 2880, location: "US-West", - paymentTokenAddress: IERC20(address(0)) // Payment in FIL + paymentTokenAddress: IERC20(address(0)), // Payment in FIL + ipniPeerId: hex"" }) ), new string[](0), diff --git a/service_contracts/test/ServiceProviderRegistry.t.sol b/service_contracts/test/ServiceProviderRegistry.t.sol index a96ad6a7..029e096f 100644 --- a/service_contracts/test/ServiceProviderRegistry.t.sol +++ b/service_contracts/test/ServiceProviderRegistry.t.sol @@ -68,7 +68,8 @@ contract ServiceProviderRegistryTest is Test { storagePricePerTibPerMonth: 500000000000000000, // 0.5 FIL per TiB per month minProvingPeriodInEpochs: 2880, location: "US-East", - paymentTokenAddress: IERC20(address(0)) // Payment in FIL + paymentTokenAddress: IERC20(address(0)), // Payment in FIL + ipniPeerId: hex"" }); // Encode PDP data @@ -111,7 +112,8 @@ contract ServiceProviderRegistryTest is Test { storagePricePerTibPerMonth: 500000000000000000, // 0.5 FIL per TiB per month minProvingPeriodInEpochs: 2880, location: "US-East", - paymentTokenAddress: IERC20(address(0)) // Payment in FIL + paymentTokenAddress: IERC20(address(0)), // Payment in FIL + ipniPeerId: hex"" }); // Encode PDP data @@ -182,7 +184,8 @@ contract ServiceProviderRegistryTest is Test { storagePricePerTibPerMonth: 500000000000000000, // 0.5 FIL per TiB per month minProvingPeriodInEpochs: 2880, location: "US-East", - paymentTokenAddress: IERC20(address(0)) // Payment in FIL + paymentTokenAddress: IERC20(address(0)), // Payment in FIL + ipniPeerId: hex"" }); bytes memory encodedData = abi.encode(pdpData); @@ -224,7 +227,8 @@ contract ServiceProviderRegistryTest is Test { storagePricePerTibPerMonth: 500000000000000000, minProvingPeriodInEpochs: 2880, location: "US-East", - paymentTokenAddress: IERC20(address(0)) + paymentTokenAddress: IERC20(address(0)), + ipniPeerId: hex"" }); bytes memory encodedData = abi.encode(pdpData); @@ -259,7 +263,8 @@ contract ServiceProviderRegistryTest is Test { storagePricePerTibPerMonth: 750000000000000000, // 0.75 FIL per TiB per month minProvingPeriodInEpochs: 2880, location: "US-East", - paymentTokenAddress: IERC20(address(0)) // Payment in FIL + paymentTokenAddress: IERC20(address(0)), // Payment in FIL + ipniPeerId: hex"" }); bytes memory encodedData = abi.encode(pdpData); @@ -323,7 +328,8 @@ contract ServiceProviderRegistryTest is Test { storagePricePerTibPerMonth: 500000000000000000, // 0.5 FIL per TiB per month minProvingPeriodInEpochs: 2880, location: "US-East", - paymentTokenAddress: IERC20(address(0)) // Payment in FIL + paymentTokenAddress: IERC20(address(0)), // Payment in FIL + ipniPeerId: hex"" }); // Encode PDP data @@ -561,7 +567,8 @@ contract ServiceProviderRegistryTest is Test { storagePricePerTibPerMonth: 1000, minProvingPeriodInEpochs: 1, location: "US", - paymentTokenAddress: IERC20(address(0)) + paymentTokenAddress: IERC20(address(0)), + ipniPeerId: hex"" }); return abi.encode(pdpOffering); } diff --git a/service_contracts/test/ServiceProviderRegistryFull.t.sol b/service_contracts/test/ServiceProviderRegistryFull.t.sol index ff8dac3f..422f05fa 100644 --- a/service_contracts/test/ServiceProviderRegistryFull.t.sol +++ b/service_contracts/test/ServiceProviderRegistryFull.t.sol @@ -61,7 +61,8 @@ contract ServiceProviderRegistryFullTest is Test { storagePricePerTibPerMonth: 1000000000000000000, // 1 FIL per TiB per month minProvingPeriodInEpochs: 2880, // 1 day in epochs (30 second blocks) location: "North America", - paymentTokenAddress: IERC20(address(0)) // Payment in FIL + paymentTokenAddress: IERC20(address(0)), // Payment in FIL + ipniPeerId: hex"" }); updatedPDPData = ServiceProviderRegistryStorage.PDPOffering({ @@ -73,7 +74,8 @@ contract ServiceProviderRegistryFullTest is Test { storagePricePerTibPerMonth: 2000000000000000000, // 2 FIL per TiB per month minProvingPeriodInEpochs: 1440, // 12 hours in epochs location: "Europe", - paymentTokenAddress: IERC20(address(0)) // Payment in FIL + paymentTokenAddress: IERC20(address(0)), // Payment in FIL + ipniPeerId: hex"" }); // Encode PDP data @@ -1800,4 +1802,183 @@ contract ServiceProviderRegistryFullTest is Test { assertFalse(tierCleared, "Tier key should not exist after update"); assertEq(clearedTier, "", "Tier key should be cleared after update"); } + + // ========== IPNI Peer ID Validation Tests ========== + + function testRegisterWithEmptyIpniPeerId() public { + ServiceProviderRegistryStorage.PDPOffering memory validPDP = defaultPDPData; + validPDP.ipniPeerId = hex""; + bytes memory encodedPDP = abi.encode(validPDP); + string[] memory emptyKeys = new string[](0); + string[] memory emptyValues = new string[](0); + + vm.prank(provider1); + uint256 providerId = registry.registerProvider{value: REGISTRATION_FEE}( + provider1, + "", + "Test provider", + ServiceProviderRegistryStorage.ProductType.PDP, + encodedPDP, + emptyKeys, + emptyValues + ); + assertEq(providerId, 1, "Should succeed with empty ipniPeerId"); + + // Verify it was stored correctly + (ServiceProviderRegistryStorage.PDPOffering memory stored,,) = registry.getPDPService(providerId); + assertEq(stored.ipniPeerId.length, 0, "Empty IPNI peer ID should be stored"); + } + + function testRegisterWithMaxLengthIpniPeerId() public { + bytes memory maxPeerId = new bytes(128); + for (uint256 i = 0; i < 128; i++) { + maxPeerId[i] = 0xFF; + } + + ServiceProviderRegistryStorage.PDPOffering memory validPDP = defaultPDPData; + validPDP.ipniPeerId = maxPeerId; + bytes memory encodedPDP = abi.encode(validPDP); + string[] memory emptyKeys = new string[](0); + string[] memory emptyValues = new string[](0); + + vm.prank(provider1); + uint256 providerId = registry.registerProvider{value: REGISTRATION_FEE}( + provider1, + "", + "Test provider", + ServiceProviderRegistryStorage.ProductType.PDP, + encodedPDP, + emptyKeys, + emptyValues + ); + assertEq(providerId, 1, "Should succeed with 128-byte ipniPeerId"); + + // Verify it was stored correctly + (ServiceProviderRegistryStorage.PDPOffering memory stored,,) = registry.getPDPService(providerId); + assertEq(stored.ipniPeerId.length, 128, "128-byte IPNI peer ID should be stored"); + assertEq(stored.ipniPeerId, maxPeerId, "IPNI peer ID should match"); + } + + function testRegisterWithTooLongIpniPeerId() public { + bytes memory longPeerId = new bytes(129); + for (uint256 i = 0; i < 129; i++) { + longPeerId[i] = 0xFF; + } + + ServiceProviderRegistryStorage.PDPOffering memory invalidPDP = defaultPDPData; + invalidPDP.ipniPeerId = longPeerId; + bytes memory encodedInvalidPDP = abi.encode(invalidPDP); + string[] memory emptyKeys = new string[](0); + string[] memory emptyValues = new string[](0); + + vm.prank(provider1); + vm.expectRevert("IPNI peer ID too long"); + registry.registerProvider{value: REGISTRATION_FEE}( + provider1, + "", + "Test provider", + ServiceProviderRegistryStorage.ProductType.PDP, + encodedInvalidPDP, + emptyKeys, + emptyValues + ); + } + + function testRegisterWithRealisticIpniPeerId() public { + // Typical libp2p peer ID is ~38 bytes (multihash of public key) + // Format: 0x1220<32-byte sha256 hash> for a total of 34 bytes + bytes memory realisticPeerId = hex"12205f8bb7e5e0e8e5f8e5e0e8e5f8e5e0e8e5f8e5e0e8e5f8e5e0e8e5f8abcdef01"; + + ServiceProviderRegistryStorage.PDPOffering memory validPDP = defaultPDPData; + validPDP.ipniPeerId = realisticPeerId; + bytes memory encodedPDP = abi.encode(validPDP); + string[] memory emptyKeys = new string[](0); + string[] memory emptyValues = new string[](0); + + vm.prank(provider1); + uint256 providerId = registry.registerProvider{value: REGISTRATION_FEE}( + provider1, + "", + "Test provider", + ServiceProviderRegistryStorage.ProductType.PDP, + encodedPDP, + emptyKeys, + emptyValues + ); + + // Verify it was stored correctly + (ServiceProviderRegistryStorage.PDPOffering memory stored,,) = registry.getPDPService(providerId); + assertEq(stored.ipniPeerId, realisticPeerId, "IPNI peer ID should be stored correctly"); + assertEq(stored.ipniPeerId.length, 34, "Realistic IPNI peer ID should be 34 bytes"); + } + + function testUpdateIpniPeerId() public { + // Register with empty IPNI peer ID + string[] memory emptyKeys = new string[](0); + string[] memory emptyValues = new string[](0); + + vm.prank(provider1); + uint256 providerId = registry.registerProvider{value: REGISTRATION_FEE}( + provider1, + "", + "Test provider", + ServiceProviderRegistryStorage.ProductType.PDP, + encodedDefaultPDPData, + emptyKeys, + emptyValues + ); + + // Verify initial state + (ServiceProviderRegistryStorage.PDPOffering memory initialStored,,) = registry.getPDPService(providerId); + assertEq(initialStored.ipniPeerId.length, 0, "Initial IPNI peer ID should be empty"); + + // Update with a peer ID + bytes memory newPeerId = hex"12205f8bb7e5e0e8e5f8e5e0e8e5f8e5e0e8e5f8e5e0e8e5f8e5e0e8e5f8abcdef01"; + ServiceProviderRegistryStorage.PDPOffering memory updatedData = updatedPDPData; + updatedData.ipniPeerId = newPeerId; + bytes memory encodedUpdatedData = abi.encode(updatedData); + + vm.prank(provider1); + registry.updateProduct( + ServiceProviderRegistryStorage.ProductType.PDP, encodedUpdatedData, emptyKeys, emptyValues + ); + + // Verify update + (ServiceProviderRegistryStorage.PDPOffering memory updatedStored,,) = registry.getPDPService(providerId); + assertEq(updatedStored.ipniPeerId, newPeerId, "IPNI peer ID should be updated"); + assertEq(updatedStored.ipniPeerId.length, 34, "Updated IPNI peer ID should be 34 bytes"); + } + + function testUpdateWithTooLongIpniPeerId() public { + // Register first + string[] memory emptyKeys = new string[](0); + string[] memory emptyValues = new string[](0); + + vm.prank(provider1); + registry.registerProvider{value: REGISTRATION_FEE}( + provider1, + "", + "Test provider", + ServiceProviderRegistryStorage.ProductType.PDP, + encodedDefaultPDPData, + emptyKeys, + emptyValues + ); + + // Try to update with too long peer ID + bytes memory longPeerId = new bytes(129); + for (uint256 i = 0; i < 129; i++) { + longPeerId[i] = 0xAB; + } + + ServiceProviderRegistryStorage.PDPOffering memory invalidData = updatedPDPData; + invalidData.ipniPeerId = longPeerId; + bytes memory encodedInvalidData = abi.encode(invalidData); + + vm.prank(provider1); + vm.expectRevert("IPNI peer ID too long"); + registry.updateProduct( + ServiceProviderRegistryStorage.ProductType.PDP, encodedInvalidData, emptyKeys, emptyValues + ); + } } diff --git a/service_contracts/test/ServiceProviderRegistryPagination.t.sol b/service_contracts/test/ServiceProviderRegistryPagination.t.sol index 7174eec9..6e26ebf0 100644 --- a/service_contracts/test/ServiceProviderRegistryPagination.t.sol +++ b/service_contracts/test/ServiceProviderRegistryPagination.t.sol @@ -48,7 +48,8 @@ contract ServiceProviderRegistryPaginationTest is Test { storagePricePerTibPerMonth: 100, minProvingPeriodInEpochs: 10, location: "US-WEST", - paymentTokenAddress: IERC20(address(0)) + paymentTokenAddress: IERC20(address(0)), + ipniPeerId: hex"" }); encodedDefaultPDPData = abi.encode(defaultPDPData);