From ba25df6d471d49ccfa7438e4097e6100b6bc9361 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Mon, 20 Oct 2025 17:29:59 -0500 Subject: [PATCH 01/25] wip bloom schema --- .../src/ServiceProviderRegistry.sol | 142 +++++------------- .../src/ServiceProviderRegistryStorage.sol | 14 -- service_contracts/src/lib/BloomSet.sol | 35 +++++ service_contracts/test/ProductTypes.t.sol | 19 +++ 4 files changed, 93 insertions(+), 117 deletions(-) create mode 100644 service_contracts/src/lib/BloomSet.sol create mode 100644 service_contracts/test/ProductTypes.t.sol diff --git a/service_contracts/src/ServiceProviderRegistry.sol b/service_contracts/src/ServiceProviderRegistry.sol index e946a25c..51ff7397 100644 --- a/service_contracts/src/ServiceProviderRegistry.sol +++ b/service_contracts/src/ServiceProviderRegistry.sol @@ -7,8 +7,11 @@ import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/U import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol"; import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol"; +import {BloomSet16} from "./lib/BloomSet.sol"; import {ServiceProviderRegistryStorage} from "./ServiceProviderRegistryStorage.sol"; +uint256 constant REQUIRED_PDP_KEYS = 0x586f5ac60d105d3930cca32e603a0e938965087b7c1478068b04d25461c0a371; + /// @title ServiceProviderRegistry /// @notice A registry contract for managing service providers across the Filecoin Services ecosystem contract ServiceProviderRegistry is @@ -43,7 +46,7 @@ contract ServiceProviderRegistry is uint256 public constant MAX_CAPABILITY_VALUE_LENGTH = 128; /// @notice Maximum number of capability key-value pairs per product - uint256 public constant MAX_CAPABILITIES = 10; + uint256 public constant MAX_CAPABILITIES = 24; /// @notice Maximum length for location field uint256 private constant MAX_LOCATION_LENGTH = 128; @@ -59,7 +62,6 @@ contract ServiceProviderRegistry is uint256 indexed providerId, ProductType indexed productType, address serviceProvider, - bytes productData, string[] capabilityKeys, string[] capabilityValues ); @@ -69,7 +71,6 @@ contract ServiceProviderRegistry is uint256 indexed providerId, ProductType indexed productType, address serviceProvider, - bytes productData, string[] capabilityKeys, string[] capabilityValues ); @@ -125,7 +126,6 @@ contract ServiceProviderRegistry is /// @param name Provider name (optional, max 128 chars) /// @param description Provider description (max 256 chars) /// @param productType The type of product to register - /// @param productData The encoded product configuration data /// @param capabilityKeys Array of capability keys /// @param capabilityValues Array of capability values /// @return providerId The unique ID assigned to the provider @@ -134,7 +134,6 @@ contract ServiceProviderRegistry is string calldata name, string calldata description, ProductType productType, - bytes calldata productData, string[] calldata capabilityKeys, string[] calldata capabilityValues ) external payable returns (uint256 providerId) { @@ -177,10 +176,10 @@ contract ServiceProviderRegistry is emit ProviderRegistered(providerId, msg.sender, payee); // Add the initial product using shared logic - _validateAndStoreProduct(providerId, productType, productData, capabilityKeys, capabilityValues); + _validateAndStoreProduct(providerId, productType, capabilityKeys, capabilityValues); // msg.sender is also providers[providerId].serviceProvider - emit ProductAdded(providerId, productType, msg.sender, productData, capabilityKeys, capabilityValues); + emit ProductAdded(providerId, productType, msg.sender, capabilityKeys, capabilityValues); // Burn the registration fee require(FVMPay.burn(REGISTRATION_FEE), "Burn failed"); @@ -188,29 +187,24 @@ contract ServiceProviderRegistry is /// @notice Add a new product to an existing provider /// @param productType The type of product to add - /// @param productData The encoded product configuration data /// @param capabilityKeys Array of capability keys (max 32 chars each, max 10 keys) /// @param capabilityValues Array of capability values (max 128 chars each, max 10 values) - function addProduct( - ProductType productType, - bytes calldata productData, - string[] calldata capabilityKeys, - string[] calldata capabilityValues - ) external { + function addProduct(ProductType productType, string[] calldata capabilityKeys, string[] calldata capabilityValues) + external + { // Only support PDP for now require(productType == ProductType.PDP, "Only PDP product type currently supported"); uint256 providerId = addressToProviderId[msg.sender]; require(providerId != 0, "Provider not registered"); - _addProduct(providerId, productType, productData, capabilityKeys, capabilityValues); + _addProduct(providerId, productType, capabilityKeys, capabilityValues); } /// @notice Internal function to add a product with validation function _addProduct( uint256 providerId, ProductType productType, - bytes memory productData, string[] memory capabilityKeys, string[] memory capabilityValues ) private providerExists(providerId) providerActive(providerId) onlyServiceProvider(providerId) { @@ -218,33 +212,28 @@ contract ServiceProviderRegistry is require(!providerProducts[providerId][productType].isActive, "Product already exists for this provider"); // Validate and store product - _validateAndStoreProduct(providerId, productType, productData, capabilityKeys, capabilityValues); + _validateAndStoreProduct(providerId, productType, capabilityKeys, capabilityValues); // msg.sender is providers[providerId].serviceProvider, because onlyServiceProvider - emit ProductAdded(providerId, productType, msg.sender, productData, capabilityKeys, capabilityValues); + emit ProductAdded(providerId, productType, msg.sender, capabilityKeys, capabilityValues); } /// @notice Internal function to validate and store a product (used by both register and add) function _validateAndStoreProduct( uint256 providerId, ProductType productType, - bytes memory productData, string[] memory capabilityKeys, string[] memory capabilityValues ) private { // Validate product data - _validateProductData(productType, productData); + _validateProductKeys(productType, capabilityKeys); // Validate capability k/v pairs _validateCapabilities(capabilityKeys, capabilityValues); // Store product - providerProducts[providerId][productType] = ServiceProduct({ - productType: productType, - productData: productData, - capabilityKeys: capabilityKeys, - isActive: true - }); + providerProducts[providerId][productType] = + ServiceProduct({productType: productType, capabilityKeys: capabilityKeys, isActive: true}); // Store capability values in mapping mapping(string => string) storage capabilities = productCapabilities[providerId][productType]; @@ -259,12 +248,10 @@ contract ServiceProviderRegistry is /// @notice Update an existing product configuration /// @param productType The type of product to update - /// @param productData The new encoded product configuration data /// @param capabilityKeys Array of capability keys (max 32 chars each, max 10 keys) /// @param capabilityValues Array of capability values (max 128 chars each, max 10 values) function updateProduct( ProductType productType, - bytes calldata productData, string[] calldata capabilityKeys, string[] calldata capabilityValues ) external { @@ -274,14 +261,13 @@ contract ServiceProviderRegistry is uint256 providerId = addressToProviderId[msg.sender]; require(providerId != 0, "Provider not registered"); - _updateProduct(providerId, productType, productData, capabilityKeys, capabilityValues); + _updateProduct(providerId, productType, capabilityKeys, capabilityValues); } /// @notice Internal function to update a product function _updateProduct( uint256 providerId, ProductType productType, - bytes memory productData, string[] memory capabilityKeys, string[] memory capabilityValues ) private providerExists(providerId) providerActive(providerId) onlyServiceProvider(providerId) { @@ -292,7 +278,7 @@ contract ServiceProviderRegistry is require(product.isActive, "Product does not exist for this provider"); // Validate product data - _validateProductData(productType, productData); + _validateProductKeys(productType, capabilityKeys); // Validate capability k/v pairs _validateCapabilities(capabilityKeys, capabilityValues); @@ -305,7 +291,6 @@ contract ServiceProviderRegistry is // Update product product.productType = productType; - product.productData = productData; product.capabilityKeys = capabilityKeys; product.isActive = true; @@ -315,7 +300,7 @@ contract ServiceProviderRegistry is } // msg.sender is also providers[providerId].serviceProvider, because onlyServiceProvider - emit ProductUpdated(providerId, productType, msg.sender, productData, capabilityKeys, capabilityValues); + emit ProductUpdated(providerId, productType, msg.sender, capabilityKeys, capabilityValues); } /// @notice Remove a product from a provider @@ -359,22 +344,6 @@ contract ServiceProviderRegistry is emit ProductRemoved(providerId, productType); } - /// @notice Update PDP service configuration with capabilities - /// @param pdpOffering The new PDP service configuration - /// @param capabilityKeys Array of capability keys (max 32 chars each, max 10 keys) - /// @param capabilityValues Array of capability values (max 128 chars each, max 10 values) - function updatePDPServiceWithCapabilities( - PDPOffering memory pdpOffering, - string[] memory capabilityKeys, - string[] memory capabilityValues - ) external { - uint256 providerId = addressToProviderId[msg.sender]; - require(providerId != 0, "Provider not registered"); - - bytes memory encodedData = abi.encode(pdpOffering); - _updateProduct(providerId, ProductType.PDP, encodedData, capabilityKeys, capabilityValues); - } - /// @notice Update provider information /// @param name New provider name (optional, max 128 chars) /// @param description New provider description (max 256 chars) @@ -421,20 +390,19 @@ contract ServiceProviderRegistry is // Mark all products as inactive and clear capabilities // For now just PDP, but this is extensible - if (providerProducts[providerId][ProductType.PDP].productData.length > 0) { - ServiceProduct storage product = providerProducts[providerId][ProductType.PDP]; - + ServiceProduct storage product = providerProducts[providerId][ProductType.PDP]; + if (product.isActive) { // Decrement active count if product was active - if (product.isActive) { - activeProductTypeProviderCount[ProductType.PDP]--; - } + activeProductTypeProviderCount[ProductType.PDP]--; // Clear capabilities from mapping mapping(string => string) storage capabilities = productCapabilities[providerId][ProductType.PDP]; for (uint256 i = 0; i < product.capabilityKeys.length; i++) { delete capabilities[product.capabilityKeys[i]]; } - product.isActive = false; + delete product.productType; + delete product.capabilityKeys; + delete product.isActive; } // Clear address mapping @@ -467,37 +435,16 @@ contract ServiceProviderRegistry is /// @notice Get product data for a specific product type /// @param providerId The ID of the provider /// @param productType The type of product to retrieve - /// @return productData The encoded product data /// @return capabilityKeys Array of capability keys /// @return isActive Whether the product is active function getProduct(uint256 providerId, ProductType productType) external view providerExists(providerId) - returns (bytes memory productData, string[] memory capabilityKeys, bool isActive) + returns (string[] memory capabilityKeys, bool isActive) { ServiceProduct memory product = providerProducts[providerId][productType]; - return (product.productData, product.capabilityKeys, product.isActive); - } - - /// @notice Get PDP service configuration for a provider (convenience function) - /// @param providerId The ID of the provider - /// @return pdpOffering The decoded PDP service data - /// @return capabilityKeys Array of capability keys - /// @return isActive Whether the PDP service is active - function getPDPService(uint256 providerId) - external - view - providerExists(providerId) - returns (PDPOffering memory pdpOffering, string[] memory capabilityKeys, bool isActive) - { - ServiceProduct memory product = providerProducts[providerId][ProductType.PDP]; - - if (product.productData.length > 0) { - pdpOffering = abi.decode(product.productData, (PDPOffering)); - capabilityKeys = product.capabilityKeys; - isActive = product.isActive; - } + return (product.capabilityKeys, product.isActive); } /// @notice Get all providers that offer a specific product type with pagination @@ -532,7 +479,7 @@ contract ServiceProviderRegistry is uint256 resultIndex = 0; for (uint256 i = 1; i <= numProviders && resultIndex < limit; i++) { - if (providerProducts[i][productType].productData.length > 0) { + if (providerProducts[i][productType].isActive) { if (currentIndex >= offset && currentIndex < offset + limit) { ServiceProviderInfo storage provider = providers[i]; result.providers[resultIndex] = ProviderWithProduct({ @@ -579,10 +526,7 @@ contract ServiceProviderRegistry is uint256 resultIndex = 0; for (uint256 i = 1; i <= numProviders && resultIndex < limit; i++) { - if ( - providers[i].isActive && providerProducts[i][productType].isActive - && providerProducts[i][productType].productData.length > 0 - ) { + if (providers[i].isActive && providerProducts[i][productType].isActive) { if (currentIndex >= offset && currentIndex < offset + limit) { ServiceProviderInfo storage provider = providers[i]; result.providers[resultIndex] = ProviderWithProduct({ @@ -795,29 +739,21 @@ contract ServiceProviderRegistry is /// @notice Validate product data based on product type /// @param productType The type of product - /// @param productData The encoded product data - function _validateProductData(ProductType productType, bytes memory productData) private pure { + function _validateProductKeys(ProductType productType, string[] memory capabilityKeys) private pure { + uint256 requiredKeys; if (productType == ProductType.PDP) { - PDPOffering memory pdpOffering = abi.decode(productData, (PDPOffering)); - _validatePDPOffering(pdpOffering); + requiredKeys = REQUIRED_PDP_KEYS; } else { revert("Unsupported product type"); } - } - - /// @notice Validate PDP offering - function _validatePDPOffering(PDPOffering memory pdpOffering) private pure { - require(bytes(pdpOffering.serviceURL).length > 0, "Service URL cannot be empty"); - require(bytes(pdpOffering.serviceURL).length <= MAX_SERVICE_URL_LENGTH, "Service URL too long"); - require(pdpOffering.minPieceSizeInBytes > 0, "Min piece size must be greater than 0"); - require( - pdpOffering.maxPieceSizeInBytes >= pdpOffering.minPieceSizeInBytes, - "Max piece size must be >= min piece size" - ); - // Validate new fields - 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"); + uint256 foundKeys = 0; + for (uint256 i = 0; i < capabilityKeys.length; i++) { + uint256 key = BloomSet16.compressed(capabilityKeys[i]); + if (BloomSet16.mayContain(requiredKeys, key)) { + foundKeys |= key; + } + } + require(BloomSet16.mayContain(foundKeys, requiredKeys)); } /// @notice Validate capability key-value pairs diff --git a/service_contracts/src/ServiceProviderRegistryStorage.sol b/service_contracts/src/ServiceProviderRegistryStorage.sol index f5c5a16c..ce373c86 100644 --- a/service_contracts/src/ServiceProviderRegistryStorage.sol +++ b/service_contracts/src/ServiceProviderRegistryStorage.sol @@ -28,24 +28,10 @@ contract ServiceProviderRegistryStorage { /// @notice Product offering of the Service Provider struct ServiceProduct { ProductType productType; - bytes productData; // ABI-encoded service-specific data string[] capabilityKeys; // Max MAX_CAPABILITY_KEY_LENGTH chars each bool isActive; } - /// @notice PDP-specific service data - struct PDPOffering { - string serviceURL; // HTTP API endpoint - uint256 minPieceSizeInBytes; // Minimum piece size accepted in bytes - uint256 maxPieceSizeInBytes; // Maximum piece size accepted in bytes - bool ipniPiece; // Supports IPNI piece CID indexing - bool ipniIpfs; // Supports IPNI IPFS CID indexing - uint256 storagePricePerTibPerMonth; // Storage price per TiB per month (in token's smallest unit) - 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) - } - /// @notice Combined provider and product information for detailed queries struct ProviderWithProduct { uint256 providerId; diff --git a/service_contracts/src/lib/BloomSet.sol b/service_contracts/src/lib/BloomSet.sol new file mode 100644 index 00000000..bbe8f53a --- /dev/null +++ b/service_contracts/src/lib/BloomSet.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT +pragma solidity ^0.8.30; + +/// @notice Bloom Filter with fixed params: +/// @notice k = 16 bits per item +/// @notice m = 256 bits per set +/// @dev probability of false positives by number of items: (via https://hur.st/bloomfilter/?m=256&k=32) +/// @dev 7: 0.000000062 +/// @dev 8: 0.00000033 +/// @dev 9: 0.000001377 +/// @dev 10: 0.000004735 +/// @dev 11: 0.000013933 +/// @dev 12: 0.000036084 +/// @dev 13: 0.000084014 +/// @dev 14: 0.000178789 +/// @dev 15: 0.000352341 +library BloomSet16 { + function compressed(string memory uncompressed) internal pure returns (uint256 item) { + uint256 hash; + assembly ("memory-safe") { + hash := keccak256(add(32, uncompressed), mload(uncompressed)) + item := 0 + } + for (uint256 i = 0; i < 16; i++) { + item |= 1 << (hash & 0xff); + hash >>= 16; + } + } + + /// @notice Checks if set probably contains the items + /// @return false when the set is definitely missing at least one of the items + function mayContain(uint256 set, uint256 items) internal pure returns (bool) { + return set & items == items; + } +} diff --git a/service_contracts/test/ProductTypes.t.sol b/service_contracts/test/ProductTypes.t.sol new file mode 100644 index 00000000..66dbee97 --- /dev/null +++ b/service_contracts/test/ProductTypes.t.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT +pragma solidity ^0.8.30; + +import {Test} from "forge-std/Test.sol"; +import {REQUIRED_PDP_KEYS} from "../src/ServiceProviderRegistry.sol"; +import {BloomSet16} from "../src/lib/BloomSet.sol"; + +contract ProductTypesTest is Test { + function testPDPKeys() public pure { + uint256 expected = ( + BloomSet16.compressed("serviceURL") | BloomSet16.compressed("minPieceSizeInBytes") + | BloomSet16.compressed("maxPieceSizeInBytes") | BloomSet16.compressed("ipniPiece") + | BloomSet16.compressed("ipniIpfs") | BloomSet16.compressed("storagePricePerTibPerDay") + | BloomSet16.compressed("minProvingPeriodInEpochs") | BloomSet16.compressed("location") + | BloomSet16.compressed("paymentTokenAddress") + ); + assertEq(expected, REQUIRED_PDP_KEYS); + } +} From 8a503aad0e74d66937932ae63212d1dbb153c6dd Mon Sep 17 00:00:00 2001 From: William Morriss Date: Mon, 20 Oct 2025 17:40:03 -0500 Subject: [PATCH 02/25] only shift by 8 --- service_contracts/src/ServiceProviderRegistry.sol | 2 +- service_contracts/src/lib/BloomSet.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/service_contracts/src/ServiceProviderRegistry.sol b/service_contracts/src/ServiceProviderRegistry.sol index 51ff7397..b5d2b16d 100644 --- a/service_contracts/src/ServiceProviderRegistry.sol +++ b/service_contracts/src/ServiceProviderRegistry.sol @@ -10,7 +10,7 @@ import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.s import {BloomSet16} from "./lib/BloomSet.sol"; import {ServiceProviderRegistryStorage} from "./ServiceProviderRegistryStorage.sol"; -uint256 constant REQUIRED_PDP_KEYS = 0x586f5ac60d105d3930cca32e603a0e938965087b7c1478068b04d25461c0a371; +uint256 constant REQUIRED_PDP_KEYS = 0x5b6e96f24dd05fa9218c80c8422a0eb70947d833531db3c4db14504405c0e132; /// @title ServiceProviderRegistry /// @notice A registry contract for managing service providers across the Filecoin Services ecosystem diff --git a/service_contracts/src/lib/BloomSet.sol b/service_contracts/src/lib/BloomSet.sol index bbe8f53a..5920a487 100644 --- a/service_contracts/src/lib/BloomSet.sol +++ b/service_contracts/src/lib/BloomSet.sol @@ -23,7 +23,7 @@ library BloomSet16 { } for (uint256 i = 0; i < 16; i++) { item |= 1 << (hash & 0xff); - hash >>= 16; + hash >>= 8; } } From 934f14eb666de6b99b1a833104a35cd04d0101ee Mon Sep 17 00:00:00 2001 From: William Morriss Date: Mon, 20 Oct 2025 17:41:48 -0500 Subject: [PATCH 03/25] parameterize K --- service_contracts/src/lib/BloomSet.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/service_contracts/src/lib/BloomSet.sol b/service_contracts/src/lib/BloomSet.sol index 5920a487..9f579ab2 100644 --- a/service_contracts/src/lib/BloomSet.sol +++ b/service_contracts/src/lib/BloomSet.sol @@ -15,13 +15,15 @@ pragma solidity ^0.8.30; /// @dev 14: 0.000178789 /// @dev 15: 0.000352341 library BloomSet16 { + uint256 private constant K = 16; + function compressed(string memory uncompressed) internal pure returns (uint256 item) { uint256 hash; assembly ("memory-safe") { hash := keccak256(add(32, uncompressed), mload(uncompressed)) item := 0 } - for (uint256 i = 0; i < 16; i++) { + for (uint256 i = 0; i < K; i++) { item |= 1 << (hash & 0xff); hash >>= 8; } From c08fb7a788def1477dc30faf8a16a5ca24b5c378 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Tue, 21 Oct 2025 00:25:46 -0500 Subject: [PATCH 04/25] test BloomSet16 --- .../src/ServiceProviderRegistry.sol | 2 +- service_contracts/src/lib/BloomSet.sol | 1 + service_contracts/test/BloomSet.t.sol | 52 +++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 service_contracts/test/BloomSet.t.sol diff --git a/service_contracts/src/ServiceProviderRegistry.sol b/service_contracts/src/ServiceProviderRegistry.sol index b5d2b16d..668ab2db 100644 --- a/service_contracts/src/ServiceProviderRegistry.sol +++ b/service_contracts/src/ServiceProviderRegistry.sol @@ -746,7 +746,7 @@ contract ServiceProviderRegistry is } else { revert("Unsupported product type"); } - uint256 foundKeys = 0; + uint256 foundKeys = BloomSet16.EMPTY; for (uint256 i = 0; i < capabilityKeys.length; i++) { uint256 key = BloomSet16.compressed(capabilityKeys[i]); if (BloomSet16.mayContain(requiredKeys, key)) { diff --git a/service_contracts/src/lib/BloomSet.sol b/service_contracts/src/lib/BloomSet.sol index 9f579ab2..0a091690 100644 --- a/service_contracts/src/lib/BloomSet.sol +++ b/service_contracts/src/lib/BloomSet.sol @@ -16,6 +16,7 @@ pragma solidity ^0.8.30; /// @dev 15: 0.000352341 library BloomSet16 { uint256 private constant K = 16; + uint256 internal constant EMPTY = 0; function compressed(string memory uncompressed) internal pure returns (uint256 item) { uint256 hash; diff --git a/service_contracts/test/BloomSet.t.sol b/service_contracts/test/BloomSet.t.sol new file mode 100644 index 00000000..3da3f9f0 --- /dev/null +++ b/service_contracts/test/BloomSet.t.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT +pragma solidity ^0.8.30; + +import {Test} from "forge-std/Test.sol"; +import {BloomSet16} from "../src/lib/BloomSet.sol"; + +contract BloomSetTest is Test { + using BloomSet16 for string; + using BloomSet16 for uint256; + + function testIdentical() public pure { + uint256 set = BloomSet16.EMPTY; + assertTrue(set.mayContain(BloomSet16.EMPTY)); + string[] memory same = new string[](6); + for (uint256 i = 0; i < same.length; i++) { + same[i] = "theVerySame"; + } + for (uint256 i = 0; i < same.length; i++) { + set |= same[i].compressed(); + } + assertTrue(set.mayContain(BloomSet16.EMPTY)); + assertTrue(set.mayContain(set)); + string memory verySame = "theVerySame"; + assertEq(set, verySame.compressed()); + assertTrue(set.mayContain(verySame.compressed())); + string memory fromCat = string.concat(string.concat("the", "Very"), "Same"); + assertEq(verySame.compressed(), fromCat.compressed()); + } + + function testDifferent() public pure { + uint256 set = BloomSet16.EMPTY; + string memory one = "1"; + string memory two = "2"; + string memory three = "3"; + set |= one.compressed(); + assertTrue(set.mayContain(one.compressed())); + assertFalse(set.mayContain(two.compressed())); + assertFalse(set.mayContain(three.compressed())); + set |= two.compressed(); + assertTrue(set.mayContain(BloomSet16.EMPTY)); + assertTrue(set.mayContain(one.compressed())); + assertTrue(set.mayContain(two.compressed())); + assertFalse(set.mayContain(three.compressed())); + assertFalse(one.compressed().mayContain(set)); + assertFalse(two.compressed().mayContain(set)); + assertFalse(three.compressed().mayContain(set)); + assertFalse(BloomSet16.EMPTY.mayContain(set)); + assertFalse(BloomSet16.EMPTY.mayContain(one.compressed())); + assertFalse(BloomSet16.EMPTY.mayContain(two.compressed())); + assertFalse(BloomSet16.EMPTY.mayContain(three.compressed())); + } +} From 35dd6c39ed71df7fc951c9498d4d4d7085193012 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Tue, 21 Oct 2025 16:12:46 -0500 Subject: [PATCH 05/25] Errors.InsufficientCapabilitiesForProduct, and restore prior PDPOffering documentation --- service_contracts/src/Errors.sol | 6 ++++++ service_contracts/src/ServiceProviderRegistry.sol | 15 ++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/service_contracts/src/Errors.sol b/service_contracts/src/Errors.sol index a42a5168..4d55511f 100644 --- a/service_contracts/src/Errors.sol +++ b/service_contracts/src/Errors.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT pragma solidity ^0.8.27; +import {ServiceProviderRegistryStorage} from "./ServiceProviderRegistryStorage.sol"; + /// @title Errors /// @notice Centralized library for custom error definitions across the protocol library Errors { @@ -287,4 +289,8 @@ library Errors { /// @param dataSetId The data set ID /// @param pdpEndEpoch The end epoch when the PDP payment rail will finalize error PaymentRailsNotFinalized(uint256 dataSetId, uint256 pdpEndEpoch); + + /// @notice The supplied capability keys did not contain all of the required keys for the product type + /// @param productType The kind of service product attempted to be registered + error InsufficientCapabilitiesForProduct(ServiceProviderRegistryStorage.ProductType productType); } diff --git a/service_contracts/src/ServiceProviderRegistry.sol b/service_contracts/src/ServiceProviderRegistry.sol index 668ab2db..af4c8543 100644 --- a/service_contracts/src/ServiceProviderRegistry.sol +++ b/service_contracts/src/ServiceProviderRegistry.sol @@ -8,8 +8,20 @@ import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Own import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol"; import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol"; import {BloomSet16} from "./lib/BloomSet.sol"; +import {Errors} from "./Errors.sol"; import {ServiceProviderRegistryStorage} from "./ServiceProviderRegistryStorage.sol"; +/** + * Required PDP Keys: + * - serviceURL: the API endpoint + * - minPieceSizeInBytes: minimum piece size in bytes + * - maxPieceSizeInBytes: maximum piece size in bytes + * - ipniPiece: Supports IPNI piece CID indexing + * - ipniIpfs: Supports IPNI IPFS CID indexing + * - storagePricePerTibPerDay: Storage price per TiB per day (in token's smallest unit) + * - minProvingPeriodInEpochs: Minimum proving period in epochs + * - paymentTokenAddress: Token contract for payment (IERC20(address(0)) for FIL) + */ uint256 constant REQUIRED_PDP_KEYS = 0x5b6e96f24dd05fa9218c80c8422a0eb70947d833531db3c4db14504405c0e132; /// @title ServiceProviderRegistry @@ -753,7 +765,8 @@ contract ServiceProviderRegistry is foundKeys |= key; } } - require(BloomSet16.mayContain(foundKeys, requiredKeys)); + // Enforce minimum schema + require(BloomSet16.mayContain(foundKeys, requiredKeys), Errors.InsufficientCapabilitiesForProduct(productType)); } /// @notice Validate capability key-value pairs From 2fb481e2977e3d7b4479a1e6a4f5a592a24795a5 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Wed, 22 Oct 2025 16:25:33 -0500 Subject: [PATCH 06/25] must tag as dev --- .../src/ServiceProviderRegistry.sol | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/service_contracts/src/ServiceProviderRegistry.sol b/service_contracts/src/ServiceProviderRegistry.sol index af4c8543..97b91d96 100644 --- a/service_contracts/src/ServiceProviderRegistry.sol +++ b/service_contracts/src/ServiceProviderRegistry.sol @@ -11,17 +11,16 @@ import {BloomSet16} from "./lib/BloomSet.sol"; import {Errors} from "./Errors.sol"; import {ServiceProviderRegistryStorage} from "./ServiceProviderRegistryStorage.sol"; -/** - * Required PDP Keys: - * - serviceURL: the API endpoint - * - minPieceSizeInBytes: minimum piece size in bytes - * - maxPieceSizeInBytes: maximum piece size in bytes - * - ipniPiece: Supports IPNI piece CID indexing - * - ipniIpfs: Supports IPNI IPFS CID indexing - * - storagePricePerTibPerDay: Storage price per TiB per day (in token's smallest unit) - * - minProvingPeriodInEpochs: Minimum proving period in epochs - * - paymentTokenAddress: Token contract for payment (IERC20(address(0)) for FIL) - */ +/// @dev Required PDP Keys: +/// - serviceURL: the API endpoint +/// - minPieceSizeInBytes: minimum piece size in bytes +/// - maxPieceSizeInBytes: maximum piece size in bytes +/// - ipniPiece: Supports IPNI piece CID indexing +/// - ipniIpfs: Supports IPNI IPFS CID indexing +/// - storagePricePerTibPerDay: Storage price per TiB per day (in token's smallest unit) +/// - minProvingPeriodInEpochs: Minimum proving period in epochs +/// - paymentTokenAddress: Token contract for payment (IERC20(address(0)) for FIL) + uint256 constant REQUIRED_PDP_KEYS = 0x5b6e96f24dd05fa9218c80c8422a0eb70947d833531db3c4db14504405c0e132; /// @title ServiceProviderRegistry From 1fdfd06a32e57783f7ecccd26fa2605bc2ce4eaf Mon Sep 17 00:00:00 2001 From: William Morriss Date: Wed, 22 Oct 2025 19:14:38 -0500 Subject: [PATCH 07/25] bytes[] capability values --- service_contracts/src/PDPOffering.sol | 80 +++++++++++++++++++ .../src/ServiceProviderRegistry.sol | 46 +++++------ .../src/ServiceProviderRegistryStorage.sol | 2 +- 3 files changed, 103 insertions(+), 25 deletions(-) create mode 100644 service_contracts/src/PDPOffering.sol diff --git a/service_contracts/src/PDPOffering.sol b/service_contracts/src/PDPOffering.sol new file mode 100644 index 00000000..0552d5f4 --- /dev/null +++ b/service_contracts/src/PDPOffering.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.30; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +/// @notice PDP-specific service data +library PDPOffering { + struct Schema { + string serviceURL; // HTTP API endpoint + uint256 minPieceSizeInBytes; // Minimum piece size accepted in bytes + uint256 maxPieceSizeInBytes; // Maximum piece size accepted in bytes + bool ipniPiece; // Supports IPNI piece CID indexing + bool ipniIpfs; // Supports IPNI IPFS CID indexing + uint256 storagePricePerTibPerMonth; // Storage price per TiB per month (in token's smallest unit) + 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) + } + + function fromCapabilities(string[] memory keys, bytes[] memory values) + internal + pure + returns (Schema memory schema) + { + require(keys.length == values.length, "Keys and values arrays must have same length"); + for (uint256 i = 0; i < keys.length; i++) { + bytes32 hash = keccak256(bytes(keys[i])); + if (hash == keccak256("serviceURL")) { + schema.serviceURL = string(values[i]); + } else if (hash == keccak256("minPieceSizeInBytes")) { + schema.minPieceSizeInBytes = abi.decode(values[i], (uint256)); + } + if (hash == keccak256("maxPieceSizeInBytes")) { + schema.maxPieceSizeInBytes = abi.decode(values[i], (uint256)); + } + if (hash == keccak256("ipniPiece")) { + schema.ipniPiece = abi.decode(values[i], (bool)); + } + if (hash == keccak256("ipniIpfs")) { + schema.ipniIpfs = abi.decode(values[i], (bool)); + } + if (hash == keccak256("storagePricePerTibPerMonth")) { + schema.storagePricePerTibPerMonth = abi.decode(values[i], (uint256)); + } + if (hash == keccak256("minProvingPeriodInEpochs")) { + schema.minProvingPeriodInEpochs = abi.decode(values[i], (uint256)); + } + if (hash == keccak256("location")) { + schema.location = string(values[i]); + } + if (hash == keccak256("paymentTokenAddress")) { + schema.paymentTokenAddress = abi.decode(values[i], (IERC20)); + } + } + return schema; + } + + function toCapabilities(Schema memory schema) internal pure returns (string[] memory keys, bytes[] memory values) { + keys = new string[](9); + values = new bytes[](9); + keys[0] = "serviceURL"; + values[0] = bytes(schema.serviceURL); + keys[1] = "minPieceSizeInBytes"; + values[1] = abi.encode(schema.minPieceSizeInBytes); + keys[2] = "maxPieceSizeInBytes"; + values[2] = abi.encode(schema.maxPieceSizeInBytes); + keys[3] = "ipniPiece"; + values[3] = abi.encode(schema.ipniPiece); + keys[4] = "ipniIpfs"; + values[4] = abi.encode(schema.ipniIpfs); + keys[5] = "storagePricePerTibPerMonth"; + values[5] = abi.encode(schema.storagePricePerTibPerMonth); + keys[6] = "minProvingPeriodInEpochs"; + values[6] = abi.encode(schema.minProvingPeriodInEpochs); + keys[7] = "location"; + values[7] = bytes(schema.location); + keys[8] = "paymentTokenAddress"; + values[8] = abi.encode(schema.paymentTokenAddress); + } +} diff --git a/service_contracts/src/ServiceProviderRegistry.sol b/service_contracts/src/ServiceProviderRegistry.sol index 97b91d96..0a723c10 100644 --- a/service_contracts/src/ServiceProviderRegistry.sol +++ b/service_contracts/src/ServiceProviderRegistry.sol @@ -74,7 +74,7 @@ contract ServiceProviderRegistry is ProductType indexed productType, address serviceProvider, string[] capabilityKeys, - string[] capabilityValues + bytes[] capabilityValues ); /// @notice Emitted when a product is added to an existing provider @@ -83,7 +83,7 @@ contract ServiceProviderRegistry is ProductType indexed productType, address serviceProvider, string[] capabilityKeys, - string[] capabilityValues + bytes[] capabilityValues ); /// @notice Emitted when a product is removed from a provider @@ -146,7 +146,7 @@ contract ServiceProviderRegistry is string calldata description, ProductType productType, string[] calldata capabilityKeys, - string[] calldata capabilityValues + bytes[] calldata capabilityValues ) external payable returns (uint256 providerId) { // Only support PDP for now require(productType == ProductType.PDP, "Only PDP product type currently supported"); @@ -200,7 +200,7 @@ contract ServiceProviderRegistry is /// @param productType The type of product to add /// @param capabilityKeys Array of capability keys (max 32 chars each, max 10 keys) /// @param capabilityValues Array of capability values (max 128 chars each, max 10 values) - function addProduct(ProductType productType, string[] calldata capabilityKeys, string[] calldata capabilityValues) + function addProduct(ProductType productType, string[] calldata capabilityKeys, bytes[] calldata capabilityValues) external { // Only support PDP for now @@ -217,7 +217,7 @@ contract ServiceProviderRegistry is uint256 providerId, ProductType productType, string[] memory capabilityKeys, - string[] memory capabilityValues + bytes[] calldata capabilityValues ) private providerExists(providerId) providerActive(providerId) onlyServiceProvider(providerId) { // Check product doesn't already exist require(!providerProducts[providerId][productType].isActive, "Product already exists for this provider"); @@ -234,7 +234,7 @@ contract ServiceProviderRegistry is uint256 providerId, ProductType productType, string[] memory capabilityKeys, - string[] memory capabilityValues + bytes[] calldata capabilityValues ) private { // Validate product data _validateProductKeys(productType, capabilityKeys); @@ -247,7 +247,7 @@ contract ServiceProviderRegistry is ServiceProduct({productType: productType, capabilityKeys: capabilityKeys, isActive: true}); // Store capability values in mapping - mapping(string => string) storage capabilities = productCapabilities[providerId][productType]; + mapping(string => bytes) storage capabilities = productCapabilities[providerId][productType]; for (uint256 i = 0; i < capabilityKeys.length; i++) { capabilities[capabilityKeys[i]] = capabilityValues[i]; } @@ -261,11 +261,9 @@ contract ServiceProviderRegistry is /// @param productType The type of product to update /// @param capabilityKeys Array of capability keys (max 32 chars each, max 10 keys) /// @param capabilityValues Array of capability values (max 128 chars each, max 10 values) - function updateProduct( - ProductType productType, - string[] calldata capabilityKeys, - string[] calldata capabilityValues - ) external { + function updateProduct(ProductType productType, string[] calldata capabilityKeys, bytes[] calldata capabilityValues) + external + { // Only support PDP for now require(productType == ProductType.PDP, "Only PDP product type currently supported"); @@ -280,7 +278,7 @@ contract ServiceProviderRegistry is uint256 providerId, ProductType productType, string[] memory capabilityKeys, - string[] memory capabilityValues + bytes[] calldata capabilityValues ) private providerExists(providerId) providerActive(providerId) onlyServiceProvider(providerId) { // Cache product storage reference ServiceProduct storage product = providerProducts[providerId][productType]; @@ -295,7 +293,7 @@ contract ServiceProviderRegistry is _validateCapabilities(capabilityKeys, capabilityValues); // Clear old capabilities from mapping - mapping(string => string) storage capabilities = productCapabilities[providerId][productType]; + mapping(string => bytes) storage capabilities = productCapabilities[providerId][productType]; for (uint256 i = 0; i < product.capabilityKeys.length; i++) { delete capabilities[product.capabilityKeys[i]]; } @@ -338,7 +336,7 @@ contract ServiceProviderRegistry is // Clear capabilities from mapping ServiceProduct storage product = providerProducts[providerId][productType]; - mapping(string => string) storage capabilities = productCapabilities[providerId][productType]; + mapping(string => bytes) storage capabilities = productCapabilities[providerId][productType]; for (uint256 i = 0; i < product.capabilityKeys.length; i++) { delete capabilities[product.capabilityKeys[i]]; } @@ -407,7 +405,7 @@ contract ServiceProviderRegistry is activeProductTypeProviderCount[ProductType.PDP]--; // Clear capabilities from mapping - mapping(string => string) storage capabilities = productCapabilities[providerId][ProductType.PDP]; + mapping(string => bytes) storage capabilities = productCapabilities[providerId][ProductType.PDP]; for (uint256 i = 0; i < product.capabilityKeys.length; i++) { delete capabilities[product.capabilityKeys[i]]; } @@ -714,17 +712,17 @@ contract ServiceProviderRegistry is external view providerExists(providerId) - returns (bool[] memory exists, string[] memory values) + returns (bool[] memory exists, bytes[] memory values) { exists = new bool[](keys.length); - values = new string[](keys.length); + values = new bytes[](keys.length); // Cache the mapping reference - mapping(string => string) storage capabilities = productCapabilities[providerId][productType]; + mapping(string => bytes) storage capabilities = productCapabilities[providerId][productType]; for (uint256 i = 0; i < keys.length; i++) { - string memory value = capabilities[keys[i]]; - if (bytes(value).length > 0) { + bytes memory value = capabilities[keys[i]]; + if (value.length > 0) { exists[i] = true; values[i] = value; } @@ -741,7 +739,7 @@ contract ServiceProviderRegistry is external view providerExists(providerId) - returns (bool exists, string memory value) + returns (bool exists, bytes memory value) { // Directly check the mapping value = productCapabilities[providerId][productType][key]; @@ -771,14 +769,14 @@ contract ServiceProviderRegistry is /// @notice Validate capability key-value pairs /// @param keys Array of capability keys /// @param values Array of capability values - function _validateCapabilities(string[] memory keys, string[] memory values) private pure { + function _validateCapabilities(string[] memory keys, bytes[] calldata values) private pure { require(keys.length == values.length, "Keys and values arrays must have same length"); require(keys.length <= MAX_CAPABILITIES, "Too many capabilities"); for (uint256 i = 0; i < keys.length; i++) { require(bytes(keys[i]).length > 0, "Capability key cannot be empty"); require(bytes(keys[i]).length <= MAX_CAPABILITY_KEY_LENGTH, "Capability key too long"); - require(bytes(values[i]).length <= MAX_CAPABILITY_VALUE_LENGTH, "Capability value too long"); + require(values[i].length <= MAX_CAPABILITY_VALUE_LENGTH, "Capability value too long"); } } diff --git a/service_contracts/src/ServiceProviderRegistryStorage.sol b/service_contracts/src/ServiceProviderRegistryStorage.sol index ce373c86..147b955b 100644 --- a/service_contracts/src/ServiceProviderRegistryStorage.sol +++ b/service_contracts/src/ServiceProviderRegistryStorage.sol @@ -61,7 +61,7 @@ contract ServiceProviderRegistryStorage { mapping(address providerAddress => uint256 providerId) public addressToProviderId; /// @notice Capability values mapping for efficient lookups - mapping(uint256 providerId => mapping(ProductType productType => mapping(string key => string value))) public + mapping(uint256 providerId => mapping(ProductType productType => mapping(string key => bytes value))) public productCapabilities; /// @notice Count of providers (including inactive) offering each product type From 11dc0ba9976ed259c099a590c38a2c40c49b01e5 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Wed, 22 Oct 2025 21:48:17 -0500 Subject: [PATCH 08/25] tests build but do not yet pass --- service_contracts/src/PDPOffering.sol | 67 +- .../src/ServiceProviderRegistryStorage.sol | 2 - .../test/FilecoinWarmStorageService.t.sol | 86 +-- .../FilecoinWarmStorageServiceOwner.t.sol | 29 +- .../test/ProviderValidation.t.sol | 60 +- .../test/ServiceProviderRegistry.t.sol | 187 ++--- .../test/ServiceProviderRegistryFull.t.sol | 705 ++++++------------ .../ServiceProviderRegistryPagination.t.sol | 74 +- 8 files changed, 429 insertions(+), 781 deletions(-) diff --git a/service_contracts/src/PDPOffering.sol b/service_contracts/src/PDPOffering.sol index 0552d5f4..90a39d25 100644 --- a/service_contracts/src/PDPOffering.sol +++ b/service_contracts/src/PDPOffering.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.30; +import {ServiceProviderRegistry} from "./ServiceProviderRegistry.sol"; +import {ServiceProviderRegistryStorage} from "./ServiceProviderRegistryStorage.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; /// @notice PDP-specific service data @@ -11,7 +13,7 @@ library PDPOffering { uint256 maxPieceSizeInBytes; // Maximum piece size accepted in bytes bool ipniPiece; // Supports IPNI piece CID indexing bool ipniIpfs; // Supports IPNI IPFS CID indexing - uint256 storagePricePerTibPerMonth; // Storage price per TiB per month (in token's smallest unit) + uint256 storagePricePerTibPerDay; // Storage price per TiB per month (in token's smallest unit) 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) @@ -39,8 +41,8 @@ library PDPOffering { if (hash == keccak256("ipniIpfs")) { schema.ipniIpfs = abi.decode(values[i], (bool)); } - if (hash == keccak256("storagePricePerTibPerMonth")) { - schema.storagePricePerTibPerMonth = abi.decode(values[i], (uint256)); + if (hash == keccak256("storagePricePerTibPerDay")) { + schema.storagePricePerTibPerDay = abi.decode(values[i], (uint256)); } if (hash == keccak256("minProvingPeriodInEpochs")) { schema.minProvingPeriodInEpochs = abi.decode(values[i], (uint256)); @@ -55,26 +57,45 @@ library PDPOffering { return schema; } + function toCapabilities(Schema memory schema, uint256 extraSize) + internal + pure + returns (string[] memory keys, bytes[] memory values) + { + keys = new string[](9 + extraSize); + values = new bytes[](9 + extraSize); + keys[extraSize] = "serviceURL"; + values[extraSize] = bytes(schema.serviceURL); + keys[extraSize + 1] = "minPieceSizeInBytes"; + values[extraSize + 1] = abi.encode(schema.minPieceSizeInBytes); + keys[extraSize + 2] = "maxPieceSizeInBytes"; + values[extraSize + 2] = abi.encode(schema.maxPieceSizeInBytes); + keys[extraSize + 3] = "ipniPiece"; + values[extraSize + 3] = abi.encode(schema.ipniPiece); + keys[extraSize + 4] = "ipniIpfs"; + values[extraSize + 4] = abi.encode(schema.ipniIpfs); + keys[extraSize + 5] = "storagePricePerTibPerDay"; + values[extraSize + 5] = abi.encode(schema.storagePricePerTibPerDay); + keys[extraSize + 6] = "minProvingPeriodInEpochs"; + values[extraSize + 6] = abi.encode(schema.minProvingPeriodInEpochs); + keys[extraSize + 7] = "location"; + values[extraSize + 7] = bytes(schema.location); + keys[extraSize + 8] = "paymentTokenAddress"; + values[extraSize + 8] = abi.encode(schema.paymentTokenAddress); + } + function toCapabilities(Schema memory schema) internal pure returns (string[] memory keys, bytes[] memory values) { - keys = new string[](9); - values = new bytes[](9); - keys[0] = "serviceURL"; - values[0] = bytes(schema.serviceURL); - keys[1] = "minPieceSizeInBytes"; - values[1] = abi.encode(schema.minPieceSizeInBytes); - keys[2] = "maxPieceSizeInBytes"; - values[2] = abi.encode(schema.maxPieceSizeInBytes); - keys[3] = "ipniPiece"; - values[3] = abi.encode(schema.ipniPiece); - keys[4] = "ipniIpfs"; - values[4] = abi.encode(schema.ipniIpfs); - keys[5] = "storagePricePerTibPerMonth"; - values[5] = abi.encode(schema.storagePricePerTibPerMonth); - keys[6] = "minProvingPeriodInEpochs"; - values[6] = abi.encode(schema.minProvingPeriodInEpochs); - keys[7] = "location"; - values[7] = bytes(schema.location); - keys[8] = "paymentTokenAddress"; - values[8] = abi.encode(schema.paymentTokenAddress); + return toCapabilities(schema, 0); + } + + function getPDPService(ServiceProviderRegistry registry, uint256 providerId) + internal + view + returns (Schema memory schema, string[] memory keys, bool isActive) + { + (keys, isActive) = registry.getProduct(providerId, ServiceProviderRegistryStorage.ProductType.PDP); + (, bytes[] memory values) = + registry.getProductCapabilities(providerId, ServiceProviderRegistryStorage.ProductType.PDP, keys); + schema = fromCapabilities(keys, values); } } diff --git a/service_contracts/src/ServiceProviderRegistryStorage.sol b/service_contracts/src/ServiceProviderRegistryStorage.sol index 147b955b..0f5b898b 100644 --- a/service_contracts/src/ServiceProviderRegistryStorage.sol +++ b/service_contracts/src/ServiceProviderRegistryStorage.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT pragma solidity ^0.8.20; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - /// @title ServiceProviderRegistryStorage /// @notice Centralized storage contract for ServiceProviderRegistry /// @dev All storage variables are declared here to prevent storage slot collisions during upgrades diff --git a/service_contracts/test/FilecoinWarmStorageService.t.sol b/service_contracts/test/FilecoinWarmStorageService.t.sol index fcc8d0aa..813d4b97 100644 --- a/service_contracts/test/FilecoinWarmStorageService.t.sol +++ b/service_contracts/test/FilecoinWarmStorageService.t.sol @@ -19,11 +19,13 @@ import {MockERC20, MockPDPVerifier} from "./mocks/SharedMocks.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {Errors} from "../src/Errors.sol"; +import {PDPOffering} from "../src/PDPOffering.sol"; import {ServiceProviderRegistryStorage} from "../src/ServiceProviderRegistryStorage.sol"; import {ServiceProviderRegistry} from "../src/ServiceProviderRegistry.sol"; contract FilecoinWarmStorageServiceTest is MockFVMTest { using SafeERC20 for MockERC20; + using PDPOffering for PDPOffering.Schema; using FilecoinWarmStorageServiceStateLibrary for FilecoinWarmStorageService; // Testing Constants @@ -134,6 +136,19 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest { MyERC1967Proxy registryProxy = new MyERC1967Proxy(address(registryImpl), registryInitData); serviceProviderRegistry = ServiceProviderRegistry(address(registryProxy)); + PDPOffering.Schema memory pdpData = PDPOffering.Schema({ + serviceURL: "https://provider.com", + minPieceSizeInBytes: 1024, + maxPieceSizeInBytes: 1024 * 1024, + ipniPiece: true, + ipniIpfs: false, + storagePricePerTibPerDay: 1 ether, + minProvingPeriodInEpochs: 2880, + location: "US-Central", + paymentTokenAddress: IERC20(address(0)) // Payment in FIL + }); + (string[] memory keys, bytes[] memory values) = pdpData.toCapabilities(); + // Register service providers in the serviceProviderRegistry vm.prank(serviceProvider); serviceProviderRegistry.registerProvider{value: 5 ether}( @@ -141,90 +156,41 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest { "Service Provider", "Service Provider Description", ServiceProviderRegistryStorage.ProductType.PDP, - abi.encode( - ServiceProviderRegistryStorage.PDPOffering({ - serviceURL: "https://provider.com", - minPieceSizeInBytes: 1024, - maxPieceSizeInBytes: 1024 * 1024, - ipniPiece: true, - ipniIpfs: false, - storagePricePerTibPerMonth: 1 ether, - minProvingPeriodInEpochs: 2880, - location: "US-Central", - paymentTokenAddress: IERC20(address(0)) // Payment in FIL - }) - ), - new string[](0), - new string[](0) + keys, + values ); + values[0] = bytes("https://sp1.com"); vm.prank(sp1); serviceProviderRegistry.registerProvider{value: 5 ether}( sp1, // payee "SP1", "Storage Provider 1", ServiceProviderRegistryStorage.ProductType.PDP, - abi.encode( - ServiceProviderRegistryStorage.PDPOffering({ - serviceURL: "https://sp1.com", - minPieceSizeInBytes: 1024, - maxPieceSizeInBytes: 1024 * 1024, - ipniPiece: true, - ipniIpfs: false, - storagePricePerTibPerMonth: 1 ether, - minProvingPeriodInEpochs: 2880, - location: "US-Central", - paymentTokenAddress: IERC20(address(0)) // Payment in FIL - }) - ), - new string[](0), - new string[](0) + keys, + values ); + values[0] = bytes("https://sp2.com"); vm.prank(sp2); serviceProviderRegistry.registerProvider{value: 5 ether}( sp2, // payee "SP2", "Storage Provider 2", ServiceProviderRegistryStorage.ProductType.PDP, - abi.encode( - ServiceProviderRegistryStorage.PDPOffering({ - serviceURL: "https://sp2.com", - minPieceSizeInBytes: 1024, - maxPieceSizeInBytes: 1024 * 1024, - ipniPiece: true, - ipniIpfs: false, - storagePricePerTibPerMonth: 1 ether, - minProvingPeriodInEpochs: 2880, - location: "US-Central", - paymentTokenAddress: IERC20(address(0)) // Payment in FIL - }) - ), - new string[](0), - new string[](0) + keys, + values ); + values[0] = bytes("https://sp3.com"); vm.prank(sp3); serviceProviderRegistry.registerProvider{value: 5 ether}( sp3, // payee "SP3", "Storage Provider 3", ServiceProviderRegistryStorage.ProductType.PDP, - abi.encode( - ServiceProviderRegistryStorage.PDPOffering({ - serviceURL: "https://sp3.com", - minPieceSizeInBytes: 1024, - maxPieceSizeInBytes: 1024 * 1024, - ipniPiece: true, - ipniIpfs: false, - storagePricePerTibPerMonth: 1 ether, - minProvingPeriodInEpochs: 2880, - location: "US-Central", - paymentTokenAddress: IERC20(address(0)) // Payment in FIL - }) - ), - new string[](0), - new string[](0) + keys, + values ); // Deploy FilecoinPayV1 contract (no longer upgradeable) diff --git a/service_contracts/test/FilecoinWarmStorageServiceOwner.t.sol b/service_contracts/test/FilecoinWarmStorageServiceOwner.t.sol index 53412775..d6d23f57 100644 --- a/service_contracts/test/FilecoinWarmStorageServiceOwner.t.sol +++ b/service_contracts/test/FilecoinWarmStorageServiceOwner.t.sol @@ -5,6 +5,7 @@ import {MockFVMTest} from "@fvm-solidity/mocks/MockFVMTest.sol"; import {console} from "forge-std/Test.sol"; import {FilecoinWarmStorageService} from "../src/FilecoinWarmStorageService.sol"; import {FilecoinWarmStorageServiceStateView} from "../src/FilecoinWarmStorageServiceStateView.sol"; +import {PDPOffering} from "../src/PDPOffering.sol"; import {ServiceProviderRegistry} from "../src/ServiceProviderRegistry.sol"; import {ServiceProviderRegistryStorage} from "../src/ServiceProviderRegistryStorage.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; @@ -16,6 +17,7 @@ import {MockERC20, MockPDPVerifier} from "./mocks/SharedMocks.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract FilecoinWarmStorageServiceOwnerTest is MockFVMTest { + using PDPOffering for PDPOffering.Schema; using SafeERC20 for MockERC20; // Constants @@ -131,8 +133,18 @@ contract FilecoinWarmStorageServiceOwnerTest is MockFVMTest { } function registerProvider(address provider, string memory name) internal { - string[] memory capabilityKeys = new string[](0); - string[] memory capabilityValues = new string[](0); + PDPOffering.Schema memory schema = PDPOffering.Schema({ + serviceURL: "https://provider.com", + minPieceSizeInBytes: 1024, + maxPieceSizeInBytes: 1024 * 1024, + ipniPiece: false, + ipniIpfs: false, + storagePricePerTibPerDay: 25 * 10 ** 5, // 2.5 USDFC per TiB per month + minProvingPeriodInEpochs: 2880, + location: "US", + paymentTokenAddress: IERC20(address(0)) + }); + (string[] memory capabilityKeys, bytes[] memory capabilityValues) = schema.toCapabilities(); vm.prank(provider); providerRegistry.registerProvider{value: 5 ether}( @@ -140,19 +152,6 @@ contract FilecoinWarmStorageServiceOwnerTest is MockFVMTest { name, string.concat(name, " Description"), ServiceProviderRegistryStorage.ProductType.PDP, - abi.encode( - ServiceProviderRegistryStorage.PDPOffering({ - serviceURL: "https://provider.com", - minPieceSizeInBytes: 1024, - maxPieceSizeInBytes: 1024 * 1024, - ipniPiece: false, - ipniIpfs: false, - storagePricePerTibPerMonth: 25 * 10 ** 5, // 2.5 USDFC per TiB per month - minProvingPeriodInEpochs: 2880, - location: "US", - paymentTokenAddress: IERC20(address(0)) - }) - ), capabilityKeys, capabilityValues ); diff --git a/service_contracts/test/ProviderValidation.t.sol b/service_contracts/test/ProviderValidation.t.sol index bf079c64..d38dc6e7 100644 --- a/service_contracts/test/ProviderValidation.t.sol +++ b/service_contracts/test/ProviderValidation.t.sol @@ -11,12 +11,14 @@ import {SessionKeyRegistry} from "@session-key-registry/SessionKeyRegistry.sol"; import {FilecoinWarmStorageService} from "../src/FilecoinWarmStorageService.sol"; import {FilecoinWarmStorageServiceStateView} from "../src/FilecoinWarmStorageServiceStateView.sol"; +import {PDPOffering} from "../src/PDPOffering.sol"; import {ServiceProviderRegistry} from "../src/ServiceProviderRegistry.sol"; import {ServiceProviderRegistryStorage} from "../src/ServiceProviderRegistryStorage.sol"; import {MockERC20, MockPDPVerifier} from "./mocks/SharedMocks.sol"; import {Errors} from "../src/Errors.sol"; contract ProviderValidationTest is MockFVMTest { + using PDPOffering for PDPOffering.Schema; using SafeERC20 for MockERC20; FilecoinWarmStorageService public warmStorage; @@ -109,6 +111,18 @@ contract ProviderValidationTest is MockFVMTest { } function testProviderRegisteredButNotApproved() public { + PDPOffering.Schema memory pdpData = PDPOffering.Schema({ + serviceURL: "https://provider1.com", + minPieceSizeInBytes: 1024, + maxPieceSizeInBytes: 1024 * 1024, + ipniPiece: true, + ipniIpfs: false, + storagePricePerTibPerDay: 1 ether, + minProvingPeriodInEpochs: 2880, + location: "US-West", + paymentTokenAddress: IERC20(address(0)) // Payment in FIL + }); + (string[] memory keys, bytes[] memory values) = pdpData.toCapabilities(); // NOTE: This operation is expected to pass. // Approval is not required to perform onboarding actions. // Register provider1 in serviceProviderRegistry @@ -118,21 +132,8 @@ contract ProviderValidationTest is MockFVMTest { "Provider 1", "Provider 1 Description", ServiceProviderRegistryStorage.ProductType.PDP, - abi.encode( - ServiceProviderRegistryStorage.PDPOffering({ - serviceURL: "https://provider1.com", - minPieceSizeInBytes: 1024, - maxPieceSizeInBytes: 1024 * 1024, - ipniPiece: true, - ipniIpfs: false, - storagePricePerTibPerMonth: 1 ether, - minProvingPeriodInEpochs: 2880, - location: "US-West", - paymentTokenAddress: IERC20(address(0)) // Payment in FIL - }) - ), - new string[](0), - new string[](0) + keys, + values ); // Setup payment approvals for client @@ -166,6 +167,18 @@ contract ProviderValidationTest is MockFVMTest { } function testProviderApprovedCanCreateDataset() public { + PDPOffering.Schema memory pdpData = PDPOffering.Schema({ + serviceURL: "https://provider1.com", + minPieceSizeInBytes: 1024, + maxPieceSizeInBytes: 1024 * 1024, + ipniPiece: true, + ipniIpfs: false, + storagePricePerTibPerDay: 1 ether, + minProvingPeriodInEpochs: 2880, + location: "US-West", + paymentTokenAddress: IERC20(address(0)) // Payment in FIL + }); + (string[] memory keys, bytes[] memory values) = pdpData.toCapabilities(); // Register provider1 in serviceProviderRegistry vm.prank(provider1); serviceProviderRegistry.registerProvider{value: 5 ether}( @@ -173,21 +186,8 @@ contract ProviderValidationTest is MockFVMTest { "Provider 1", "Provider 1 Description", ServiceProviderRegistryStorage.ProductType.PDP, - abi.encode( - ServiceProviderRegistryStorage.PDPOffering({ - serviceURL: "https://provider1.com", - minPieceSizeInBytes: 1024, - maxPieceSizeInBytes: 1024 * 1024, - ipniPiece: true, - ipniIpfs: false, - storagePricePerTibPerMonth: 1 ether, - minProvingPeriodInEpochs: 2880, - location: "US-West", - paymentTokenAddress: IERC20(address(0)) // Payment in FIL - }) - ), - new string[](0), - new string[](0) + keys, + values ); // Approve provider1 diff --git a/service_contracts/test/ServiceProviderRegistry.t.sol b/service_contracts/test/ServiceProviderRegistry.t.sol index 38918afa..713094d2 100644 --- a/service_contracts/test/ServiceProviderRegistry.t.sol +++ b/service_contracts/test/ServiceProviderRegistry.t.sol @@ -5,6 +5,7 @@ import {MockFVMTest} from "@fvm-solidity/mocks/MockFVMTest.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {PDPOffering} from "../src/PDPOffering.sol"; import {ServiceProviderRegistry} from "../src/ServiceProviderRegistry.sol"; import {ServiceProviderRegistryStorage} from "../src/ServiceProviderRegistryStorage.sol"; @@ -15,6 +16,8 @@ contract ServiceProviderRegistryTest is MockFVMTest { address public user1; address public user2; + using PDPOffering for PDPOffering.Schema; + function setUp() public override { super.setUp(); owner = address(this); @@ -55,79 +58,33 @@ contract ServiceProviderRegistryTest is MockFVMTest { assertFalse(registry.isRegisteredProvider(user2), "Should return false for unregistered address"); } - function testRegisterProviderWithEmptyCapabilities() public { - // Give user1 some ETH for registration fee - vm.deal(user1, 10 ether); - - // Prepare PDP data - ServiceProviderRegistryStorage.PDPOffering memory pdpData = ServiceProviderRegistryStorage.PDPOffering({ - serviceURL: "https://example.com", - minPieceSizeInBytes: 1024, - maxPieceSizeInBytes: 1024 * 1024, - ipniPiece: true, - ipniIpfs: false, - storagePricePerTibPerMonth: 500000000000000000, // 0.5 FIL per TiB per month - minProvingPeriodInEpochs: 2880, - location: "US-East", - paymentTokenAddress: IERC20(address(0)) // Payment in FIL - }); - - // Encode PDP data - bytes memory encodedData = abi.encode(pdpData); - - // Empty capability arrays - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); - - vm.prank(user1); - uint256 providerId = registry.registerProvider{value: 5 ether}( - user1, // payee - "Provider One", - "Test provider description", - ServiceProviderRegistryStorage.ProductType.PDP, - encodedData, - emptyKeys, - emptyValues - ); - assertEq(providerId, 1, "Should register with ID 1"); - assertTrue(registry.isRegisteredProvider(user1), "Should be registered"); - - // Verify empty capabilities - (, string[] memory returnedKeys,) = - registry.getProduct(providerId, ServiceProviderRegistryStorage.ProductType.PDP); - assertEq(returnedKeys.length, 0, "Should have no capability keys"); - } - function testRegisterProviderWithCapabilities() public { // Give user1 some ETH for registration fee vm.deal(user1, 10 ether); // Prepare PDP data - ServiceProviderRegistryStorage.PDPOffering memory pdpData = ServiceProviderRegistryStorage.PDPOffering({ + PDPOffering.Schema memory pdpData = PDPOffering.Schema({ serviceURL: "https://example.com", minPieceSizeInBytes: 1024, maxPieceSizeInBytes: 1024 * 1024, ipniPiece: true, ipniIpfs: false, - storagePricePerTibPerMonth: 500000000000000000, // 0.5 FIL per TiB per month + storagePricePerTibPerDay: 500000000000000000, // 0.5 FIL per TiB per month minProvingPeriodInEpochs: 2880, location: "US-East", paymentTokenAddress: IERC20(address(0)) // Payment in FIL }); // Encode PDP data - bytes memory encodedData = abi.encode(pdpData); - // Non-empty capability arrays - string[] memory capabilityKeys = new string[](3); + (string[] memory capabilityKeys, bytes[] memory capabilityValues) = pdpData.toCapabilities(3); capabilityKeys[0] = "region"; capabilityKeys[1] = "tier"; capabilityKeys[2] = "compliance"; - string[] memory capabilityValues = new string[](3); - capabilityValues[0] = "us-east-1"; - capabilityValues[1] = "premium"; - capabilityValues[2] = "SOC2"; + capabilityValues[0] = bytes("us-east-1"); + capabilityValues[1] = bytes("premium"); + capabilityValues[2] = bytes("SOC2"); vm.prank(user1); uint256 providerId = registry.registerProvider{value: 5 ether}( @@ -135,7 +92,6 @@ contract ServiceProviderRegistryTest is MockFVMTest { "Provider One", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedData, capabilityKeys, capabilityValues ); @@ -143,27 +99,27 @@ contract ServiceProviderRegistryTest is MockFVMTest { assertTrue(registry.isRegisteredProvider(user1), "Should be registered"); // Verify capabilities were stored correctly - (, string[] memory returnedKeys,) = + (string[] memory returnedKeys,) = registry.getProduct(providerId, ServiceProviderRegistryStorage.ProductType.PDP); - assertEq(returnedKeys.length, 3, "Should have 3 capability keys"); + assertEq(returnedKeys.length, capabilityKeys.length, "Should have expected capability keys count"); assertEq(returnedKeys[0], "region", "First key should be region"); assertEq(returnedKeys[1], "tier", "Second key should be tier"); assertEq(returnedKeys[2], "compliance", "Third key should be compliance"); // Use the new query methods to verify values - (bool existsRegion, string memory region) = + (bool existsRegion, bytes memory region) = registry.getProductCapability(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "region"); assertTrue(existsRegion, "region capability should exist"); - assertEq(region, "us-east-1", "First value should be us-east-1"); + assertEq(region, bytes("us-east-1"), "First value should be us-east-1"); - (bool existsTier, string memory tier) = + (bool existsTier, bytes memory tier) = registry.getProductCapability(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "tier"); assertTrue(existsTier, "tier capability should exist"); assertEq(tier, "premium", "Second value should be premium"); - (bool existsCompliance, string memory compliance) = + (bool existsCompliance, bytes memory compliance) = registry.getProductCapability(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "compliance"); assertTrue(existsCompliance, "compliance capability should exist"); assertEq(compliance, "SOC2", "Third value should be SOC2"); @@ -174,23 +130,20 @@ contract ServiceProviderRegistryTest is MockFVMTest { vm.deal(user1, 10 ether); // Register a provider with user2 as beneficiary - ServiceProviderRegistryStorage.PDPOffering memory pdpData = ServiceProviderRegistryStorage.PDPOffering({ + PDPOffering.Schema memory pdpData = PDPOffering.Schema({ serviceURL: "https://example.com", minPieceSizeInBytes: 1024, maxPieceSizeInBytes: 1024 * 1024, ipniPiece: true, ipniIpfs: false, - storagePricePerTibPerMonth: 500000000000000000, // 0.5 FIL per TiB per month + storagePricePerTibPerDay: 500000000000000000, // 0.5 FIL per TiB per month minProvingPeriodInEpochs: 2880, location: "US-East", paymentTokenAddress: IERC20(address(0)) // Payment in FIL }); - bytes memory encodedData = abi.encode(pdpData); - // Empty capability arrays - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); + (string[] memory keys, bytes[] memory values) = pdpData.toCapabilities(); // Register with user2 as beneficiary vm.prank(user1); @@ -199,9 +152,8 @@ contract ServiceProviderRegistryTest is MockFVMTest { "Provider One", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedData, - emptyKeys, - emptyValues + keys, + values ); // Verify provider info @@ -216,23 +168,19 @@ contract ServiceProviderRegistryTest is MockFVMTest { // Give user1 some ETH for registration fee vm.deal(user1, 10 ether); - ServiceProviderRegistryStorage.PDPOffering memory pdpData = ServiceProviderRegistryStorage.PDPOffering({ + PDPOffering.Schema memory pdpData = PDPOffering.Schema({ serviceURL: "https://example.com", minPieceSizeInBytes: 1024, maxPieceSizeInBytes: 1024 * 1024, ipniPiece: true, ipniIpfs: false, - storagePricePerTibPerMonth: 500000000000000000, + storagePricePerTibPerDay: 500000000000000000, minProvingPeriodInEpochs: 2880, location: "US-East", paymentTokenAddress: IERC20(address(0)) }); - bytes memory encodedData = abi.encode(pdpData); - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); - - // Try to register with zero beneficiary + (string[] memory keys, bytes[] memory values) = pdpData.toCapabilities(); vm.prank(user1); vm.expectRevert("Payee cannot be zero address"); registry.registerProvider{value: 5 ether}( @@ -240,9 +188,8 @@ contract ServiceProviderRegistryTest is MockFVMTest { "Provider One", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedData, - emptyKeys, - emptyValues + keys, + values ); } @@ -251,23 +198,19 @@ contract ServiceProviderRegistryTest is MockFVMTest { vm.deal(user1, 10 ether); // Register a provider first - ServiceProviderRegistryStorage.PDPOffering memory pdpData = ServiceProviderRegistryStorage.PDPOffering({ + PDPOffering.Schema memory pdpData = PDPOffering.Schema({ serviceURL: "https://example.com", minPieceSizeInBytes: 1024, maxPieceSizeInBytes: 1024 * 1024, ipniPiece: true, ipniIpfs: false, - storagePricePerTibPerMonth: 750000000000000000, // 0.75 FIL per TiB per month + storagePricePerTibPerDay: 750000000000000000, // 0.75 FIL per TiB per month minProvingPeriodInEpochs: 2880, location: "US-East", paymentTokenAddress: IERC20(address(0)) // Payment in FIL }); - bytes memory encodedData = abi.encode(pdpData); - - // Empty capability arrays - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); + (string[] memory keys, bytes[] memory values) = pdpData.toCapabilities(); vm.prank(user1); registry.registerProvider{value: 5 ether}( @@ -275,9 +218,8 @@ contract ServiceProviderRegistryTest is MockFVMTest { "Provider One", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedData, - emptyKeys, - emptyValues + keys, + values ); // Now get provider should work @@ -315,24 +257,20 @@ contract ServiceProviderRegistryTest is MockFVMTest { vm.deal(user1, 10 ether); // Prepare PDP data - ServiceProviderRegistryStorage.PDPOffering memory pdpData = ServiceProviderRegistryStorage.PDPOffering({ + PDPOffering.Schema memory pdpData = PDPOffering.Schema({ serviceURL: "https://example.com", minPieceSizeInBytes: 1024, maxPieceSizeInBytes: 1024 * 1024, ipniPiece: true, ipniIpfs: false, - storagePricePerTibPerMonth: 500000000000000000, // 0.5 FIL per TiB per month + storagePricePerTibPerDay: 500000000000000000, // 0.5 FIL per TiB per month minProvingPeriodInEpochs: 2880, location: "US-East", paymentTokenAddress: IERC20(address(0)) // Payment in FIL }); // Encode PDP data - bytes memory encodedData = abi.encode(pdpData); - - // Empty capability arrays - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); + (string[] memory keys, bytes[] memory values) = pdpData.toCapabilities(); // Register provider with user2 as payee vm.prank(user1); @@ -341,9 +279,8 @@ contract ServiceProviderRegistryTest is MockFVMTest { "Provider One", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedData, - emptyKeys, - emptyValues + keys, + values ); // Verify helper returns the payee address @@ -376,15 +313,12 @@ contract ServiceProviderRegistryTest is MockFVMTest { function testGetProvidersByIdsSingleValidProvider() public { // Register a provider first vm.deal(user1, 10 ether); + + (string[] memory keys, bytes[] memory values) = _createValidPDPOffering().toCapabilities(); + vm.prank(user1); uint256 providerId = registry.registerProvider{value: 5 ether}( - user1, - "Test Provider", - "Test Description", - ServiceProviderRegistryStorage.ProductType.PDP, - _createValidPDPOffering(), - new string[](0), - new string[](0) + user1, "Test Provider", "Test Description", ServiceProviderRegistryStorage.ProductType.PDP, keys, values ); uint256[] memory ids = new uint256[](1); @@ -408,26 +342,15 @@ contract ServiceProviderRegistryTest is MockFVMTest { vm.deal(user1, 10 ether); vm.deal(user2, 10 ether); + (string[] memory keys, bytes[] memory values) = _createValidPDPOffering().toCapabilities(); vm.prank(user1); uint256 providerId1 = registry.registerProvider{value: 5 ether}( - user1, - "Provider 1", - "Description 1", - ServiceProviderRegistryStorage.ProductType.PDP, - _createValidPDPOffering(), - new string[](0), - new string[](0) + user1, "Provider 1", "Description 1", ServiceProviderRegistryStorage.ProductType.PDP, keys, values ); vm.prank(user2); uint256 providerId2 = registry.registerProvider{value: 5 ether}( - user2, - "Provider 2", - "Description 2", - ServiceProviderRegistryStorage.ProductType.PDP, - _createValidPDPOffering(), - new string[](0), - new string[](0) + user2, "Provider 2", "Description 2", ServiceProviderRegistryStorage.ProductType.PDP, keys, values ); uint256[] memory ids = new uint256[](2); @@ -481,15 +404,10 @@ contract ServiceProviderRegistryTest is MockFVMTest { function testGetProvidersByIdsMixedValidAndInvalid() public { // Register one provider vm.deal(user1, 10 ether); + (string[] memory keys, bytes[] memory values) = _createValidPDPOffering().toCapabilities(); vm.prank(user1); uint256 validProviderId = registry.registerProvider{value: 5 ether}( - user1, - "Valid Provider", - "Valid Description", - ServiceProviderRegistryStorage.ProductType.PDP, - _createValidPDPOffering(), - new string[](0), - new string[](0) + user1, "Valid Provider", "Valid Description", ServiceProviderRegistryStorage.ProductType.PDP, keys, values ); uint256[] memory ids = new uint256[](4); @@ -522,15 +440,10 @@ contract ServiceProviderRegistryTest is MockFVMTest { function testGetProvidersByIdsInactiveProvider() public { // Register a provider vm.deal(user1, 10 ether); + (string[] memory keys, bytes[] memory values) = _createValidPDPOffering().toCapabilities(); vm.prank(user1); uint256 providerId = registry.registerProvider{value: 5 ether}( - user1, - "Test Provider", - "Test Description", - ServiceProviderRegistryStorage.ProductType.PDP, - _createValidPDPOffering(), - new string[](0), - new string[](0) + user1, "Test Provider", "Test Description", ServiceProviderRegistryStorage.ProductType.PDP, keys, values ); // Remove the provider (make it inactive) @@ -552,18 +465,18 @@ contract ServiceProviderRegistryTest is MockFVMTest { } // Helper function to create a valid PDP offering for tests - function _createValidPDPOffering() internal pure returns (bytes memory) { - ServiceProviderRegistryStorage.PDPOffering memory pdpOffering = ServiceProviderRegistryStorage.PDPOffering({ + function _createValidPDPOffering() internal pure returns (PDPOffering.Schema memory schema) { + PDPOffering.Schema memory pdpOffering = PDPOffering.Schema({ serviceURL: "https://example.com/api", minPieceSizeInBytes: 1024, maxPieceSizeInBytes: 1024 * 1024, ipniPiece: true, ipniIpfs: true, - storagePricePerTibPerMonth: 1000, + storagePricePerTibPerDay: 1000, minProvingPeriodInEpochs: 1, location: "US", paymentTokenAddress: IERC20(address(0)) }); - return abi.encode(pdpOffering); + return pdpOffering; } } diff --git a/service_contracts/test/ServiceProviderRegistryFull.t.sol b/service_contracts/test/ServiceProviderRegistryFull.t.sol index 54acbb79..45061427 100644 --- a/service_contracts/test/ServiceProviderRegistryFull.t.sol +++ b/service_contracts/test/ServiceProviderRegistryFull.t.sol @@ -3,13 +3,17 @@ pragma solidity ^0.8.20; import {BURN_ADDRESS} from "@fvm-solidity/FVMActors.sol"; import {MockFVMTest} from "@fvm-solidity/mocks/MockFVMTest.sol"; +import {PDPOffering} from "../src/PDPOffering.sol"; import {ServiceProviderRegistry} from "../src/ServiceProviderRegistry.sol"; import {ServiceProviderRegistryStorage} from "../src/ServiceProviderRegistryStorage.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract ServiceProviderRegistryFullTest is MockFVMTest { - ServiceProviderRegistry public implementation; + using PDPOffering for PDPOffering.Schema; + using PDPOffering for ServiceProviderRegistry; + + ServiceProviderRegistry private implementation; ServiceProviderRegistry public registry; address public owner; @@ -19,14 +23,13 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { address public user; string constant SERVICE_URL = "https://provider1.example.com"; - string constant SERVICE_URL_2 = "https://provider2.example.com"; + bytes constant SERVICE_URL_2 = "https://provider2.example.com"; string constant UPDATED_SERVICE_URL = "https://provider1-updated.example.com"; uint256 constant REGISTRATION_FEE = 5 ether; // 5 FIL in attoFIL - ServiceProviderRegistryStorage.PDPOffering public defaultPDPData; - ServiceProviderRegistryStorage.PDPOffering public updatedPDPData; - bytes public encodedDefaultPDPData; + PDPOffering.Schema public defaultPDPData; + PDPOffering.Schema public updatedPDPData; bytes public encodedUpdatedPDPData; function setUp() public override { @@ -54,34 +57,29 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { registry = ServiceProviderRegistry(address(proxy)); // Setup default PDP data - defaultPDPData = ServiceProviderRegistryStorage.PDPOffering({ + defaultPDPData = PDPOffering.Schema({ serviceURL: SERVICE_URL, minPieceSizeInBytes: 1024, maxPieceSizeInBytes: 1024 * 1024, ipniPiece: true, ipniIpfs: false, - storagePricePerTibPerMonth: 1000000000000000000, // 1 FIL per TiB per month + storagePricePerTibPerDay: 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 }); - updatedPDPData = ServiceProviderRegistryStorage.PDPOffering({ + updatedPDPData = PDPOffering.Schema({ serviceURL: UPDATED_SERVICE_URL, minPieceSizeInBytes: 512, maxPieceSizeInBytes: 2 * 1024 * 1024, ipniPiece: true, ipniIpfs: true, - storagePricePerTibPerMonth: 2000000000000000000, // 2 FIL per TiB per month + storagePricePerTibPerDay: 2000000000000000000, // 2 FIL per TiB per month minProvingPeriodInEpochs: 1440, // 12 hours in epochs location: "Europe", paymentTokenAddress: IERC20(address(0)) // Payment in FIL }); - - // Encode PDP data - encodedDefaultPDPData = abi.encode(defaultPDPData); - - encodedUpdatedPDPData = abi.encode(updatedPDPData); } // ========== Initial State Tests ========== @@ -113,21 +111,20 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { emit ServiceProviderRegistry.ProviderRegistered(1, provider1, provider1); // Non-empty capability arrays - string[] memory capKeys = new string[](4); + (string[] memory capKeys, bytes[] memory capValues) = defaultPDPData.toCapabilities(4); capKeys[0] = "datacenter"; capKeys[1] = "redundancy"; capKeys[2] = "latency"; capKeys[3] = "cert"; - string[] memory capValues = new string[](4); - capValues[0] = "EU-WEST"; - capValues[1] = "3x"; - capValues[2] = "low"; - capValues[3] = "ISO27001"; + capValues[0] = bytes("EU-WEST"); + capValues[1] = bytes("3x"); + capValues[2] = bytes("low"); + capValues[3] = bytes("ISO27001"); vm.expectEmit(true, true, false, true); emit ServiceProviderRegistry.ProductAdded( - 1, ServiceProviderRegistryStorage.ProductType.PDP, provider1, encodedDefaultPDPData, capKeys, capValues + 1, ServiceProviderRegistryStorage.ProductType.PDP, provider1, capKeys, capValues ); // Register provider @@ -136,7 +133,6 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, capKeys, capValues ); @@ -162,15 +158,14 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { assertTrue(info.info.isActive, "Provider should be active"); // Verify PDP service using getPDPService (including capabilities) - (ServiceProviderRegistryStorage.PDPOffering memory pdpData, string[] memory keys, bool isActive) = - registry.getPDPService(1); + (PDPOffering.Schema memory pdpData, string[] memory keys, bool isActive) = registry.getPDPService(1); assertEq(pdpData.serviceURL, SERVICE_URL, "Service URL should match"); assertEq(pdpData.minPieceSizeInBytes, defaultPDPData.minPieceSizeInBytes, "Min piece size should match"); assertEq(pdpData.maxPieceSizeInBytes, defaultPDPData.maxPieceSizeInBytes, "Max piece size should match"); assertEq(pdpData.ipniPiece, defaultPDPData.ipniPiece, "IPNI piece should match"); assertEq(pdpData.ipniIpfs, defaultPDPData.ipniIpfs, "IPNI IPFS should match"); assertEq( - pdpData.storagePricePerTibPerMonth, defaultPDPData.storagePricePerTibPerMonth, "Storage price should match" + pdpData.storagePricePerTibPerDay, defaultPDPData.storagePricePerTibPerDay, "Storage price should match" ); assertEq( pdpData.minProvingPeriodInEpochs, defaultPDPData.minProvingPeriodInEpochs, "Min proving period should match" @@ -179,7 +174,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { assertTrue(isActive, "PDP service should be active"); // Verify capabilities - assertEq(keys.length, 4, "Should have 4 capability keys"); + assertEq(keys.length, capKeys.length, "Should have 4 capability keys"); assertEq(keys[0], "datacenter", "First key should be datacenter"); assertEq(keys[1], "redundancy", "Second key should be redundancy"); assertEq(keys[2], "latency", "Third key should be latency"); @@ -192,7 +187,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { queryKeys[2] = "latency"; queryKeys[3] = "cert"; - (bool[] memory exists, string[] memory values) = + (bool[] memory exists, bytes[] memory values) = registry.getProductCapabilities(1, ServiceProviderRegistryStorage.ProductType.PDP, queryKeys); assertTrue(exists[0], "First key should exist"); assertEq(values[0], "EU-WEST", "First value should be EU-WEST"); @@ -204,16 +199,16 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { assertEq(values[3], "ISO27001", "Fourth value should be ISO27001"); // Also verify using getProduct - (bytes memory productData, string[] memory productKeys, bool productActive) = + (string[] memory productKeys, bool productActive) = registry.getProduct(providerId, ServiceProviderRegistryStorage.ProductType.PDP); assertTrue(productActive, "Product should be active"); - assertEq(productKeys.length, 4, "Product should have 4 capability keys"); + assertEq(productKeys.length, capKeys.length, "Product should have 4 capability keys"); assertEq(productKeys[0], "datacenter", "Product first key should be datacenter"); // Verify value using direct mapping access - string memory datacenterValue = + bytes memory datacenterValue = registry.productCapabilities(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "datacenter"); - assertEq(datacenterValue, "EU-WEST", "Product first value should be EU-WEST"); + assertEq(datacenterValue, bytes("EU-WEST"), "Product first value should be EU-WEST"); // Verify fee was burned uint256 burnActorBalanceAfter = BURN_ADDRESS.balance; @@ -221,9 +216,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { } function testCannotRegisterTwice() public { - // Empty capability arrays - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); // First registration vm.prank(provider1); @@ -232,9 +225,8 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - emptyKeys, - emptyValues + keys, + values ); // Try to register again @@ -245,19 +237,17 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - emptyKeys, - emptyValues + keys, + values ); } function testRegisterMultipleProviders() public { // Provider 1 capabilities - string[] memory capKeys1 = new string[](2); + (string[] memory capKeys1, bytes[] memory capValues1) = defaultPDPData.toCapabilities(2); capKeys1[0] = "region"; capKeys1[1] = "performance"; - string[] memory capValues1 = new string[](2); capValues1[0] = "US-EAST"; capValues1[1] = "high"; @@ -268,26 +258,22 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Provider 1 description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, capKeys1, capValues1 ); // Provider 2 capabilities - string[] memory capKeys2 = new string[](3); + (string[] memory capKeys2, bytes[] memory capValues2) = defaultPDPData.toCapabilities(3); capKeys2[0] = "region"; capKeys2[1] = "storage"; capKeys2[2] = "availability"; - string[] memory capValues2 = new string[](3); - capValues2[0] = "ASIA-PAC"; - capValues2[1] = "100TB"; - capValues2[2] = "99.999%"; + capValues2[0] = bytes("ASIA-PAC"); + capValues2[1] = bytes("100TB"); + capValues2[2] = bytes("99.999%"); + capValues2[3] = SERVICE_URL_2; // Register provider 2 - ServiceProviderRegistryStorage.PDPOffering memory pdpData2 = defaultPDPData; - pdpData2.serviceURL = SERVICE_URL_2; - bytes memory encodedPDPData2 = abi.encode(pdpData2); vm.prank(provider2); uint256 id2 = registry.registerProvider{value: REGISTRATION_FEE}( @@ -295,7 +281,6 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Provider 2 description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedPDPData2, capKeys2, capValues2 ); @@ -313,12 +298,12 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { // Verify provider 1 capabilities (, string[] memory keys1,) = registry.getPDPService(1); - assertEq(keys1.length, 2, "Provider 1 should have 2 capability keys"); + assertEq(keys1.length, capKeys1.length, "Provider 1 should have as many capability keys as provided"); assertEq(keys1[0], "region", "Provider 1 first key should be region"); assertEq(keys1[1], "performance", "Provider 1 second key should be performance"); // Query values for provider 1 - (bool[] memory exists1, string[] memory values1) = + (bool[] memory exists1, bytes[] memory values1) = registry.getProductCapabilities(1, ServiceProviderRegistryStorage.ProductType.PDP, keys1); assertTrue(exists1[0] && exists1[1], "All keys should exist for provider 1"); assertEq(values1[0], "US-EAST", "Provider 1 first value should be US-EAST"); @@ -326,13 +311,13 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { // Verify provider 2 capabilities (, string[] memory keys2,) = registry.getPDPService(2); - assertEq(keys2.length, 3, "Provider 2 should have 3 capability keys"); + assertEq(keys2.length, capKeys2.length, "Provider 2 should have as many capability keys as provided"); assertEq(keys2[0], "region", "Provider 2 first key should be region"); assertEq(keys2[1], "storage", "Provider 2 second key should be storage"); assertEq(keys2[2], "availability", "Provider 2 third key should be availability"); // Query values for provider 2 - (bool[] memory exists2, string[] memory values2) = + (bool[] memory exists2, bytes[] memory values2) = registry.getProductCapabilities(2, ServiceProviderRegistryStorage.ProductType.PDP, keys2); assertTrue(exists2[0] && exists2[1], "All keys should exist for provider 2"); assertEq(values2[0], "ASIA-PAC", "Provider 2 first value should be ASIA-PAC"); @@ -341,9 +326,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { } function testRegisterWithInsufficientFee() public { - // Empty capability arrays - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); // Try to register with less than 5 FIL vm.prank(provider1); @@ -353,9 +336,8 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - emptyKeys, - emptyValues + keys, + values ); // Try with 0 fee @@ -366,16 +348,13 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - emptyKeys, - emptyValues + keys, + values ); } function testRegisterWithExcessFee() public { - // Empty capability arrays - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); // Try to register with 2 FIL (less than 5 FIL) - should fail vm.prank(provider1); @@ -385,9 +364,8 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - emptyKeys, - emptyValues + keys, + values ); // Verify provider was not registered @@ -397,30 +375,10 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { } function testRegisterWithInvalidData() public { - // Test empty service URL - ServiceProviderRegistryStorage.PDPOffering memory invalidPDP = defaultPDPData; - invalidPDP.serviceURL = ""; - bytes memory encodedInvalidPDP = abi.encode(invalidPDP); - // Empty capability arrays - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); - - vm.prank(provider1); - vm.expectRevert("Service URL cannot be empty"); - registry.registerProvider{value: REGISTRATION_FEE}( - provider1, // payee - "", - "Test provider description", - ServiceProviderRegistryStorage.ProductType.PDP, - encodedInvalidPDP, - emptyKeys, - emptyValues - ); + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); // Test service URL too long - string memory longURL = new string(257); - invalidPDP.serviceURL = longURL; - encodedInvalidPDP = abi.encode(invalidPDP); + values[0] = new bytes(129); vm.prank(provider1); vm.expectRevert("Service URL too long"); registry.registerProvider{value: REGISTRATION_FEE}( @@ -428,83 +386,15 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedInvalidPDP, - emptyKeys, - emptyValues - ); - - // Test invalid PDP data - min piece size 0 - invalidPDP = defaultPDPData; - invalidPDP.minPieceSizeInBytes = 0; - encodedInvalidPDP = abi.encode(invalidPDP); - vm.prank(provider1); - vm.expectRevert("Min piece size must be greater than 0"); - registry.registerProvider{value: REGISTRATION_FEE}( - provider1, // payee - "", - "Test provider description", - ServiceProviderRegistryStorage.ProductType.PDP, - encodedInvalidPDP, - emptyKeys, - emptyValues - ); - - // Test invalid PDP data - max < min - invalidPDP.minPieceSizeInBytes = 1024; - invalidPDP.maxPieceSizeInBytes = 512; - encodedInvalidPDP = abi.encode(invalidPDP); - vm.prank(provider1); - vm.expectRevert("Max piece size must be >= min piece size"); - registry.registerProvider{value: REGISTRATION_FEE}( - provider1, // payee - "", - "Test provider description", - ServiceProviderRegistryStorage.ProductType.PDP, - encodedInvalidPDP, - emptyKeys, - emptyValues - ); - - // Test invalid PDP data - min proving period 0 - invalidPDP = defaultPDPData; - invalidPDP.minProvingPeriodInEpochs = 0; - encodedInvalidPDP = abi.encode(invalidPDP); - vm.prank(provider1); - vm.expectRevert("Min proving period must be greater than 0"); - registry.registerProvider{value: REGISTRATION_FEE}( - provider1, // payee - "", - "Test provider description", - ServiceProviderRegistryStorage.ProductType.PDP, - encodedInvalidPDP, - emptyKeys, - emptyValues - ); - - // Test invalid PDP data - empty location - invalidPDP = defaultPDPData; - invalidPDP.location = ""; - encodedInvalidPDP = abi.encode(invalidPDP); - vm.prank(provider1); - vm.expectRevert("Location cannot be empty"); - registry.registerProvider{value: REGISTRATION_FEE}( - provider1, // payee - "", - "Test provider description", - ServiceProviderRegistryStorage.ProductType.PDP, - encodedInvalidPDP, - emptyKeys, - emptyValues + keys, + values ); // Test invalid PDP data - location too long - invalidPDP = defaultPDPData; - bytes memory longLocation = new bytes(129); - for (uint256 i = 0; i < 129; i++) { - longLocation[i] = "a"; + values[7] = new bytes(129); + for (uint256 i = 0; i < values.length; i++) { + values[7][i] = "a"; } - invalidPDP.location = string(longLocation); - encodedInvalidPDP = abi.encode(invalidPDP); vm.prank(provider1); vm.expectRevert("Location too long"); registry.registerProvider{value: REGISTRATION_FEE}( @@ -512,18 +402,15 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedInvalidPDP, - emptyKeys, - emptyValues + keys, + values ); } // ========== Update Tests ========== function testUpdateProduct() public { - // Empty capability arrays - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); // Register provider vm.prank(provider1); @@ -532,9 +419,8 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - emptyKeys, - emptyValues + keys, + values ); // Update PDP service using new updateProduct function @@ -542,27 +428,22 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { vm.expectEmit(true, true, false, true); emit ServiceProviderRegistry.ProductUpdated( - 1, ServiceProviderRegistryStorage.ProductType.PDP, provider1, encodedUpdatedPDPData, emptyKeys, emptyValues + 1, ServiceProviderRegistryStorage.ProductType.PDP, provider1, keys, values ); - registry.updateProduct( - ServiceProviderRegistryStorage.ProductType.PDP, encodedUpdatedPDPData, emptyKeys, emptyValues - ); + registry.updateProduct(ServiceProviderRegistryStorage.ProductType.PDP, keys, values); vm.stopPrank(); // Verify update - (ServiceProviderRegistryStorage.PDPOffering memory pdpData, string[] memory keys, bool isActive) = - registry.getPDPService(1); + (PDPOffering.Schema memory pdpData,, bool isActive) = registry.getPDPService(1); assertEq(pdpData.serviceURL, UPDATED_SERVICE_URL, "Service URL should be updated"); assertEq(pdpData.minPieceSizeInBytes, updatedPDPData.minPieceSizeInBytes, "Min piece size should be updated"); assertEq(pdpData.maxPieceSizeInBytes, updatedPDPData.maxPieceSizeInBytes, "Max piece size should be updated"); assertEq(pdpData.ipniPiece, updatedPDPData.ipniPiece, "IPNI piece should be updated"); assertEq(pdpData.ipniIpfs, updatedPDPData.ipniIpfs, "IPNI IPFS should be updated"); assertEq( - pdpData.storagePricePerTibPerMonth, - updatedPDPData.storagePricePerTibPerMonth, - "Storage price should be updated" + pdpData.storagePricePerTibPerDay, updatedPDPData.storagePricePerTibPerDay, "Storage price should be updated" ); assertEq( pdpData.minProvingPeriodInEpochs, @@ -574,9 +455,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { } function testOnlyOwnerCanUpdate() public { - // Empty capability arrays - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); // Register provider vm.prank(provider1); @@ -585,23 +464,18 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - emptyKeys, - emptyValues + keys, + values ); // Try to update as non-owner vm.prank(provider2); vm.expectRevert("Provider not registered"); - registry.updateProduct( - ServiceProviderRegistryStorage.ProductType.PDP, encodedUpdatedPDPData, emptyKeys, emptyValues - ); + registry.updateProduct(ServiceProviderRegistryStorage.ProductType.PDP, keys, values); } function testCannotUpdateRemovedProvider() public { - // Empty capability arrays - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); // Register and remove provider vm.prank(provider1); @@ -610,9 +484,8 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - emptyKeys, - emptyValues + keys, + values ); vm.prank(provider1); @@ -621,9 +494,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { // Try to update vm.prank(provider1); vm.expectRevert("Provider not registered"); - registry.updateProduct( - ServiceProviderRegistryStorage.ProductType.PDP, encodedUpdatedPDPData, emptyKeys, emptyValues - ); + registry.updateProduct(ServiceProviderRegistryStorage.ProductType.PDP, keys, values); } // ========== Ownership Tests (Transfer functionality removed) ========== @@ -633,9 +504,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { // ========== Removal Tests ========== function testRemoveProvider() public { - // Empty capability arrays - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); // Register provider vm.prank(provider1); @@ -644,9 +513,8 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - emptyKeys, - emptyValues + keys, + values ); // Remove provider @@ -682,9 +550,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { } function testCannotRemoveAlreadyRemoved() public { - // Empty capability arrays - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); vm.prank(provider1); registry.registerProvider{value: REGISTRATION_FEE}( @@ -692,9 +558,8 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - emptyKeys, - emptyValues + keys, + values ); vm.prank(provider1); @@ -706,9 +571,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { } function testOnlyOwnerCanRemove() public { - // Empty capability arrays - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); vm.prank(provider1); registry.registerProvider{value: REGISTRATION_FEE}( @@ -716,9 +579,8 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - emptyKeys, - emptyValues + keys, + values ); vm.prank(provider2); @@ -727,9 +589,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { } function testCanReregisterAfterRemoval() public { - // Empty capability arrays - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); // Register, remove, then register again vm.prank(provider1); @@ -738,23 +598,22 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Provider 1 description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - emptyKeys, - emptyValues + keys, + values ); vm.prank(provider1); registry.removeProvider(); + (string[] memory updatedKeys, bytes[] memory updatedValues) = updatedPDPData.toCapabilities(); vm.prank(provider1); uint256 id2 = registry.registerProvider{value: REGISTRATION_FEE}( provider1, // payee "", "Provider 2 description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedUpdatedPDPData, - emptyKeys, - emptyValues + updatedKeys, + updatedValues ); // Should get new ID @@ -767,9 +626,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { // ========== Multi-Product Tests ========== function testGetProvidersByProductType() public { - // Empty capability arrays - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); // Register 3 providers with PDP vm.prank(provider1); @@ -778,37 +635,30 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - emptyKeys, - emptyValues + keys, + values ); - ServiceProviderRegistryStorage.PDPOffering memory pdpData2 = defaultPDPData; - pdpData2.serviceURL = SERVICE_URL_2; - bytes memory encodedPDPData2 = abi.encode(pdpData2); + values[0] = SERVICE_URL_2; vm.prank(provider2); registry.registerProvider{value: REGISTRATION_FEE}( provider2, // payee "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedPDPData2, - emptyKeys, - emptyValues + keys, + values ); - ServiceProviderRegistryStorage.PDPOffering memory pdpData3 = defaultPDPData; - pdpData3.serviceURL = "https://provider3.example.com"; - bytes memory encodedPDPData3 = abi.encode(pdpData3); + values[0] = "https://provider3.example.com"; vm.prank(provider3); registry.registerProvider{value: REGISTRATION_FEE}( provider3, // payee "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedPDPData3, - emptyKeys, - emptyValues + keys, + values ); // Get providers by product type with pagination @@ -822,9 +672,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { } function testGetActiveProvidersByProductType() public { - // Empty capability arrays - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); // Register 3 providers with PDP vm.prank(provider1); @@ -833,37 +681,30 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - emptyKeys, - emptyValues + keys, + values ); - ServiceProviderRegistryStorage.PDPOffering memory pdpData2 = defaultPDPData; - pdpData2.serviceURL = SERVICE_URL_2; - bytes memory encodedPDPData2 = abi.encode(pdpData2); + values[0] = SERVICE_URL_2; vm.prank(provider2); registry.registerProvider{value: REGISTRATION_FEE}( provider2, // payee "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedPDPData2, - emptyKeys, - emptyValues + keys, + values ); - ServiceProviderRegistryStorage.PDPOffering memory pdpData3 = defaultPDPData; - pdpData3.serviceURL = "https://provider3.example.com"; - bytes memory encodedPDPData3 = abi.encode(pdpData3); + values[0] = "https://provider3.example.com"; vm.prank(provider3); registry.registerProvider{value: REGISTRATION_FEE}( provider3, // payee "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedPDPData3, - emptyKeys, - emptyValues + keys, + values ); // Remove provider 2 @@ -880,9 +721,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { } function testProviderHasProduct() public { - // Empty capability arrays - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); vm.prank(provider1); registry.registerProvider{value: REGISTRATION_FEE}( @@ -890,9 +729,8 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - emptyKeys, - emptyValues + keys, + values ); assertTrue( @@ -902,9 +740,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { } function testGetProduct() public { - // Empty capability arrays - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); vm.prank(provider1); registry.registerProvider{value: REGISTRATION_FEE}( @@ -912,26 +748,23 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - emptyKeys, - emptyValues + keys, + values ); - (bytes memory productData, string[] memory keys, bool isActive) = + (string[] memory getProductKeys, bool isActive) = registry.getProduct(1, ServiceProviderRegistryStorage.ProductType.PDP); - assertTrue(productData.length > 0, "Product data should exist"); assertTrue(isActive, "Product should be active"); + (, bytes[] memory getProductCapabilities) = + registry.getProductCapabilities(1, ServiceProviderRegistryStorage.ProductType.PDP, getProductKeys); // Decode and verify - ServiceProviderRegistryStorage.PDPOffering memory decoded = - abi.decode(productData, (ServiceProviderRegistryStorage.PDPOffering)); + PDPOffering.Schema memory decoded = PDPOffering.fromCapabilities(getProductKeys, getProductCapabilities); assertEq(decoded.serviceURL, SERVICE_URL, "Service URL should match"); } function testCannotAddProductTwice() public { - // Empty capability arrays - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); vm.prank(provider1); registry.registerProvider{value: REGISTRATION_FEE}( @@ -939,23 +772,19 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - emptyKeys, - emptyValues + keys, + values ); + (string[] memory updatedKeys, bytes[] memory updatedValues) = updatedPDPData.toCapabilities(); // Try to add PDP again vm.prank(provider1); vm.expectRevert("Product already exists for this provider"); - registry.addProduct( - ServiceProviderRegistryStorage.ProductType.PDP, encodedUpdatedPDPData, emptyKeys, emptyValues - ); + registry.addProduct(ServiceProviderRegistryStorage.ProductType.PDP, updatedKeys, updatedValues); } function testCanRemoveLastProduct() public { - // Empty capability arrays - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); vm.prank(provider1); uint256 providerId = registry.registerProvider{value: REGISTRATION_FEE}( @@ -963,9 +792,8 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "serviceURL", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - emptyKeys, - emptyValues + keys, + values ); // Verify product exists before removal @@ -980,10 +808,10 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { // Verify product is removed assertFalse(registry.providerHasProduct(providerId, ServiceProviderRegistryStorage.ProductType.PDP)); - (ServiceProviderRegistryStorage.PDPOffering memory pdpData, string[] memory keys, bool isActive) = + (PDPOffering.Schema memory pdpData, string[] memory keysAfter, bool isActive) = registry.getPDPService(providerId); assertFalse(isActive); - assertEq(keys.length, 0); + assertEq(keysAfter.length, 0); assertEq(bytes(pdpData.serviceURL).length, 0); assertEq(bytes(pdpData.location).length, 0); assertEq(pdpData.minPieceSizeInBytes, 0); @@ -992,16 +820,14 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { assertFalse(pdpData.ipniPiece); assertFalse(pdpData.ipniIpfs); assertEq(pdpData.minProvingPeriodInEpochs, 0); - assertEq(pdpData.storagePricePerTibPerMonth, 0); + assertEq(pdpData.storagePricePerTibPerDay, 0); assertEq(address(pdpData.paymentTokenAddress), address(0)); } // ========== Getter Tests ========== function testGetAllActiveProviders() public { - // Empty capability arrays - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); // Register 3 providers vm.prank(provider1); @@ -1010,37 +836,29 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - emptyKeys, - emptyValues + keys, + values ); - ServiceProviderRegistryStorage.PDPOffering memory pdpData2 = defaultPDPData; - pdpData2.serviceURL = SERVICE_URL_2; - bytes memory encodedPDPData2 = abi.encode(pdpData2); - vm.prank(provider2); + values[0] = SERVICE_URL_2; registry.registerProvider{value: REGISTRATION_FEE}( provider2, // payee "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedPDPData2, - emptyKeys, - emptyValues + keys, + values ); - ServiceProviderRegistryStorage.PDPOffering memory pdpData3 = defaultPDPData; - pdpData3.serviceURL = "https://provider3.example.com"; - bytes memory encodedPDPData3 = abi.encode(pdpData3); + values[0] = "https://provider3.example.com"; vm.prank(provider3); registry.registerProvider{value: REGISTRATION_FEE}( provider3, // payee "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedPDPData3, - emptyKeys, - emptyValues + keys, + values ); // Remove provider 2 @@ -1057,9 +875,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { function testGetProviderCount() public { assertEq(registry.getProviderCount(), 0, "Initial count should be 0"); - // Empty capability arrays - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); vm.prank(provider1); registry.registerProvider{value: REGISTRATION_FEE}( @@ -1067,24 +883,20 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - emptyKeys, - emptyValues + keys, + values ); assertEq(registry.getProviderCount(), 1, "Count should be 1"); - ServiceProviderRegistryStorage.PDPOffering memory pdpData2 = defaultPDPData; - pdpData2.serviceURL = SERVICE_URL_2; - bytes memory encodedPDPData2 = abi.encode(pdpData2); + values[0] = SERVICE_URL_2; vm.prank(provider2); registry.registerProvider{value: REGISTRATION_FEE}( provider2, // payee "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedPDPData2, - emptyKeys, - emptyValues + keys, + values ); assertEq(registry.getProviderCount(), 2, "Count should be 2"); @@ -1108,9 +920,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { // ========== Edge Cases ========== function testMultipleUpdatesInSameBlock() public { - // Empty capability arrays - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); vm.prank(provider1); registry.registerProvider{value: REGISTRATION_FEE}( @@ -1118,35 +928,31 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - emptyKeys, - emptyValues + keys, + values ); vm.startPrank(provider1); + (string[] memory updatedKeys, bytes[] memory updatedValues) = updatedPDPData.toCapabilities(); // Expect the update event with timestamp vm.expectEmit(true, true, true, true); emit ServiceProviderRegistry.ProductUpdated( - 1, ServiceProviderRegistryStorage.ProductType.PDP, provider1, encodedUpdatedPDPData, emptyKeys, emptyValues + 1, ServiceProviderRegistryStorage.ProductType.PDP, provider1, updatedKeys, updatedValues ); - registry.updateProduct( - ServiceProviderRegistryStorage.ProductType.PDP, encodedUpdatedPDPData, emptyKeys, emptyValues - ); + registry.updateProduct(ServiceProviderRegistryStorage.ProductType.PDP, keys, values); vm.stopPrank(); // Verify the product was updated (check the actual data) - (ServiceProviderRegistryStorage.PDPOffering memory pdpData,,) = registry.getPDPService(1); + (PDPOffering.Schema memory pdpData,,) = registry.getPDPService(1); assertEq(pdpData.serviceURL, UPDATED_SERVICE_URL, "Service URL should be updated"); } // ========== Provider Info Update Tests ========== function testUpdateProviderDescription() public { - // Empty capability arrays - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); // Register provider vm.prank(provider1); @@ -1155,9 +961,8 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Initial description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - emptyKeys, - emptyValues + keys, + values ); // Verify initial description @@ -1178,9 +983,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { } function testCannotUpdateProviderDescriptionIfNotOwner() public { - // Empty capability arrays - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); // Register provider vm.prank(provider1); @@ -1189,9 +992,8 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Initial description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - emptyKeys, - emptyValues + keys, + values ); // Try to update as non-owner @@ -1201,9 +1003,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { } function testCannotUpdateProviderDescriptionTooLong() public { - // Empty capability arrays - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); // Register provider vm.prank(provider1); @@ -1212,9 +1012,8 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Initial description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - emptyKeys, - emptyValues + keys, + values ); // Try to update with description that's too long @@ -1233,9 +1032,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { longName[i] = "a"; } - // Empty capability arrays - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); vm.prank(provider1); vm.expectRevert("Name too long"); @@ -1244,16 +1041,13 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { string(longName), "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - emptyKeys, - emptyValues + keys, + values ); } function testNameTooLongOnUpdate() public { - // Register provider first - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); vm.prank(provider1); registry.registerProvider{value: REGISTRATION_FEE}( @@ -1261,9 +1055,8 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "Initial Name", "Initial description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - emptyKeys, - emptyValues + keys, + values ); // Create a name that's too long (129 chars, max is 128) @@ -1280,9 +1073,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { // ========== Event Timestamp Tests ========== function testEventTimestampsEmittedCorrectly() public { - // Empty capability arrays - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); // Test ProviderRegistered and ProductAdded events vm.prank(provider1); @@ -1290,7 +1081,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { emit ServiceProviderRegistry.ProviderRegistered(1, provider1, provider1); vm.expectEmit(true, true, true, true); emit ServiceProviderRegistry.ProductAdded( - 1, ServiceProviderRegistryStorage.ProductType.PDP, provider1, encodedDefaultPDPData, emptyKeys, emptyValues + 1, ServiceProviderRegistryStorage.ProductType.PDP, provider1, keys, values ); registry.registerProvider{value: REGISTRATION_FEE}( @@ -1298,20 +1089,18 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - emptyKeys, - emptyValues + keys, + values ); + (string[] memory updatedKeys, bytes[] memory updatedValues) = updatedPDPData.toCapabilities(); // Test ProductUpdated event vm.prank(provider1); vm.expectEmit(true, true, true, true); emit ServiceProviderRegistry.ProductUpdated( - 1, ServiceProviderRegistryStorage.ProductType.PDP, provider1, encodedUpdatedPDPData, emptyKeys, emptyValues - ); - registry.updateProduct( - ServiceProviderRegistryStorage.ProductType.PDP, encodedUpdatedPDPData, emptyKeys, emptyValues + 1, ServiceProviderRegistryStorage.ProductType.PDP, provider1, updatedKeys, updatedValues ); + registry.updateProduct(ServiceProviderRegistryStorage.ProductType.PDP, updatedKeys, updatedValues); // Test ProviderRemoved event vm.prank(provider1); @@ -1324,12 +1113,11 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { function testRegisterWithCapabilities() public { // Create capability arrays - string[] memory capKeys = new string[](3); + (string[] memory capKeys, bytes[] memory capValues) = updatedPDPData.toCapabilities(3); capKeys[0] = "region"; capKeys[1] = "bandwidth"; capKeys[2] = "encryption"; - string[] memory capValues = new string[](3); capValues[0] = "us-west-2"; capValues[1] = "10Gbps"; capValues[2] = "AES256"; @@ -1340,22 +1128,21 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, capKeys, capValues ); // Get the product and verify capabilities - (bytes memory productData, string[] memory returnedKeys, bool isActive) = + (string[] memory returnedKeys, bool isActive) = registry.getProduct(providerId, ServiceProviderRegistryStorage.ProductType.PDP); - assertEq(returnedKeys.length, 3, "Should have 3 capability keys"); + assertEq(returnedKeys.length, capKeys.length, "Should have same number of capability keys"); assertEq(returnedKeys[0], "region", "First key should be region"); assertEq(returnedKeys[1], "bandwidth", "Second key should be bandwidth"); assertEq(returnedKeys[2], "encryption", "Third key should be encryption"); // Query values using new methods - (bool[] memory existsReturned, string[] memory returnedValues) = + (bool[] memory existsReturned, bytes[] memory returnedValues) = registry.getProductCapabilities(providerId, ServiceProviderRegistryStorage.ProductType.PDP, returnedKeys); assertTrue(existsReturned[0] && existsReturned[1] && existsReturned[2], "All keys should exist"); assertEq(returnedValues[0], "us-west-2", "First value should be us-west-2"); @@ -1365,9 +1152,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { } function testUpdateWithCapabilities() public { - // Empty capability arrays - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); // Register with empty capabilities vm.prank(provider1); @@ -1376,43 +1161,38 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - emptyKeys, - emptyValues + keys, + values ); + (string[] memory updatedKeys, bytes[] memory updatedValues) = updatedPDPData.toCapabilities(2); // Update with capabilities - string[] memory capKeys = new string[](2); - capKeys[0] = "support"; - capKeys[1] = "sla"; + updatedKeys[0] = "support"; + updatedKeys[1] = "sla"; - string[] memory capValues = new string[](2); - capValues[0] = "24/7"; - capValues[1] = "99.99%"; + updatedValues[0] = bytes("24/7"); + updatedValues[1] = bytes("99.99%"); vm.prank(provider1); - registry.updateProduct( - ServiceProviderRegistryStorage.ProductType.PDP, encodedUpdatedPDPData, capKeys, capValues - ); + registry.updateProduct(ServiceProviderRegistryStorage.ProductType.PDP, updatedKeys, updatedValues); // Verify capabilities updated - (, string[] memory returnedKeys,) = registry.getProduct(1, ServiceProviderRegistryStorage.ProductType.PDP); + (string[] memory returnedKeys,) = registry.getProduct(1, ServiceProviderRegistryStorage.ProductType.PDP); - assertEq(returnedKeys.length, 2, "Should have 2 capability keys"); + assertEq(returnedKeys.length, updatedKeys.length, "Should have 2 capability keys"); assertEq(returnedKeys[0], "support", "First key should be support"); // Verify value using new method - (bool supExists, string memory supportVal) = + (bool supExists, bytes memory supportVal) = registry.getProductCapability(1, ServiceProviderRegistryStorage.ProductType.PDP, "support"); assertTrue(supExists, "support capability should exist"); assertEq(supportVal, "24/7", "First value should be 24/7"); } function testInvalidCapabilityKeyTooLong() public { - string[] memory capKeys = new string[](1); + (string[] memory capKeys, bytes[] memory capValues) = defaultPDPData.toCapabilities(1); capKeys[0] = "thisKeyIsWayTooLongAndExceedsLimit"; // 35 chars, max is MAX_CAPABILITY_KEY_LENGTH (32) - string[] memory capValues = new string[](1); capValues[0] = "value"; vm.prank(provider1); @@ -1422,17 +1202,15 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, capKeys, capValues ); } function testInvalidCapabilityValueTooLong() public { - string[] memory capKeys = new string[](1); + (string[] memory capKeys, bytes[] memory capValues) = defaultPDPData.toCapabilities(1); capKeys[0] = "key"; - string[] memory capValues = new string[](1); capValues[0] = "This value is way too long and exceeds the maximum allowed length. It is specifically designed to be longer than 128 characters to test the validation of capability values"; // > MAX_CAPABILITY_VALUE_LENGTH (128) chars @@ -1443,7 +1221,6 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, capKeys, capValues ); @@ -1454,7 +1231,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { capKeys[0] = "key1"; capKeys[1] = "key2"; - string[] memory capValues = new string[](1); + bytes[] memory capValues = new bytes[](1); capValues[0] = "value1"; vm.prank(provider1); @@ -1464,7 +1241,6 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, capKeys, capValues ); @@ -1475,9 +1251,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { string memory longDescription = "This is a very long description that exceeds the maximum allowed length of 256 characters. It just keeps going and going and going and going and going and going and going and going and going and going and going and going and going and going and going and characters limit!"; - // Empty capability arrays - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); vm.prank(provider1); vm.expectRevert("Description too long"); @@ -1486,9 +1260,8 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", longDescription, ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - emptyKeys, - emptyValues + keys, + values ); } @@ -1496,7 +1269,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { string[] memory capKeys = new string[](1); capKeys[0] = ""; - string[] memory capValues = new string[](1); + bytes[] memory capValues = new bytes[](1); capValues[0] = "value"; vm.prank(provider1); @@ -1506,7 +1279,6 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, capKeys, capValues ); @@ -1515,11 +1287,11 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { function testTooManyCapabilities() public { // Create 11 capabilities (exceeds MAX_CAPABILITIES of 10) string[] memory capKeys = new string[](11); - string[] memory capValues = new string[](11); + bytes[] memory capValues = new bytes[](11); - for (uint256 i = 0; i < 11; i++) { + for (uint256 i = 0; i < 31; i++) { capKeys[i] = string(abi.encodePacked("key", vm.toString(i))); - capValues[i] = string(abi.encodePacked("value", vm.toString(i))); + capValues[i] = abi.encodePacked("value", vm.toString(i)); } vm.prank(provider1); @@ -1529,29 +1301,27 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, capKeys, capValues ); } function testMaxCapabilitiesAllowed() public { - // Create exactly 10 capabilities (should succeed) - string[] memory capKeys = new string[](10); - string[] memory capValues = new string[](10); + (string[] memory capKeys, bytes[] memory capValues) = defaultPDPData.toCapabilities(24); - for (uint256 i = 0; i < 10; i++) { + for (uint256 i = 0; i < 15; i++) { capKeys[i] = string(abi.encodePacked("key", vm.toString(i))); - capValues[i] = string(abi.encodePacked("value", vm.toString(i))); + capValues[i] = abi.encodePacked("value", vm.toString(i)); } + assertEq(capKeys.length, registry.MAX_CAPABILITIES()); + vm.prank(provider1); uint256 providerId = registry.registerProvider{value: REGISTRATION_FEE}( provider1, // payee "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, capKeys, capValues ); @@ -1559,9 +1329,9 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { assertEq(providerId, 1, "Should register successfully with 10 capabilities"); // Verify all 10 capabilities were stored - (, string[] memory returnedKeys,) = + (string[] memory returnedKeys,) = registry.getProduct(providerId, ServiceProviderRegistryStorage.ProductType.PDP); - assertEq(returnedKeys.length, 10, "Should have exactly 10 capability keys"); + assertEq(returnedKeys.length, capKeys.length, "Should have the same number of keys"); } // ========== New Capability Query Methods Tests ========== @@ -1573,7 +1343,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { capKeys[1] = "tier"; capKeys[2] = "storage"; - string[] memory capValues = new string[](3); + bytes[] memory capValues = new bytes[](3); capValues[0] = "us-west-2"; capValues[1] = "premium"; capValues[2] = "100TB"; @@ -1584,29 +1354,28 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, capKeys, capValues ); // Test single capability queries - (bool regionExists, string memory region) = + (bool regionExists, bytes memory region) = registry.getProductCapability(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "region"); assertTrue(regionExists, "region capability should exist"); assertEq(region, "us-west-2", "Region capability should match"); - (bool tierExists, string memory tier) = + (bool tierExists, bytes memory tier) = registry.getProductCapability(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "tier"); assertTrue(tierExists, "tier capability should exist"); assertEq(tier, "premium", "Tier capability should match"); - (bool storageExists, string memory storageVal) = + (bool storageExists, bytes memory storageVal) = registry.getProductCapability(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "storage"); assertTrue(storageExists, "storage capability should exist"); assertEq(storageVal, "100TB", "Storage capability should match"); // Test querying non-existent capability - (bool nonExists, string memory nonExistent) = + (bool nonExists, bytes memory nonExistent) = registry.getProductCapability(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "nonexistent"); assertFalse(nonExists, "Non-existent capability should not exist"); assertEq(nonExistent, "", "Non-existent capability should return empty string"); @@ -1620,7 +1389,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { capKeys[2] = "storage"; capKeys[3] = "compliance"; - string[] memory capValues = new string[](4); + bytes[] memory capValues = new bytes[](4); capValues[0] = "eu-west-1"; capValues[1] = "standard"; capValues[2] = "50TB"; @@ -1632,7 +1401,6 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, capKeys, capValues ); @@ -1643,7 +1411,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { queryKeys[1] = "compliance"; queryKeys[2] = "region"; - (bool[] memory resultsExist, string[] memory results) = + (bool[] memory resultsExist, bytes[] memory results) = registry.getProductCapabilities(providerId, ServiceProviderRegistryStorage.ProductType.PDP, queryKeys); assertEq(results.length, 3, "Should return 3 values"); @@ -1659,7 +1427,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { mixedKeys[2] = "storage"; mixedKeys[3] = "nonexistent2"; - (bool[] memory mixedExist, string[] memory mixedResults) = + (bool[] memory mixedExist, bytes[] memory mixedResults) = registry.getProductCapabilities(providerId, ServiceProviderRegistryStorage.ProductType.PDP, mixedKeys); assertEq(mixedResults.length, 4, "Should return 4 values"); @@ -1674,12 +1442,11 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { } function testDirectMappingAccess() public { + (string[] memory capKeys, bytes[] memory capValues) = updatedPDPData.toCapabilities(2); // Register provider with capabilities - string[] memory capKeys = new string[](2); capKeys[0] = "datacenter"; capKeys[1] = "bandwidth"; - string[] memory capValues = new string[](2); capValues[0] = "NYC-01"; capValues[1] = "10Gbps"; @@ -1689,25 +1456,22 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, capKeys, capValues ); // Test direct public mapping access - string memory datacenter = + bytes memory datacenter = registry.productCapabilities(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "datacenter"); assertEq(datacenter, "NYC-01", "Direct mapping access should work"); - string memory bandwidth = + bytes memory bandwidth = registry.productCapabilities(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "bandwidth"); assertEq(bandwidth, "10Gbps", "Direct mapping access should work for bandwidth"); } function testUpdateWithTooManyCapabilities() public { - // Register provider with empty capabilities first - string[] memory emptyKeys = new string[](0); - string[] memory emptyValues = new string[](0); + (string[] memory capKeys, bytes[] memory capValues) = defaultPDPData.toCapabilities(25); vm.prank(provider1); registry.registerProvider{value: REGISTRATION_FEE}( @@ -1715,35 +1479,30 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider description", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - emptyKeys, - emptyValues + capKeys, + capValues ); // Try to update with 11 capabilities (exceeds MAX_CAPABILITIES of 10) - string[] memory capKeys = new string[](11); - string[] memory capValues = new string[](11); - for (uint256 i = 0; i < 11; i++) { capKeys[i] = string(abi.encodePacked("key", vm.toString(i))); - capValues[i] = string(abi.encodePacked("value", vm.toString(i))); + capValues[i] = abi.encodePacked("value", vm.toString(i)); } + assertEq(capKeys.length, registry.MAX_CAPABILITIES() + 2); + vm.prank(provider1); vm.expectRevert("Too many capabilities"); - registry.updateProduct( - ServiceProviderRegistryStorage.ProductType.PDP, encodedUpdatedPDPData, capKeys, capValues - ); + registry.updateProduct(ServiceProviderRegistryStorage.ProductType.PDP, capKeys, capValues); } function testCapabilityUpdateClearsOldValues() public { + (string[] memory initialKeys, bytes[] memory initialValues) = updatedPDPData.toCapabilities(3); // Register provider with initial capabilities - string[] memory initialKeys = new string[](3); initialKeys[0] = "region"; initialKeys[1] = "tier"; initialKeys[2] = "oldkey"; - string[] memory initialValues = new string[](3); initialValues[0] = "us-east-1"; initialValues[1] = "basic"; initialValues[2] = "oldvalue"; @@ -1754,50 +1513,46 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { "", "Test provider", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, initialKeys, initialValues ); // Verify initial values - (bool oldExists, string memory oldValue) = + (bool oldExists, bytes memory oldValue) = registry.getProductCapability(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "oldkey"); assertTrue(oldExists, "Old key should exist initially"); assertEq(oldValue, "oldvalue", "Old key should have value initially"); + (string[] memory newKeys, bytes[] memory newValues) = updatedPDPData.toCapabilities(2); // Update with new capabilities (without oldkey) - string[] memory newKeys = new string[](2); newKeys[0] = "region"; newKeys[1] = "newkey"; - string[] memory newValues = new string[](2); newValues[0] = "eu-central-1"; newValues[1] = "newvalue"; vm.prank(provider1); - registry.updateProduct( - ServiceProviderRegistryStorage.ProductType.PDP, encodedUpdatedPDPData, newKeys, newValues - ); + registry.updateProduct(ServiceProviderRegistryStorage.ProductType.PDP, newKeys, newValues); // Verify old key is cleared - (bool clearedExists, string memory clearedValue) = + (bool clearedExists, bytes memory clearedValue) = registry.getProductCapability(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "oldkey"); assertFalse(clearedExists, "Old key should not exist after update"); assertEq(clearedValue, "", "Old key should be cleared after update"); // Verify new values are set - (bool regionExists, string memory newRegion) = + (bool regionExists, bytes memory newRegion) = registry.getProductCapability(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "region"); assertTrue(regionExists, "Region key should exist"); assertEq(newRegion, "eu-central-1", "Region should be updated"); - (bool newKeyExists, string memory newKey) = + (bool newKeyExists, bytes memory newKey) = registry.getProductCapability(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "newkey"); assertTrue(newKeyExists, "New key should exist"); assertEq(newKey, "newvalue", "New key should have value"); // Verify tier key is also cleared (was in initial but not in update) - (bool tierCleared, string memory clearedTier) = + (bool tierCleared, bytes memory clearedTier) = registry.getProductCapability(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "tier"); assertFalse(tierCleared, "Tier key should not exist after update"); assertEq(clearedTier, "", "Tier key should be cleared after update"); diff --git a/service_contracts/test/ServiceProviderRegistryPagination.t.sol b/service_contracts/test/ServiceProviderRegistryPagination.t.sol index ba9d9ef2..fce01563 100644 --- a/service_contracts/test/ServiceProviderRegistryPagination.t.sol +++ b/service_contracts/test/ServiceProviderRegistryPagination.t.sol @@ -4,10 +4,13 @@ pragma solidity ^0.8.20; import {MockFVMTest} from "@fvm-solidity/mocks/MockFVMTest.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {PDPOffering} from "../src/PDPOffering.sol"; import {ServiceProviderRegistry} from "../src/ServiceProviderRegistry.sol"; import {ServiceProviderRegistryStorage} from "../src/ServiceProviderRegistryStorage.sol"; contract ServiceProviderRegistryPaginationTest is MockFVMTest { + using PDPOffering for PDPOffering.Schema; + ServiceProviderRegistry public registry; address public owner = address(0x1); @@ -21,8 +24,7 @@ contract ServiceProviderRegistryPaginationTest is MockFVMTest { uint256 public constant REGISTRATION_FEE = 5 ether; string public constant SERVICE_URL = "https://test-service.com"; - ServiceProviderRegistryStorage.PDPOffering public defaultPDPData; - bytes public encodedDefaultPDPData; + PDPOffering.Schema public defaultPDPData; function setUp() public override { super.setUp(); @@ -40,20 +42,18 @@ contract ServiceProviderRegistryPaginationTest is MockFVMTest { vm.stopPrank(); // Set up default PDP data - defaultPDPData = ServiceProviderRegistryStorage.PDPOffering({ + defaultPDPData = PDPOffering.Schema({ serviceURL: SERVICE_URL, minPieceSizeInBytes: 1024, maxPieceSizeInBytes: 1048576, ipniPiece: true, ipniIpfs: false, - storagePricePerTibPerMonth: 100, + storagePricePerTibPerDay: 100, minProvingPeriodInEpochs: 10, location: "US-WEST", paymentTokenAddress: IERC20(address(0)) }); - encodedDefaultPDPData = abi.encode(defaultPDPData); - // Give providers ETH for registration vm.deal(provider1, 10 ether); vm.deal(provider2, 10 ether); @@ -85,14 +85,14 @@ contract ServiceProviderRegistryPaginationTest is MockFVMTest { function testPaginationSingleProvider() public { // Register one provider vm.prank(provider1); + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); registry.registerProvider{value: REGISTRATION_FEE}( provider1, // payee "", "Provider 1", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - new string[](0), - new string[](0) + keys, + values ); // Get with limit larger than count @@ -123,6 +123,7 @@ contract ServiceProviderRegistryPaginationTest is MockFVMTest { function testPaginationPageBoundaries() public { // Register 5 providers address[5] memory providers = [provider1, provider2, provider3, provider4, provider5]; + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); for (uint256 i = 0; i < 5; i++) { vm.prank(providers[i]); registry.registerProvider{value: REGISTRATION_FEE}( @@ -130,9 +131,8 @@ contract ServiceProviderRegistryPaginationTest is MockFVMTest { "", string.concat("Provider ", vm.toString(i + 1)), ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - new string[](0), - new string[](0) + keys, + values ); } @@ -174,6 +174,7 @@ contract ServiceProviderRegistryPaginationTest is MockFVMTest { function testPaginationWithInactiveProviders() public { // Register 5 providers address[5] memory providers = [provider1, provider2, provider3, provider4, provider5]; + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); for (uint256 i = 0; i < 5; i++) { vm.prank(providers[i]); registry.registerProvider{value: REGISTRATION_FEE}( @@ -181,9 +182,8 @@ contract ServiceProviderRegistryPaginationTest is MockFVMTest { "", string.concat("Provider ", vm.toString(i + 1)), ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - new string[](0), - new string[](0) + keys, + values ); } @@ -218,6 +218,7 @@ contract ServiceProviderRegistryPaginationTest is MockFVMTest { // ========== Test Edge Cases with Limits ========== function testPaginationEdgeLimits() public { + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); // Register 3 providers vm.prank(provider1); registry.registerProvider{value: REGISTRATION_FEE}( @@ -225,9 +226,8 @@ contract ServiceProviderRegistryPaginationTest is MockFVMTest { "", "Provider 1", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - new string[](0), - new string[](0) + keys, + values ); vm.prank(provider2); @@ -236,9 +236,8 @@ contract ServiceProviderRegistryPaginationTest is MockFVMTest { "", "Provider 2", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - new string[](0), - new string[](0) + keys, + values ); vm.prank(provider3); @@ -247,9 +246,8 @@ contract ServiceProviderRegistryPaginationTest is MockFVMTest { "", "Provider 3", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - new string[](0), - new string[](0) + keys, + values ); // Test with limit 0 (should return empty) @@ -277,6 +275,7 @@ contract ServiceProviderRegistryPaginationTest is MockFVMTest { // ========== Test Consistency with getAllActiveProviders ========== function testPaginationConsistencyWithGetAll() public { + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); // Register 6 providers address[6] memory providers = [provider1, provider2, provider3, provider4, provider5, provider6]; for (uint256 i = 0; i < 6; i++) { @@ -286,9 +285,8 @@ contract ServiceProviderRegistryPaginationTest is MockFVMTest { "", string.concat("Provider ", vm.toString(i + 1)), ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - new string[](0), - new string[](0) + keys, + values ); } @@ -340,6 +338,7 @@ contract ServiceProviderRegistryPaginationTest is MockFVMTest { // Initially should be 0 assertEq(registry.activeProviderCount(), 0); + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); // Register first provider vm.prank(provider1); registry.registerProvider{value: REGISTRATION_FEE}( @@ -347,9 +346,8 @@ contract ServiceProviderRegistryPaginationTest is MockFVMTest { "", "Provider 1", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - new string[](0), - new string[](0) + keys, + values ); assertEq(registry.activeProviderCount(), 1); @@ -360,9 +358,8 @@ contract ServiceProviderRegistryPaginationTest is MockFVMTest { "", "Provider 2", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - new string[](0), - new string[](0) + keys, + values ); assertEq(registry.activeProviderCount(), 2); @@ -378,9 +375,8 @@ contract ServiceProviderRegistryPaginationTest is MockFVMTest { "", "Provider 3", ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - new string[](0), - new string[](0) + keys, + values ); assertEq(registry.activeProviderCount(), 2); @@ -397,6 +393,7 @@ contract ServiceProviderRegistryPaginationTest is MockFVMTest { // ========== Test Sequential Pages ========== function testSequentialPagination() public { + (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); // Register 10 providers (need 4 more addresses) address provider7 = address(0x8); address provider8 = address(0x9); @@ -428,9 +425,8 @@ contract ServiceProviderRegistryPaginationTest is MockFVMTest { "", string.concat("Provider ", vm.toString(i + 1)), ServiceProviderRegistryStorage.ProductType.PDP, - encodedDefaultPDPData, - new string[](0), - new string[](0) + keys, + values ); } From e317e430090ebf07baf101d77845b7aa13f38b28 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Wed, 22 Oct 2025 22:33:07 -0500 Subject: [PATCH 09/25] tests pass --- .../src/ServiceProviderRegistry.sol | 6 +-- .../test/ServiceProviderRegistryFull.t.sol | 42 +++++++++---------- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/service_contracts/src/ServiceProviderRegistry.sol b/service_contracts/src/ServiceProviderRegistry.sol index 0a723c10..ef875c9a 100644 --- a/service_contracts/src/ServiceProviderRegistry.sol +++ b/service_contracts/src/ServiceProviderRegistry.sol @@ -236,12 +236,12 @@ contract ServiceProviderRegistry is string[] memory capabilityKeys, bytes[] calldata capabilityValues ) private { - // Validate product data - _validateProductKeys(productType, capabilityKeys); - // Validate capability k/v pairs _validateCapabilities(capabilityKeys, capabilityValues); + // Validate product data + _validateProductKeys(productType, capabilityKeys); + // Store product providerProducts[providerId][productType] = ServiceProduct({productType: productType, capabilityKeys: capabilityKeys, isActive: true}); diff --git a/service_contracts/test/ServiceProviderRegistryFull.t.sol b/service_contracts/test/ServiceProviderRegistryFull.t.sol index 45061427..8dd664c0 100644 --- a/service_contracts/test/ServiceProviderRegistryFull.t.sol +++ b/service_contracts/test/ServiceProviderRegistryFull.t.sol @@ -95,7 +95,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { // Verify capability constants assertEq(registry.MAX_CAPABILITY_KEY_LENGTH(), 32, "Max capability key length should be 32"); assertEq(registry.MAX_CAPABILITY_VALUE_LENGTH(), 128, "Max capability value length should be 128"); - assertEq(registry.MAX_CAPABILITIES(), 10, "Max capabilities should be 10"); + assertEq(registry.MAX_CAPABILITIES(), 24, "Max capabilities should be 24"); } // ========== Registration Tests ========== @@ -380,7 +380,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { // Test service URL too long values[0] = new bytes(129); vm.prank(provider1); - vm.expectRevert("Service URL too long"); + vm.expectRevert("Capability value too long"); registry.registerProvider{value: REGISTRATION_FEE}( provider1, // payee "", @@ -396,7 +396,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { values[7][i] = "a"; } vm.prank(provider1); - vm.expectRevert("Location too long"); + vm.expectRevert("Capability value too long"); registry.registerProvider{value: REGISTRATION_FEE}( provider1, // payee "", @@ -424,6 +424,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { ); // Update PDP service using new updateProduct function + (keys, values) = updatedPDPData.toCapabilities(); vm.startPrank(provider1); vm.expectEmit(true, true, false, true); @@ -841,6 +842,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { ); values[0] = SERVICE_URL_2; + vm.prank(provider2); registry.registerProvider{value: REGISTRATION_FEE}( provider2, // payee "", @@ -911,7 +913,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { registry.getProvider(1); vm.expectRevert("Provider does not exist"); - registry.getPDPService(1); + registry.getProduct(1, ServiceProviderRegistryStorage.ProductType.PDP); vm.expectRevert("Provider does not exist"); registry.isProviderActive(1); @@ -941,7 +943,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { 1, ServiceProviderRegistryStorage.ProductType.PDP, provider1, updatedKeys, updatedValues ); - registry.updateProduct(ServiceProviderRegistryStorage.ProductType.PDP, keys, values); + registry.updateProduct(ServiceProviderRegistryStorage.ProductType.PDP, updatedKeys, updatedValues); vm.stopPrank(); // Verify the product was updated (check the actual data) @@ -1266,10 +1268,9 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { } function testEmptyCapabilityKey() public { - string[] memory capKeys = new string[](1); + (string[] memory capKeys, bytes[] memory capValues) = updatedPDPData.toCapabilities(1); capKeys[0] = ""; - bytes[] memory capValues = new bytes[](1); capValues[0] = "value"; vm.prank(provider1); @@ -1285,11 +1286,9 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { } function testTooManyCapabilities() public { - // Create 11 capabilities (exceeds MAX_CAPABILITIES of 10) - string[] memory capKeys = new string[](11); - bytes[] memory capValues = new bytes[](11); + (string[] memory capKeys, bytes[] memory capValues) = defaultPDPData.toCapabilities(25); - for (uint256 i = 0; i < 31; i++) { + for (uint256 i = 0; i < 16; i++) { capKeys[i] = string(abi.encodePacked("key", vm.toString(i))); capValues[i] = abi.encodePacked("value", vm.toString(i)); } @@ -1307,7 +1306,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { } function testMaxCapabilitiesAllowed() public { - (string[] memory capKeys, bytes[] memory capValues) = defaultPDPData.toCapabilities(24); + (string[] memory capKeys, bytes[] memory capValues) = defaultPDPData.toCapabilities(15); for (uint256 i = 0; i < 15; i++) { capKeys[i] = string(abi.encodePacked("key", vm.toString(i))); @@ -1337,13 +1336,12 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { // ========== New Capability Query Methods Tests ========== function testGetProductCapability() public { + (string[] memory capKeys, bytes[] memory capValues) = defaultPDPData.toCapabilities(3); // Register provider with capabilities - string[] memory capKeys = new string[](3); capKeys[0] = "region"; capKeys[1] = "tier"; capKeys[2] = "storage"; - bytes[] memory capValues = new bytes[](3); capValues[0] = "us-west-2"; capValues[1] = "premium"; capValues[2] = "100TB"; @@ -1382,14 +1380,13 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { } function testGetProductCapabilities() public { + (string[] memory capKeys, bytes[] memory capValues) = defaultPDPData.toCapabilities(4); // Register provider with capabilities - string[] memory capKeys = new string[](4); capKeys[0] = "region"; capKeys[1] = "tier"; capKeys[2] = "storage"; capKeys[3] = "compliance"; - bytes[] memory capValues = new bytes[](4); capValues[0] = "eu-west-1"; capValues[1] = "standard"; capValues[2] = "50TB"; @@ -1471,7 +1468,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { } function testUpdateWithTooManyCapabilities() public { - (string[] memory capKeys, bytes[] memory capValues) = defaultPDPData.toCapabilities(25); + (string[] memory capKeys, bytes[] memory capValues) = defaultPDPData.toCapabilities(); vm.prank(provider1); registry.registerProvider{value: REGISTRATION_FEE}( @@ -1483,17 +1480,18 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { capValues ); + (string[] memory updatedKeys, bytes[] memory updatedValues) = updatedPDPData.toCapabilities(16); // Try to update with 11 capabilities (exceeds MAX_CAPABILITIES of 10) - for (uint256 i = 0; i < 11; i++) { - capKeys[i] = string(abi.encodePacked("key", vm.toString(i))); - capValues[i] = abi.encodePacked("value", vm.toString(i)); + for (uint256 i = 0; i < 16; i++) { + updatedKeys[i] = string(abi.encodePacked("key", vm.toString(i))); + updatedValues[i] = abi.encodePacked("value", vm.toString(i)); } - assertEq(capKeys.length, registry.MAX_CAPABILITIES() + 2); + assertEq(updatedKeys.length, registry.MAX_CAPABILITIES() + 1); vm.prank(provider1); vm.expectRevert("Too many capabilities"); - registry.updateProduct(ServiceProviderRegistryStorage.ProductType.PDP, capKeys, capValues); + registry.updateProduct(ServiceProviderRegistryStorage.ProductType.PDP, updatedKeys, updatedValues); } function testCapabilityUpdateClearsOldValues() public { From fc80b4ba79392d31a6e2e0ce2591d1752376267d Mon Sep 17 00:00:00 2001 From: William Morriss Date: Wed, 22 Oct 2025 22:38:18 -0500 Subject: [PATCH 10/25] mv PDPOffering.sol to test --- service_contracts/test/FilecoinWarmStorageService.t.sol | 2 +- service_contracts/test/FilecoinWarmStorageServiceOwner.t.sol | 2 +- service_contracts/{src => test}/PDPOffering.sol | 4 ++-- service_contracts/test/ProviderValidation.t.sol | 2 +- service_contracts/test/ServiceProviderRegistry.t.sol | 2 +- service_contracts/test/ServiceProviderRegistryFull.t.sol | 2 +- .../test/ServiceProviderRegistryPagination.t.sol | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) rename service_contracts/{src => test}/PDPOffering.sol (96%) diff --git a/service_contracts/test/FilecoinWarmStorageService.t.sol b/service_contracts/test/FilecoinWarmStorageService.t.sol index 813d4b97..c23c086a 100644 --- a/service_contracts/test/FilecoinWarmStorageService.t.sol +++ b/service_contracts/test/FilecoinWarmStorageService.t.sol @@ -19,7 +19,7 @@ import {MockERC20, MockPDPVerifier} from "./mocks/SharedMocks.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {Errors} from "../src/Errors.sol"; -import {PDPOffering} from "../src/PDPOffering.sol"; +import {PDPOffering} from "./PDPOffering.sol"; import {ServiceProviderRegistryStorage} from "../src/ServiceProviderRegistryStorage.sol"; import {ServiceProviderRegistry} from "../src/ServiceProviderRegistry.sol"; diff --git a/service_contracts/test/FilecoinWarmStorageServiceOwner.t.sol b/service_contracts/test/FilecoinWarmStorageServiceOwner.t.sol index d6d23f57..d2e6e967 100644 --- a/service_contracts/test/FilecoinWarmStorageServiceOwner.t.sol +++ b/service_contracts/test/FilecoinWarmStorageServiceOwner.t.sol @@ -5,7 +5,7 @@ import {MockFVMTest} from "@fvm-solidity/mocks/MockFVMTest.sol"; import {console} from "forge-std/Test.sol"; import {FilecoinWarmStorageService} from "../src/FilecoinWarmStorageService.sol"; import {FilecoinWarmStorageServiceStateView} from "../src/FilecoinWarmStorageServiceStateView.sol"; -import {PDPOffering} from "../src/PDPOffering.sol"; +import {PDPOffering} from "./PDPOffering.sol"; import {ServiceProviderRegistry} from "../src/ServiceProviderRegistry.sol"; import {ServiceProviderRegistryStorage} from "../src/ServiceProviderRegistryStorage.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; diff --git a/service_contracts/src/PDPOffering.sol b/service_contracts/test/PDPOffering.sol similarity index 96% rename from service_contracts/src/PDPOffering.sol rename to service_contracts/test/PDPOffering.sol index 90a39d25..6957a92e 100644 --- a/service_contracts/src/PDPOffering.sol +++ b/service_contracts/test/PDPOffering.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.30; -import {ServiceProviderRegistry} from "./ServiceProviderRegistry.sol"; -import {ServiceProviderRegistryStorage} from "./ServiceProviderRegistryStorage.sol"; +import {ServiceProviderRegistry} from "../src/ServiceProviderRegistry.sol"; +import {ServiceProviderRegistryStorage} from "../src/ServiceProviderRegistryStorage.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; /// @notice PDP-specific service data diff --git a/service_contracts/test/ProviderValidation.t.sol b/service_contracts/test/ProviderValidation.t.sol index d38dc6e7..4ab8a7dd 100644 --- a/service_contracts/test/ProviderValidation.t.sol +++ b/service_contracts/test/ProviderValidation.t.sol @@ -11,7 +11,7 @@ import {SessionKeyRegistry} from "@session-key-registry/SessionKeyRegistry.sol"; import {FilecoinWarmStorageService} from "../src/FilecoinWarmStorageService.sol"; import {FilecoinWarmStorageServiceStateView} from "../src/FilecoinWarmStorageServiceStateView.sol"; -import {PDPOffering} from "../src/PDPOffering.sol"; +import {PDPOffering} from "./PDPOffering.sol"; import {ServiceProviderRegistry} from "../src/ServiceProviderRegistry.sol"; import {ServiceProviderRegistryStorage} from "../src/ServiceProviderRegistryStorage.sol"; import {MockERC20, MockPDPVerifier} from "./mocks/SharedMocks.sol"; diff --git a/service_contracts/test/ServiceProviderRegistry.t.sol b/service_contracts/test/ServiceProviderRegistry.t.sol index 713094d2..e9692b98 100644 --- a/service_contracts/test/ServiceProviderRegistry.t.sol +++ b/service_contracts/test/ServiceProviderRegistry.t.sol @@ -5,7 +5,7 @@ import {MockFVMTest} from "@fvm-solidity/mocks/MockFVMTest.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {PDPOffering} from "../src/PDPOffering.sol"; +import {PDPOffering} from "./PDPOffering.sol"; import {ServiceProviderRegistry} from "../src/ServiceProviderRegistry.sol"; import {ServiceProviderRegistryStorage} from "../src/ServiceProviderRegistryStorage.sol"; diff --git a/service_contracts/test/ServiceProviderRegistryFull.t.sol b/service_contracts/test/ServiceProviderRegistryFull.t.sol index 8dd664c0..d85817a1 100644 --- a/service_contracts/test/ServiceProviderRegistryFull.t.sol +++ b/service_contracts/test/ServiceProviderRegistryFull.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.20; import {BURN_ADDRESS} from "@fvm-solidity/FVMActors.sol"; import {MockFVMTest} from "@fvm-solidity/mocks/MockFVMTest.sol"; -import {PDPOffering} from "../src/PDPOffering.sol"; +import {PDPOffering} from "./PDPOffering.sol"; import {ServiceProviderRegistry} from "../src/ServiceProviderRegistry.sol"; import {ServiceProviderRegistryStorage} from "../src/ServiceProviderRegistryStorage.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; diff --git a/service_contracts/test/ServiceProviderRegistryPagination.t.sol b/service_contracts/test/ServiceProviderRegistryPagination.t.sol index fce01563..1e9fbb8d 100644 --- a/service_contracts/test/ServiceProviderRegistryPagination.t.sol +++ b/service_contracts/test/ServiceProviderRegistryPagination.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.20; import {MockFVMTest} from "@fvm-solidity/mocks/MockFVMTest.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {PDPOffering} from "../src/PDPOffering.sol"; +import {PDPOffering} from "./PDPOffering.sol"; import {ServiceProviderRegistry} from "../src/ServiceProviderRegistry.sol"; import {ServiceProviderRegistryStorage} from "../src/ServiceProviderRegistryStorage.sol"; From 3d6682de610e9277fb01472bf62e04595530f945 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Wed, 22 Oct 2025 22:38:39 -0500 Subject: [PATCH 11/25] make update-abi --- .../abi/ServiceProviderRegistry.abi.json | 236 ++---------------- .../ServiceProviderRegistryStorage.abi.json | 9 +- 2 files changed, 29 insertions(+), 216 deletions(-) diff --git a/service_contracts/abi/ServiceProviderRegistry.abi.json b/service_contracts/abi/ServiceProviderRegistry.abi.json index 36e0fdb2..dcf426d8 100644 --- a/service_contracts/abi/ServiceProviderRegistry.abi.json +++ b/service_contracts/abi/ServiceProviderRegistry.abi.json @@ -123,11 +123,6 @@ "type": "uint8", "internalType": "enum ServiceProviderRegistryStorage.ProductType" }, - { - "name": "productData", - "type": "bytes", - "internalType": "bytes" - }, { "name": "capabilityKeys", "type": "string[]", @@ -135,8 +130,8 @@ }, { "name": "capabilityValues", - "type": "string[]", - "internalType": "string[]" + "type": "bytes[]", + "internalType": "bytes[]" } ], "outputs": [], @@ -282,11 +277,6 @@ "type": "uint8", "internalType": "enum ServiceProviderRegistryStorage.ProductType" }, - { - "name": "productData", - "type": "bytes", - "internalType": "bytes" - }, { "name": "capabilityKeys", "type": "string[]", @@ -353,82 +343,6 @@ ], "stateMutability": "view" }, - { - "type": "function", - "name": "getPDPService", - "inputs": [ - { - "name": "providerId", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "pdpOffering", - "type": "tuple", - "internalType": "struct ServiceProviderRegistryStorage.PDPOffering", - "components": [ - { - "name": "serviceURL", - "type": "string", - "internalType": "string" - }, - { - "name": "minPieceSizeInBytes", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "maxPieceSizeInBytes", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "ipniPiece", - "type": "bool", - "internalType": "bool" - }, - { - "name": "ipniIpfs", - "type": "bool", - "internalType": "bool" - }, - { - "name": "storagePricePerTibPerMonth", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "minProvingPeriodInEpochs", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "location", - "type": "string", - "internalType": "string" - }, - { - "name": "paymentTokenAddress", - "type": "address", - "internalType": "contract IERC20" - } - ] - }, - { - "name": "capabilityKeys", - "type": "string[]", - "internalType": "string[]" - }, - { - "name": "isActive", - "type": "bool", - "internalType": "bool" - } - ], - "stateMutability": "view" - }, { "type": "function", "name": "getProduct", @@ -445,11 +359,6 @@ } ], "outputs": [ - { - "name": "productData", - "type": "bytes", - "internalType": "bytes" - }, { "name": "capabilityKeys", "type": "string[]", @@ -491,8 +400,8 @@ }, { "name": "values", - "type": "string[]", - "internalType": "string[]" + "type": "bytes[]", + "internalType": "bytes[]" } ], "stateMutability": "view" @@ -525,8 +434,8 @@ }, { "name": "value", - "type": "string", - "internalType": "string" + "type": "bytes", + "internalType": "bytes" } ], "stateMutability": "view" @@ -839,11 +748,6 @@ "type": "uint8", "internalType": "enum ServiceProviderRegistryStorage.ProductType" }, - { - "name": "productData", - "type": "bytes", - "internalType": "bytes" - }, { "name": "capabilityKeys", "type": "string[]", @@ -962,8 +866,8 @@ "outputs": [ { "name": "value", - "type": "string", - "internalType": "string" + "type": "bytes", + "internalType": "bytes" } ], "stateMutability": "view" @@ -1032,11 +936,6 @@ "type": "uint8", "internalType": "enum ServiceProviderRegistryStorage.ProductType" }, - { - "name": "productData", - "type": "bytes", - "internalType": "bytes" - }, { "name": "isActive", "type": "bool", @@ -1121,11 +1020,6 @@ "type": "uint8", "internalType": "enum ServiceProviderRegistryStorage.ProductType" }, - { - "name": "productData", - "type": "bytes", - "internalType": "bytes" - }, { "name": "capabilityKeys", "type": "string[]", @@ -1133,8 +1027,8 @@ }, { "name": "capabilityValues", - "type": "string[]", - "internalType": "string[]" + "type": "bytes[]", + "internalType": "bytes[]" } ], "outputs": [ @@ -1186,76 +1080,6 @@ "outputs": [], "stateMutability": "nonpayable" }, - { - "type": "function", - "name": "updatePDPServiceWithCapabilities", - "inputs": [ - { - "name": "pdpOffering", - "type": "tuple", - "internalType": "struct ServiceProviderRegistryStorage.PDPOffering", - "components": [ - { - "name": "serviceURL", - "type": "string", - "internalType": "string" - }, - { - "name": "minPieceSizeInBytes", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "maxPieceSizeInBytes", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "ipniPiece", - "type": "bool", - "internalType": "bool" - }, - { - "name": "ipniIpfs", - "type": "bool", - "internalType": "bool" - }, - { - "name": "storagePricePerTibPerMonth", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "minProvingPeriodInEpochs", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "location", - "type": "string", - "internalType": "string" - }, - { - "name": "paymentTokenAddress", - "type": "address", - "internalType": "contract IERC20" - } - ] - }, - { - "name": "capabilityKeys", - "type": "string[]", - "internalType": "string[]" - }, - { - "name": "capabilityValues", - "type": "string[]", - "internalType": "string[]" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, { "type": "function", "name": "updateProduct", @@ -1265,11 +1089,6 @@ "type": "uint8", "internalType": "enum ServiceProviderRegistryStorage.ProductType" }, - { - "name": "productData", - "type": "bytes", - "internalType": "bytes" - }, { "name": "capabilityKeys", "type": "string[]", @@ -1277,8 +1096,8 @@ }, { "name": "capabilityValues", - "type": "string[]", - "internalType": "string[]" + "type": "bytes[]", + "internalType": "bytes[]" } ], "outputs": [], @@ -1399,12 +1218,6 @@ "indexed": false, "internalType": "address" }, - { - "name": "productData", - "type": "bytes", - "indexed": false, - "internalType": "bytes" - }, { "name": "capabilityKeys", "type": "string[]", @@ -1413,9 +1226,9 @@ }, { "name": "capabilityValues", - "type": "string[]", + "type": "bytes[]", "indexed": false, - "internalType": "string[]" + "internalType": "bytes[]" } ], "anonymous": false @@ -1461,12 +1274,6 @@ "indexed": false, "internalType": "address" }, - { - "name": "productData", - "type": "bytes", - "indexed": false, - "internalType": "bytes" - }, { "name": "capabilityKeys", "type": "string[]", @@ -1475,9 +1282,9 @@ }, { "name": "capabilityValues", - "type": "string[]", + "type": "bytes[]", "indexed": false, - "internalType": "string[]" + "internalType": "bytes[]" } ], "anonymous": false @@ -1578,6 +1385,17 @@ "name": "FailedCall", "inputs": [] }, + { + "type": "error", + "name": "InsufficientCapabilitiesForProduct", + "inputs": [ + { + "name": "productType", + "type": "uint8", + "internalType": "enum ServiceProviderRegistryStorage.ProductType" + } + ] + }, { "type": "error", "name": "InvalidInitialization", diff --git a/service_contracts/abi/ServiceProviderRegistryStorage.abi.json b/service_contracts/abi/ServiceProviderRegistryStorage.abi.json index 5f05c7e7..9121c16e 100644 --- a/service_contracts/abi/ServiceProviderRegistryStorage.abi.json +++ b/service_contracts/abi/ServiceProviderRegistryStorage.abi.json @@ -73,8 +73,8 @@ "outputs": [ { "name": "value", - "type": "string", - "internalType": "string" + "type": "bytes", + "internalType": "bytes" } ], "stateMutability": "view" @@ -119,11 +119,6 @@ "type": "uint8", "internalType": "enum ServiceProviderRegistryStorage.ProductType" }, - { - "name": "productData", - "type": "bytes", - "internalType": "bytes" - }, { "name": "isActive", "type": "bool", From 17bd9ec7d8982a7ec7fdaca26c7fd627d6d718ec Mon Sep 17 00:00:00 2001 From: William Morriss Date: Thu, 23 Oct 2025 14:07:40 -0500 Subject: [PATCH 12/25] update subgraph: ProductUpdated and create product --- subgraph/schemas/schema.v1.graphql | 2 -- subgraph/src/service-provider-registry.ts | 6 ------ subgraph/src/utils/decoders.ts | 25 ----------------------- subgraph/src/utils/entity.ts | 4 ---- subgraph/templates/subgraph.template.yaml | 4 ++-- 5 files changed, 2 insertions(+), 39 deletions(-) delete mode 100644 subgraph/src/utils/decoders.ts diff --git a/subgraph/schemas/schema.v1.graphql b/subgraph/schemas/schema.v1.graphql index a8a35aac..c0be2479 100644 --- a/subgraph/schemas/schema.v1.graphql +++ b/subgraph/schemas/schema.v1.graphql @@ -103,8 +103,6 @@ type Provider @entity(immutable: false) { type ProviderProduct @entity(immutable: false) { id: ID! provider: Provider! - productData: Bytes! - decodedProductData: String! productType: BigInt! capabilityKeys: [String!] capabilityValues: [String!] diff --git a/subgraph/src/service-provider-registry.ts b/subgraph/src/service-provider-registry.ts index 01e65c7e..6e3d4ecb 100644 --- a/subgraph/src/service-provider-registry.ts +++ b/subgraph/src/service-provider-registry.ts @@ -10,7 +10,6 @@ import { import { Provider, ProviderProduct } from "../generated/schema"; import { BIGINT_ONE } from "./utils/constants"; import { getServiceProviderInfo } from "./utils/contract-calls"; -import { decodePDPOfferingData } from "./utils/decoders"; import { createProviderProduct, initiateProvider } from "./utils/entity"; import { getProviderProductEntityId } from "./utils/keys"; @@ -101,7 +100,6 @@ export function handleProductAdded(event: ProductAddedEvent): void { export function handleProductUpdated(event: ProductUpdatedEvent): void { const productType = event.params.productType; const serviceProvider = event.params.serviceProvider; - const productData = event.params.productData; const capabilityKeys = event.params.capabilityKeys; const capabilityValues = event.params.capabilityValues; @@ -114,12 +112,8 @@ export function handleProductUpdated(event: ProductUpdatedEvent): void { return; } - const decodedProductData = decodePDPOfferingData(productData); - providerProduct.capabilityKeys = capabilityKeys; providerProduct.capabilityValues = capabilityValues; - providerProduct.productData = productData; - providerProduct.decodedProductData = decodedProductData.toJSON(); providerProduct.isActive = true; providerProduct.save(); } diff --git a/subgraph/src/utils/decoders.ts b/subgraph/src/utils/decoders.ts deleted file mode 100644 index d6f0f9da..00000000 --- a/subgraph/src/utils/decoders.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Bytes, ethereum, log } from "@graphprotocol/graph-ts"; -import { PDP_OFFERING_DEF } from "./constants"; -import { PDPOffering } from "./types"; - -export function decodePDPOfferingData(data: Bytes): PDPOffering { - const decoded = ethereum.decode(PDP_OFFERING_DEF, data); - - if (!decoded) { - log.warning("[decodePDPOfferingData]: failed to decode data: {}", [data.toHexString()]); - return PDPOffering.empty(); - } - - const decodedTuple = decoded.toTuple(); - return new PDPOffering( - decodedTuple[0].toString(), - decodedTuple[1].toBigInt(), - decodedTuple[2].toBigInt(), - decodedTuple[3].toBoolean(), - decodedTuple[4].toBoolean(), - decodedTuple[5].toBigInt(), - decodedTuple[6].toBigInt(), - decodedTuple[7].toString(), - decodedTuple[8].toAddress(), - ); -} diff --git a/subgraph/src/utils/entity.ts b/subgraph/src/utils/entity.ts index 74abcb9f..616e4880 100644 --- a/subgraph/src/utils/entity.ts +++ b/subgraph/src/utils/entity.ts @@ -4,7 +4,6 @@ import { ProductAdded as ProductAddedEvent } from "../../generated/ServiceProvid import { BIGINT_ZERO, BIGINT_ONE, ContractAddresses, LeafSize } from "./constants"; import { ProviderStatus } from "./types"; import { getProviderProductEntityId, getPieceEntityId, getDataSetEntityId } from "./keys"; -import { decodePDPOfferingData } from "./decoders"; import { validateCommPv2, unpaddedSize } from "./cid"; export function createRails( @@ -40,7 +39,6 @@ export function createRails( export function createProviderProduct(event: ProductAddedEvent): void { const productType = event.params.productType; const serviceProvider = event.params.serviceProvider; - const productData = event.params.productData; const capabilityKeys = event.params.capabilityKeys; const capabilityValues = event.params.capabilityValues; @@ -48,8 +46,6 @@ export function createProviderProduct(event: ProductAddedEvent): void { const providerProduct = new ProviderProduct(productId); providerProduct.provider = serviceProvider; - providerProduct.productData = productData; - providerProduct.decodedProductData = decodePDPOfferingData(productData).toJSON(); providerProduct.productType = BigInt.fromI32(productType); providerProduct.capabilityKeys = capabilityKeys; providerProduct.capabilityValues = capabilityValues; diff --git a/subgraph/templates/subgraph.template.yaml b/subgraph/templates/subgraph.template.yaml index a525e4cf..2b209831 100644 --- a/subgraph/templates/subgraph.template.yaml +++ b/subgraph/templates/subgraph.template.yaml @@ -135,9 +135,9 @@ dataSources: handler: handleProviderInfoUpdated - event: "ProviderRemoved(indexed uint256)" handler: handleProviderRemoved - - event: "ProductAdded(indexed uint256,indexed uint8,address,bytes,string[],string[])" + - event: "ProductAdded(indexed uint256,indexed uint8,address,string[],string[])" handler: handleProductAdded - - event: "ProductUpdated(indexed uint256,indexed uint8,address,bytes,string[],string[])" + - event: "ProductUpdated(indexed uint256,indexed uint8,address,string[],string[])" handler: handleProductUpdated - event: "ProductRemoved(indexed uint256,indexed uint8)" handler: handleProductRemoved From 8061aa6566800aa6429072c259734a3d173529da Mon Sep 17 00:00:00 2001 From: William Morriss Date: Thu, 23 Oct 2025 14:18:46 -0500 Subject: [PATCH 13/25] subgraph: bytes capabilityValues --- subgraph/schemas/schema.v1.graphql | 2 +- subgraph/templates/subgraph.template.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/subgraph/schemas/schema.v1.graphql b/subgraph/schemas/schema.v1.graphql index c0be2479..f27c041f 100644 --- a/subgraph/schemas/schema.v1.graphql +++ b/subgraph/schemas/schema.v1.graphql @@ -105,7 +105,7 @@ type ProviderProduct @entity(immutable: false) { provider: Provider! productType: BigInt! capabilityKeys: [String!] - capabilityValues: [String!] + capabilityValues: [Bytes!] isActive: Boolean! } diff --git a/subgraph/templates/subgraph.template.yaml b/subgraph/templates/subgraph.template.yaml index 2b209831..a72b7018 100644 --- a/subgraph/templates/subgraph.template.yaml +++ b/subgraph/templates/subgraph.template.yaml @@ -135,9 +135,9 @@ dataSources: handler: handleProviderInfoUpdated - event: "ProviderRemoved(indexed uint256)" handler: handleProviderRemoved - - event: "ProductAdded(indexed uint256,indexed uint8,address,string[],string[])" + - event: "ProductAdded(indexed uint256,indexed uint8,address,string[],bytes[])" handler: handleProductAdded - - event: "ProductUpdated(indexed uint256,indexed uint8,address,string[],string[])" + - event: "ProductUpdated(indexed uint256,indexed uint8,address,string[],bytes[])" handler: handleProductUpdated - event: "ProductRemoved(indexed uint256,indexed uint8)" handler: handleProductRemoved From 621f789cb8ba8a9958041a660e13c219a7b412a6 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Thu, 23 Oct 2025 14:40:50 -0500 Subject: [PATCH 14/25] remove misleading exists bools --- .../abi/ServiceProviderRegistry.abi.json | 39 ---------- .../src/ServiceProviderRegistry.sol | 27 +------ service_contracts/test/PDPOffering.sol | 6 +- .../test/ServiceProviderRegistry.t.sol | 22 +++--- .../test/ServiceProviderRegistryFull.t.sol | 76 +++++++------------ 5 files changed, 43 insertions(+), 127 deletions(-) diff --git a/service_contracts/abi/ServiceProviderRegistry.abi.json b/service_contracts/abi/ServiceProviderRegistry.abi.json index dcf426d8..4ad76d7d 100644 --- a/service_contracts/abi/ServiceProviderRegistry.abi.json +++ b/service_contracts/abi/ServiceProviderRegistry.abi.json @@ -393,11 +393,6 @@ } ], "outputs": [ - { - "name": "exists", - "type": "bool[]", - "internalType": "bool[]" - }, { "name": "values", "type": "bytes[]", @@ -406,40 +401,6 @@ ], "stateMutability": "view" }, - { - "type": "function", - "name": "getProductCapability", - "inputs": [ - { - "name": "providerId", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "productType", - "type": "uint8", - "internalType": "enum ServiceProviderRegistryStorage.ProductType" - }, - { - "name": "key", - "type": "string", - "internalType": "string" - } - ], - "outputs": [ - { - "name": "exists", - "type": "bool", - "internalType": "bool" - }, - { - "name": "value", - "type": "bytes", - "internalType": "bytes" - } - ], - "stateMutability": "view" - }, { "type": "function", "name": "getProvider", diff --git a/service_contracts/src/ServiceProviderRegistry.sol b/service_contracts/src/ServiceProviderRegistry.sol index ef875c9a..6af4aa51 100644 --- a/service_contracts/src/ServiceProviderRegistry.sol +++ b/service_contracts/src/ServiceProviderRegistry.sol @@ -706,46 +706,23 @@ contract ServiceProviderRegistry is /// @param providerId The ID of the provider /// @param productType The type of product /// @param keys Array of capability keys to query - /// @return exists Array of booleans indicating whether each key exists /// @return values Array of capability values corresponding to the keys (empty string for non-existent keys) function getProductCapabilities(uint256 providerId, ProductType productType, string[] calldata keys) external view providerExists(providerId) - returns (bool[] memory exists, bytes[] memory values) + returns (bytes[] memory values) { - exists = new bool[](keys.length); values = new bytes[](keys.length); // Cache the mapping reference mapping(string => bytes) storage capabilities = productCapabilities[providerId][productType]; for (uint256 i = 0; i < keys.length; i++) { - bytes memory value = capabilities[keys[i]]; - if (value.length > 0) { - exists[i] = true; - values[i] = value; - } + values[i] = capabilities[keys[i]]; } } - /// @notice Get a single capability value for a product - /// @param providerId The ID of the provider - /// @param productType The type of product - /// @param key The capability key to query - /// @return exists Whether the capability key exists - /// @return value The capability value (empty string if key doesn't exist) - function getProductCapability(uint256 providerId, ProductType productType, string calldata key) - external - view - providerExists(providerId) - returns (bool exists, bytes memory value) - { - // Directly check the mapping - value = productCapabilities[providerId][productType][key]; - exists = bytes(value).length > 0; - } - /// @notice Validate product data based on product type /// @param productType The type of product function _validateProductKeys(ProductType productType, string[] memory capabilityKeys) private pure { diff --git a/service_contracts/test/PDPOffering.sol b/service_contracts/test/PDPOffering.sol index 6957a92e..8f9bc222 100644 --- a/service_contracts/test/PDPOffering.sol +++ b/service_contracts/test/PDPOffering.sol @@ -94,8 +94,8 @@ library PDPOffering { returns (Schema memory schema, string[] memory keys, bool isActive) { (keys, isActive) = registry.getProduct(providerId, ServiceProviderRegistryStorage.ProductType.PDP); - (, bytes[] memory values) = - registry.getProductCapabilities(providerId, ServiceProviderRegistryStorage.ProductType.PDP, keys); - schema = fromCapabilities(keys, values); + schema = fromCapabilities( + keys, registry.getProductCapabilities(providerId, ServiceProviderRegistryStorage.ProductType.PDP, keys) + ); } } diff --git a/service_contracts/test/ServiceProviderRegistry.t.sol b/service_contracts/test/ServiceProviderRegistry.t.sol index e9692b98..90fd083b 100644 --- a/service_contracts/test/ServiceProviderRegistry.t.sol +++ b/service_contracts/test/ServiceProviderRegistry.t.sol @@ -109,19 +109,19 @@ contract ServiceProviderRegistryTest is MockFVMTest { assertEq(returnedKeys[2], "compliance", "Third key should be compliance"); // Use the new query methods to verify values - (bool existsRegion, bytes memory region) = - registry.getProductCapability(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "region"); - assertTrue(existsRegion, "region capability should exist"); - assertEq(region, bytes("us-east-1"), "First value should be us-east-1"); - - (bool existsTier, bytes memory tier) = - registry.getProductCapability(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "tier"); - assertTrue(existsTier, "tier capability should exist"); + bytes memory region = + registry.productCapabilities(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "region"); + assertTrue(region.length > 0, "region capability should exist"); + assertEq(region, "us-east-1", "First value should be us-east-1"); + + bytes memory tier = + registry.productCapabilities(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "tier"); + assertTrue(tier.length > 0, "tier capability should exist"); assertEq(tier, "premium", "Second value should be premium"); - (bool existsCompliance, bytes memory compliance) = - registry.getProductCapability(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "compliance"); - assertTrue(existsCompliance, "compliance capability should exist"); + bytes memory compliance = + registry.productCapabilities(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "compliance"); + assertTrue(compliance.length > 0, "compliance capability should exist"); assertEq(compliance, "SOC2", "Third value should be SOC2"); } diff --git a/service_contracts/test/ServiceProviderRegistryFull.t.sol b/service_contracts/test/ServiceProviderRegistryFull.t.sol index d85817a1..6ba54efa 100644 --- a/service_contracts/test/ServiceProviderRegistryFull.t.sol +++ b/service_contracts/test/ServiceProviderRegistryFull.t.sol @@ -187,15 +187,11 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { queryKeys[2] = "latency"; queryKeys[3] = "cert"; - (bool[] memory exists, bytes[] memory values) = + bytes[] memory values = registry.getProductCapabilities(1, ServiceProviderRegistryStorage.ProductType.PDP, queryKeys); - assertTrue(exists[0], "First key should exist"); assertEq(values[0], "EU-WEST", "First value should be EU-WEST"); - assertTrue(exists[1], "Second key should exist"); assertEq(values[1], "3x", "Second value should be 3x"); - assertTrue(exists[2], "Third key should exist"); assertEq(values[2], "low", "Third value should be low"); - assertTrue(exists[3], "Fourth key should exist"); assertEq(values[3], "ISO27001", "Fourth value should be ISO27001"); // Also verify using getProduct @@ -303,9 +299,8 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { assertEq(keys1[1], "performance", "Provider 1 second key should be performance"); // Query values for provider 1 - (bool[] memory exists1, bytes[] memory values1) = + bytes[] memory values1 = registry.getProductCapabilities(1, ServiceProviderRegistryStorage.ProductType.PDP, keys1); - assertTrue(exists1[0] && exists1[1], "All keys should exist for provider 1"); assertEq(values1[0], "US-EAST", "Provider 1 first value should be US-EAST"); assertEq(values1[1], "high", "Provider 1 second value should be high"); @@ -317,9 +312,8 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { assertEq(keys2[2], "availability", "Provider 2 third key should be availability"); // Query values for provider 2 - (bool[] memory exists2, bytes[] memory values2) = + bytes[] memory values2 = registry.getProductCapabilities(2, ServiceProviderRegistryStorage.ProductType.PDP, keys2); - assertTrue(exists2[0] && exists2[1], "All keys should exist for provider 2"); assertEq(values2[0], "ASIA-PAC", "Provider 2 first value should be ASIA-PAC"); assertEq(values2[1], "100TB", "Provider 2 second value should be 100TB"); assertEq(values2[2], "99.999%", "Provider 2 third value should be 99.999%"); @@ -756,7 +750,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { (string[] memory getProductKeys, bool isActive) = registry.getProduct(1, ServiceProviderRegistryStorage.ProductType.PDP); assertTrue(isActive, "Product should be active"); - (, bytes[] memory getProductCapabilities) = + bytes[] memory getProductCapabilities = registry.getProductCapabilities(1, ServiceProviderRegistryStorage.ProductType.PDP, getProductKeys); // Decode and verify @@ -1144,9 +1138,8 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { assertEq(returnedKeys[2], "encryption", "Third key should be encryption"); // Query values using new methods - (bool[] memory existsReturned, bytes[] memory returnedValues) = + bytes[] memory returnedValues = registry.getProductCapabilities(providerId, ServiceProviderRegistryStorage.ProductType.PDP, returnedKeys); - assertTrue(existsReturned[0] && existsReturned[1] && existsReturned[2], "All keys should exist"); assertEq(returnedValues[0], "us-west-2", "First value should be us-west-2"); assertEq(returnedValues[1], "10Gbps", "Second value should be 10Gbps"); assertEq(returnedValues[2], "AES256", "Third value should be AES256"); @@ -1185,9 +1178,8 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { assertEq(returnedKeys[0], "support", "First key should be support"); // Verify value using new method - (bool supExists, bytes memory supportVal) = - registry.getProductCapability(1, ServiceProviderRegistryStorage.ProductType.PDP, "support"); - assertTrue(supExists, "support capability should exist"); + bytes memory supportVal = + registry.productCapabilities(1, ServiceProviderRegistryStorage.ProductType.PDP, "support"); assertEq(supportVal, "24/7", "First value should be 24/7"); } @@ -1357,25 +1349,21 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { ); // Test single capability queries - (bool regionExists, bytes memory region) = - registry.getProductCapability(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "region"); - assertTrue(regionExists, "region capability should exist"); + bytes memory region = + registry.productCapabilities(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "region"); assertEq(region, "us-west-2", "Region capability should match"); - (bool tierExists, bytes memory tier) = - registry.getProductCapability(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "tier"); - assertTrue(tierExists, "tier capability should exist"); + bytes memory tier = + registry.productCapabilities(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "tier"); assertEq(tier, "premium", "Tier capability should match"); - (bool storageExists, bytes memory storageVal) = - registry.getProductCapability(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "storage"); - assertTrue(storageExists, "storage capability should exist"); + bytes memory storageVal = + registry.productCapabilities(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "storage"); assertEq(storageVal, "100TB", "Storage capability should match"); // Test querying non-existent capability - (bool nonExists, bytes memory nonExistent) = - registry.getProductCapability(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "nonexistent"); - assertFalse(nonExists, "Non-existent capability should not exist"); + bytes memory nonExistent = + registry.productCapabilities(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "nonexistent"); assertEq(nonExistent, "", "Non-existent capability should return empty string"); } @@ -1408,11 +1396,10 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { queryKeys[1] = "compliance"; queryKeys[2] = "region"; - (bool[] memory resultsExist, bytes[] memory results) = + bytes[] memory results = registry.getProductCapabilities(providerId, ServiceProviderRegistryStorage.ProductType.PDP, queryKeys); assertEq(results.length, 3, "Should return 3 values"); - assertTrue(resultsExist[0] && resultsExist[1] && resultsExist[2], "All queried keys should exist"); assertEq(results[0], "standard", "First result should be tier value"); assertEq(results[1], "GDPR", "Second result should be compliance value"); assertEq(results[2], "eu-west-1", "Third result should be region value"); @@ -1424,14 +1411,10 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { mixedKeys[2] = "storage"; mixedKeys[3] = "nonexistent2"; - (bool[] memory mixedExist, bytes[] memory mixedResults) = + bytes[] memory mixedResults = registry.getProductCapabilities(providerId, ServiceProviderRegistryStorage.ProductType.PDP, mixedKeys); assertEq(mixedResults.length, 4, "Should return 4 values"); - assertTrue(mixedExist[0], "First key should exist"); - assertFalse(mixedExist[1], "Second key should not exist"); - assertTrue(mixedExist[2], "Third key should exist"); - assertFalse(mixedExist[3], "Fourth key should not exist"); assertEq(mixedResults[0], "eu-west-1", "First result should be region"); assertEq(mixedResults[1], "", "Second result should be empty"); assertEq(mixedResults[2], "50TB", "Third result should be storage"); @@ -1516,9 +1499,8 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { ); // Verify initial values - (bool oldExists, bytes memory oldValue) = - registry.getProductCapability(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "oldkey"); - assertTrue(oldExists, "Old key should exist initially"); + bytes memory oldValue = + registry.productCapabilities(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "oldkey"); assertEq(oldValue, "oldvalue", "Old key should have value initially"); (string[] memory newKeys, bytes[] memory newValues) = updatedPDPData.toCapabilities(2); @@ -1533,26 +1515,22 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { registry.updateProduct(ServiceProviderRegistryStorage.ProductType.PDP, newKeys, newValues); // Verify old key is cleared - (bool clearedExists, bytes memory clearedValue) = - registry.getProductCapability(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "oldkey"); - assertFalse(clearedExists, "Old key should not exist after update"); + bytes memory clearedValue = + registry.productCapabilities(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "oldkey"); assertEq(clearedValue, "", "Old key should be cleared after update"); // Verify new values are set - (bool regionExists, bytes memory newRegion) = - registry.getProductCapability(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "region"); - assertTrue(regionExists, "Region key should exist"); + bytes memory newRegion = + registry.productCapabilities(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "region"); assertEq(newRegion, "eu-central-1", "Region should be updated"); - (bool newKeyExists, bytes memory newKey) = - registry.getProductCapability(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "newkey"); - assertTrue(newKeyExists, "New key should exist"); + bytes memory newKey = + registry.productCapabilities(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "newkey"); assertEq(newKey, "newvalue", "New key should have value"); // Verify tier key is also cleared (was in initial but not in update) - (bool tierCleared, bytes memory clearedTier) = - registry.getProductCapability(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "tier"); - assertFalse(tierCleared, "Tier key should not exist after update"); + bytes memory clearedTier = + registry.productCapabilities(providerId, ServiceProviderRegistryStorage.ProductType.PDP, "tier"); assertEq(clearedTier, "", "Tier key should be cleared after update"); } } From f565a89c423d9b19def2e0ff652f6fca599c5167 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Thu, 23 Oct 2025 14:52:55 -0500 Subject: [PATCH 15/25] make ipni fields optional and document ipniPeerId --- service_contracts/src/ServiceProviderRegistry.sol | 8 +++++--- service_contracts/test/ProductTypes.t.sol | 3 +-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/service_contracts/src/ServiceProviderRegistry.sol b/service_contracts/src/ServiceProviderRegistry.sol index 6af4aa51..84ca9392 100644 --- a/service_contracts/src/ServiceProviderRegistry.sol +++ b/service_contracts/src/ServiceProviderRegistry.sol @@ -15,13 +15,15 @@ import {ServiceProviderRegistryStorage} from "./ServiceProviderRegistryStorage.s /// - serviceURL: the API endpoint /// - minPieceSizeInBytes: minimum piece size in bytes /// - maxPieceSizeInBytes: maximum piece size in bytes -/// - ipniPiece: Supports IPNI piece CID indexing -/// - ipniIpfs: Supports IPNI IPFS CID indexing /// - storagePricePerTibPerDay: Storage price per TiB per day (in token's smallest unit) /// - minProvingPeriodInEpochs: Minimum proving period in epochs /// - paymentTokenAddress: Token contract for payment (IERC20(address(0)) for FIL) +/// Optional PDP keys: +/// - ipniPiece: Supports IPNI piece CID indexing +/// - ipniIpfs: Supports IPNI IPFS CID indexing +/// - ipniPeerId: IPNI peer ID -uint256 constant REQUIRED_PDP_KEYS = 0x5b6e96f24dd05fa9218c80c8422a0eb70947d833531db3c4db14504405c0e132; +uint256 constant REQUIRED_PDP_KEYS = 0x5b6a06f24dd05729018c808802020eb60947d813531db3c45b14504401400102; /// @title ServiceProviderRegistry /// @notice A registry contract for managing service providers across the Filecoin Services ecosystem diff --git a/service_contracts/test/ProductTypes.t.sol b/service_contracts/test/ProductTypes.t.sol index 66dbee97..f8131861 100644 --- a/service_contracts/test/ProductTypes.t.sol +++ b/service_contracts/test/ProductTypes.t.sol @@ -9,8 +9,7 @@ contract ProductTypesTest is Test { function testPDPKeys() public pure { uint256 expected = ( BloomSet16.compressed("serviceURL") | BloomSet16.compressed("minPieceSizeInBytes") - | BloomSet16.compressed("maxPieceSizeInBytes") | BloomSet16.compressed("ipniPiece") - | BloomSet16.compressed("ipniIpfs") | BloomSet16.compressed("storagePricePerTibPerDay") + | BloomSet16.compressed("maxPieceSizeInBytes") | BloomSet16.compressed("storagePricePerTibPerDay") | BloomSet16.compressed("minProvingPeriodInEpochs") | BloomSet16.compressed("location") | BloomSet16.compressed("paymentTokenAddress") ); From 891e3b692439cb5f861ec4694243f3c114d0ac63 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Thu, 23 Oct 2025 16:04:37 -0500 Subject: [PATCH 16/25] getAllProductCapabilities, and add capability values to getActiveProvidersByProductType and getProvidersByProductType --- .../src/ServiceProviderRegistry.sol | 29 ++++++++-- .../src/ServiceProviderRegistryStorage.sol | 1 + .../test/ServiceProviderRegistryFull.t.sol | 56 ++++++++++++++++++- 3 files changed, 80 insertions(+), 6 deletions(-) diff --git a/service_contracts/src/ServiceProviderRegistry.sol b/service_contracts/src/ServiceProviderRegistry.sol index 84ca9392..6df83799 100644 --- a/service_contracts/src/ServiceProviderRegistry.sol +++ b/service_contracts/src/ServiceProviderRegistry.sol @@ -493,10 +493,12 @@ contract ServiceProviderRegistry is if (providerProducts[i][productType].isActive) { if (currentIndex >= offset && currentIndex < offset + limit) { ServiceProviderInfo storage provider = providers[i]; + ServiceProduct storage product = providerProducts[i][productType]; result.providers[resultIndex] = ProviderWithProduct({ providerId: i, providerInfo: provider, - product: providerProducts[i][productType] + product: product, + productCapabilityValues: getProductCapabilities(i, productType, product.capabilityKeys) }); resultIndex++; } @@ -540,10 +542,12 @@ contract ServiceProviderRegistry is if (providers[i].isActive && providerProducts[i][productType].isActive) { if (currentIndex >= offset && currentIndex < offset + limit) { ServiceProviderInfo storage provider = providers[i]; + ServiceProduct storage product = providerProducts[i][productType]; result.providers[resultIndex] = ProviderWithProduct({ providerId: i, providerInfo: provider, - product: providerProducts[i][productType] + product: product, + productCapabilityValues: getProductCapabilities(i, productType, product.capabilityKeys) }); resultIndex++; } @@ -709,8 +713,8 @@ contract ServiceProviderRegistry is /// @param productType The type of product /// @param keys Array of capability keys to query /// @return values Array of capability values corresponding to the keys (empty string for non-existent keys) - function getProductCapabilities(uint256 providerId, ProductType productType, string[] calldata keys) - external + function getProductCapabilities(uint256 providerId, ProductType productType, string[] memory keys) + public view providerExists(providerId) returns (bytes[] memory values) @@ -725,6 +729,23 @@ contract ServiceProviderRegistry is } } + function getAllProductCapabilities(uint256 providerId, ProductType productType) + external + view + providerExists(providerId) + returns (bool isActive, string[] memory keys, bytes[] memory values) + { + ServiceProduct storage product = providerProducts[providerId][productType]; + isActive = product.isActive; + keys = product.capabilityKeys; + values = new bytes[](keys.length); + + mapping(string => bytes) storage capabilities = productCapabilities[providerId][productType]; + for (uint256 i = 0; i < keys.length; i++) { + values[i] = capabilities[keys[i]]; + } + } + /// @notice Validate product data based on product type /// @param productType The type of product function _validateProductKeys(ProductType productType, string[] memory capabilityKeys) private pure { diff --git a/service_contracts/src/ServiceProviderRegistryStorage.sol b/service_contracts/src/ServiceProviderRegistryStorage.sol index 0f5b898b..e4cfb949 100644 --- a/service_contracts/src/ServiceProviderRegistryStorage.sol +++ b/service_contracts/src/ServiceProviderRegistryStorage.sol @@ -35,6 +35,7 @@ contract ServiceProviderRegistryStorage { uint256 providerId; ServiceProviderInfo providerInfo; ServiceProduct product; + bytes[] productCapabilityValues; } /// @notice Paginated result for provider queries diff --git a/service_contracts/test/ServiceProviderRegistryFull.t.sol b/service_contracts/test/ServiceProviderRegistryFull.t.sol index 6ba54efa..1fb1a57c 100644 --- a/service_contracts/test/ServiceProviderRegistryFull.t.sol +++ b/service_contracts/test/ServiceProviderRegistryFull.t.sol @@ -661,8 +661,25 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { registry.getProvidersByProductType(ServiceProviderRegistryStorage.ProductType.PDP, 0, 10); assertEq(result.providers.length, 3, "Should have 3 providers with PDP"); assertEq(result.providers[0].providerId, 1, "First provider should be ID 1"); + assertEq(result.providers[0].providerInfo.payee, provider1, "Unexpected provider payee"); + assertTrue(result.providers[0].product.isActive, "product should be active"); + assertEq(result.providers[0].product.capabilityKeys.length, keys.length, "capability key length mismatch "); + assertEq(result.providers[0].product.capabilityKeys[0], keys[0], "capability key mismatch "); + assertEq( + uint256(result.providers[0].product.productType), + uint256(ServiceProviderRegistryStorage.ProductType.PDP), + "unexpected product type" + ); + assertEq(result.providers[0].productCapabilityValues.length, values.length, "capability values length mismatch"); + assertEq( + result.providers[0].productCapabilityValues[0], + "https://provider1.example.com", + "incorrect capabilities value" + ); assertEq(result.providers[1].providerId, 2, "Second provider should be ID 2"); + assertEq(result.providers[1].providerInfo.payee, provider2, "Unexpected provider payee"); assertEq(result.providers[2].providerId, 3, "Third provider should be ID 3"); + assertEq(result.providers[2].providerInfo.payee, provider3, "Unexpected provider payee"); assertFalse(result.hasMore, "Should not have more results"); } @@ -711,7 +728,23 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { registry.getActiveProvidersByProductType(ServiceProviderRegistryStorage.ProductType.PDP, 0, 10); assertEq(activeResult.providers.length, 2, "Should have 2 active providers with PDP"); assertEq(activeResult.providers[0].providerId, 1, "First active should be ID 1"); + assertEq( + activeResult.providers[0].providerInfo.description, "Test provider description", "description mismatch" + ); + assertTrue(activeResult.providers[0].product.isActive, "should be active"); + assertEq( + activeResult.providers[0].product.capabilityKeys.length, keys.length, "capability keys length mismatch" + ); + assertEq( + activeResult.providers[1].productCapabilityValues.length, values.length, "capability values length mismatch" + ); assertEq(activeResult.providers[1].providerId, 3, "Second active should be ID 3"); + assertEq( + activeResult.providers[1].product.capabilityKeys.length, keys.length, "capability keys length mismatch" + ); + assertEq( + activeResult.providers[1].productCapabilityValues.length, values.length, "capability values length mismatch" + ); assertFalse(activeResult.hasMore, "Should not have more results"); } @@ -738,7 +771,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { (string[] memory keys, bytes[] memory values) = defaultPDPData.toCapabilities(); vm.prank(provider1); - registry.registerProvider{value: REGISTRATION_FEE}( + uint256 providerId = registry.registerProvider{value: REGISTRATION_FEE}( provider1, // payee "", "Test provider description", @@ -748,7 +781,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { ); (string[] memory getProductKeys, bool isActive) = - registry.getProduct(1, ServiceProviderRegistryStorage.ProductType.PDP); + registry.getProduct(providerId, ServiceProviderRegistryStorage.ProductType.PDP); assertTrue(isActive, "Product should be active"); bytes[] memory getProductCapabilities = registry.getProductCapabilities(1, ServiceProviderRegistryStorage.ProductType.PDP, getProductKeys); @@ -756,6 +789,25 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { // Decode and verify PDPOffering.Schema memory decoded = PDPOffering.fromCapabilities(getProductKeys, getProductCapabilities); assertEq(decoded.serviceURL, SERVICE_URL, "Service URL should match"); + + // compare to getAllProductCapabilities + ( + bool getAllProductCapabilitiesIsActive, + string[] memory getAllProductCapabilitiesKeys, + bytes[] memory getAllProductCapabilitiesValues + ) = registry.getAllProductCapabilities(1, ServiceProviderRegistryStorage.ProductType.PDP); + assertTrue(getAllProductCapabilitiesIsActive, "Product should be active"); + assertEq( + getAllProductCapabilitiesKeys.length, + getAllProductCapabilitiesValues.length, + "keys and values length mismatch" + ); + assertEq(getProductKeys.length, getAllProductCapabilitiesKeys.length, "key length mismatch"); + assertEq(getProductCapabilities.length, getAllProductCapabilitiesValues.length, "key length mismatch"); + for (uint256 i = 0; i < getProductCapabilities.length; i++) { + assertEq(getProductKeys[i], getAllProductCapabilitiesKeys[i], "key length mismatch"); + assertEq(getProductCapabilities[i], getAllProductCapabilitiesValues[i], "key length mismatch"); + } } function testCannotAddProductTwice() public { From 7a572f10f546d0dd2b5ffbec5c8152c8aaad53b1 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Thu, 23 Oct 2025 16:13:51 -0500 Subject: [PATCH 17/25] make update-abi --- .../abi/ServiceProviderRegistry.abi.json | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/service_contracts/abi/ServiceProviderRegistry.abi.json b/service_contracts/abi/ServiceProviderRegistry.abi.json index 4ad76d7d..1e5aa65b 100644 --- a/service_contracts/abi/ServiceProviderRegistry.abi.json +++ b/service_contracts/abi/ServiceProviderRegistry.abi.json @@ -288,6 +288,11 @@ "internalType": "bool" } ] + }, + { + "name": "productCapabilityValues", + "type": "bytes[]", + "internalType": "bytes[]" } ] }, @@ -330,6 +335,40 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "getAllProductCapabilities", + "inputs": [ + { + "name": "providerId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "productType", + "type": "uint8", + "internalType": "enum ServiceProviderRegistryStorage.ProductType" + } + ], + "outputs": [ + { + "name": "isActive", + "type": "bool", + "internalType": "bool" + }, + { + "name": "keys", + "type": "string[]", + "internalType": "string[]" + }, + { + "name": "values", + "type": "bytes[]", + "internalType": "bytes[]" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "getNextProviderId", @@ -720,6 +759,11 @@ "internalType": "bool" } ] + }, + { + "name": "productCapabilityValues", + "type": "bytes[]", + "internalType": "bytes[]" } ] }, From 5a3224cb5ff022fcc0ed2203debec88c32e2a1d3 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Thu, 23 Oct 2025 16:47:59 -0500 Subject: [PATCH 18/25] else if --- service_contracts/test/PDPOffering.sol | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/service_contracts/test/PDPOffering.sol b/service_contracts/test/PDPOffering.sol index 8f9bc222..f993a23e 100644 --- a/service_contracts/test/PDPOffering.sol +++ b/service_contracts/test/PDPOffering.sol @@ -31,26 +31,19 @@ library PDPOffering { schema.serviceURL = string(values[i]); } else if (hash == keccak256("minPieceSizeInBytes")) { schema.minPieceSizeInBytes = abi.decode(values[i], (uint256)); - } - if (hash == keccak256("maxPieceSizeInBytes")) { + } else if (hash == keccak256("maxPieceSizeInBytes")) { schema.maxPieceSizeInBytes = abi.decode(values[i], (uint256)); - } - if (hash == keccak256("ipniPiece")) { + } else if (hash == keccak256("ipniPiece")) { schema.ipniPiece = abi.decode(values[i], (bool)); - } - if (hash == keccak256("ipniIpfs")) { + } else if (hash == keccak256("ipniIpfs")) { schema.ipniIpfs = abi.decode(values[i], (bool)); - } - if (hash == keccak256("storagePricePerTibPerDay")) { + } else if (hash == keccak256("storagePricePerTibPerDay")) { schema.storagePricePerTibPerDay = abi.decode(values[i], (uint256)); - } - if (hash == keccak256("minProvingPeriodInEpochs")) { + } else if (hash == keccak256("minProvingPeriodInEpochs")) { schema.minProvingPeriodInEpochs = abi.decode(values[i], (uint256)); - } - if (hash == keccak256("location")) { + } else if (hash == keccak256("location")) { schema.location = string(values[i]); - } - if (hash == keccak256("paymentTokenAddress")) { + } else if (hash == keccak256("paymentTokenAddress")) { schema.paymentTokenAddress = abi.decode(values[i], (IERC20)); } } From 2709451c3aa218b2343d2553c4860a18b3147f65 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Thu, 23 Oct 2025 20:55:47 -0500 Subject: [PATCH 19/25] doc getAllProductCapabilities --- service_contracts/src/ServiceProviderRegistry.sol | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/service_contracts/src/ServiceProviderRegistry.sol b/service_contracts/src/ServiceProviderRegistry.sol index 6df83799..ab751a20 100644 --- a/service_contracts/src/ServiceProviderRegistry.sol +++ b/service_contracts/src/ServiceProviderRegistry.sol @@ -729,6 +729,12 @@ contract ServiceProviderRegistry is } } + /// @notice Get all capability keys and values for a product + /// @param providerId The ID of the provider + /// @param productType The type of product + /// @return isActive Whether the product is active + /// @return keys Array of all capability keys of the product + /// @return values Array of capability values corresponding to the keys function getAllProductCapabilities(uint256 providerId, ProductType productType) external view From 99d9cf9887d467ab5b6ab6d50fad52132b51cde2 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Thu, 23 Oct 2025 21:03:53 -0500 Subject: [PATCH 20/25] ban empty capability value --- .../src/ServiceProviderRegistry.sol | 1 + .../test/ServiceProviderRegistryFull.t.sol | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/service_contracts/src/ServiceProviderRegistry.sol b/service_contracts/src/ServiceProviderRegistry.sol index ab751a20..792752ba 100644 --- a/service_contracts/src/ServiceProviderRegistry.sol +++ b/service_contracts/src/ServiceProviderRegistry.sol @@ -782,6 +782,7 @@ contract ServiceProviderRegistry is for (uint256 i = 0; i < keys.length; i++) { require(bytes(keys[i]).length > 0, "Capability key cannot be empty"); require(bytes(keys[i]).length <= MAX_CAPABILITY_KEY_LENGTH, "Capability key too long"); + require(values[i].length > 0, "Capability value cannot be empty"); require(values[i].length <= MAX_CAPABILITY_VALUE_LENGTH, "Capability value too long"); } } diff --git a/service_contracts/test/ServiceProviderRegistryFull.t.sol b/service_contracts/test/ServiceProviderRegistryFull.t.sol index 1fb1a57c..a6168e23 100644 --- a/service_contracts/test/ServiceProviderRegistryFull.t.sol +++ b/service_contracts/test/ServiceProviderRegistryFull.t.sol @@ -1329,6 +1329,25 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { ); } + function testEmptyCapabilityValue() public { + (string[] memory capKeys, bytes[] memory capValues) = updatedPDPData.toCapabilities(1); + capKeys[0] = "key"; + + capValues[0] = ""; + + vm.prank(provider1); + vm.expectRevert("Capability value cannot be empty"); + registry.registerProvider{value: REGISTRATION_FEE}( + provider1, // payee + "", + "Test provider description", + ServiceProviderRegistryStorage.ProductType.PDP, + capKeys, + capValues + ); + } + + function testTooManyCapabilities() public { (string[] memory capKeys, bytes[] memory capValues) = defaultPDPData.toCapabilities(25); From efc977358d6b895cbb5b38506f662cdaf7320222 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Thu, 23 Oct 2025 21:05:58 -0500 Subject: [PATCH 21/25] forge lint --- service_contracts/test/ServiceProviderRegistryFull.t.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/service_contracts/test/ServiceProviderRegistryFull.t.sol b/service_contracts/test/ServiceProviderRegistryFull.t.sol index a6168e23..d66e083d 100644 --- a/service_contracts/test/ServiceProviderRegistryFull.t.sol +++ b/service_contracts/test/ServiceProviderRegistryFull.t.sol @@ -1347,7 +1347,6 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { ); } - function testTooManyCapabilities() public { (string[] memory capKeys, bytes[] memory capValues) = defaultPDPData.toCapabilities(25); From a4d006256a8965bd13c28dfb05a07f911eaecf1e Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Sat, 25 Oct 2025 02:20:10 +1100 Subject: [PATCH 22/25] feat(registry): provide more useful accessors (#328) --- .../abi/ServiceProviderRegistry.abi.json | 231 +++++++----------- .../src/ServiceProviderRegistry.sol | 87 ++----- service_contracts/test/PDPOffering.sol | 9 +- .../test/ServiceProviderRegistry.t.sol | 16 +- .../test/ServiceProviderRegistryFull.t.sol | 50 ++-- 5 files changed, 166 insertions(+), 227 deletions(-) diff --git a/service_contracts/abi/ServiceProviderRegistry.abi.json b/service_contracts/abi/ServiceProviderRegistry.abi.json index 1e5aa65b..b605b3f4 100644 --- a/service_contracts/abi/ServiceProviderRegistry.abi.json +++ b/service_contracts/abi/ServiceProviderRegistry.abi.json @@ -199,113 +199,6 @@ ], "stateMutability": "view" }, - { - "type": "function", - "name": "getActiveProvidersByProductType", - "inputs": [ - { - "name": "productType", - "type": "uint8", - "internalType": "enum ServiceProviderRegistryStorage.ProductType" - }, - { - "name": "offset", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "limit", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "result", - "type": "tuple", - "internalType": "struct ServiceProviderRegistryStorage.PaginatedProviders", - "components": [ - { - "name": "providers", - "type": "tuple[]", - "internalType": "struct ServiceProviderRegistryStorage.ProviderWithProduct[]", - "components": [ - { - "name": "providerId", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "providerInfo", - "type": "tuple", - "internalType": "struct ServiceProviderRegistryStorage.ServiceProviderInfo", - "components": [ - { - "name": "serviceProvider", - "type": "address", - "internalType": "address" - }, - { - "name": "payee", - "type": "address", - "internalType": "address" - }, - { - "name": "name", - "type": "string", - "internalType": "string" - }, - { - "name": "description", - "type": "string", - "internalType": "string" - }, - { - "name": "isActive", - "type": "bool", - "internalType": "bool" - } - ] - }, - { - "name": "product", - "type": "tuple", - "internalType": "struct ServiceProviderRegistryStorage.ServiceProduct", - "components": [ - { - "name": "productType", - "type": "uint8", - "internalType": "enum ServiceProviderRegistryStorage.ProductType" - }, - { - "name": "capabilityKeys", - "type": "string[]", - "internalType": "string[]" - }, - { - "name": "isActive", - "type": "bool", - "internalType": "bool" - } - ] - }, - { - "name": "productCapabilityValues", - "type": "bytes[]", - "internalType": "bytes[]" - } - ] - }, - { - "name": "hasMore", - "type": "bool", - "internalType": "bool" - } - ] - } - ], - "stateMutability": "view" - }, { "type": "function", "name": "getAllActiveProviders", @@ -382,35 +275,6 @@ ], "stateMutability": "view" }, - { - "type": "function", - "name": "getProduct", - "inputs": [ - { - "name": "providerId", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "productType", - "type": "uint8", - "internalType": "enum ServiceProviderRegistryStorage.ProductType" - } - ], - "outputs": [ - { - "name": "capabilityKeys", - "type": "string[]", - "internalType": "string[]" - }, - { - "name": "isActive", - "type": "bool", - "internalType": "bool" - } - ], - "stateMutability": "view" - }, { "type": "function", "name": "getProductCapabilities", @@ -607,6 +471,96 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "getProviderWithProduct", + "inputs": [ + { + "name": "providerId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "productType", + "type": "uint8", + "internalType": "enum ServiceProviderRegistryStorage.ProductType" + } + ], + "outputs": [ + { + "name": "", + "type": "tuple", + "internalType": "struct ServiceProviderRegistryStorage.ProviderWithProduct", + "components": [ + { + "name": "providerId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "providerInfo", + "type": "tuple", + "internalType": "struct ServiceProviderRegistryStorage.ServiceProviderInfo", + "components": [ + { + "name": "serviceProvider", + "type": "address", + "internalType": "address" + }, + { + "name": "payee", + "type": "address", + "internalType": "address" + }, + { + "name": "name", + "type": "string", + "internalType": "string" + }, + { + "name": "description", + "type": "string", + "internalType": "string" + }, + { + "name": "isActive", + "type": "bool", + "internalType": "bool" + } + ] + }, + { + "name": "product", + "type": "tuple", + "internalType": "struct ServiceProviderRegistryStorage.ServiceProduct", + "components": [ + { + "name": "productType", + "type": "uint8", + "internalType": "enum ServiceProviderRegistryStorage.ProductType" + }, + { + "name": "capabilityKeys", + "type": "string[]", + "internalType": "string[]" + }, + { + "name": "isActive", + "type": "bool", + "internalType": "bool" + } + ] + }, + { + "name": "productCapabilityValues", + "type": "bytes[]", + "internalType": "bytes[]" + } + ] + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "getProvidersByIds", @@ -679,6 +633,11 @@ "type": "uint8", "internalType": "enum ServiceProviderRegistryStorage.ProductType" }, + { + "name": "onlyActive", + "type": "bool", + "internalType": "bool" + }, { "name": "offset", "type": "uint256", diff --git a/service_contracts/src/ServiceProviderRegistry.sol b/service_contracts/src/ServiceProviderRegistry.sol index 792752ba..943f5339 100644 --- a/service_contracts/src/ServiceProviderRegistry.sol +++ b/service_contracts/src/ServiceProviderRegistry.sol @@ -11,18 +11,20 @@ import {BloomSet16} from "./lib/BloomSet.sol"; import {Errors} from "./Errors.sol"; import {ServiceProviderRegistryStorage} from "./ServiceProviderRegistryStorage.sol"; -/// @dev Required PDP Keys: +/// @dev Required PDP Keys (validated by REQUIRED_PDP_KEYS Bloom filter): /// - serviceURL: the API endpoint /// - minPieceSizeInBytes: minimum piece size in bytes /// - maxPieceSizeInBytes: maximum piece size in bytes /// - storagePricePerTibPerDay: Storage price per TiB per day (in token's smallest unit) /// - minProvingPeriodInEpochs: Minimum proving period in epochs +/// - location: Geographic location of the service provider /// - paymentTokenAddress: Token contract for payment (IERC20(address(0)) for FIL) -/// Optional PDP keys: +/// Optional PDP keys (not validated by Bloom filter): /// - ipniPiece: Supports IPNI piece CID indexing /// - ipniIpfs: Supports IPNI IPFS CID indexing /// - ipniPeerId: IPNI peer ID +// Bloom filter representing the required keys for PDP uint256 constant REQUIRED_PDP_KEYS = 0x5b6a06f24dd05729018c808802020eb60947d813531db3c45b14504401400102; /// @title ServiceProviderRegistry @@ -443,32 +445,40 @@ contract ServiceProviderRegistry is return providers[providerId].payee; } - /// @notice Get product data for a specific product type + /// @notice Get complete provider and product information /// @param providerId The ID of the provider /// @param productType The type of product to retrieve - /// @return capabilityKeys Array of capability keys - /// @return isActive Whether the product is active - function getProduct(uint256 providerId, ProductType productType) + /// @return Complete provider with product information + function getProviderWithProduct(uint256 providerId, ProductType productType) external view providerExists(providerId) - returns (string[] memory capabilityKeys, bool isActive) + returns (ProviderWithProduct memory) { - ServiceProduct memory product = providerProducts[providerId][productType]; - return (product.capabilityKeys, product.isActive); + ServiceProviderInfo storage provider = providers[providerId]; + ServiceProduct storage product = providerProducts[providerId][productType]; + + return ProviderWithProduct({ + providerId: providerId, + providerInfo: provider, + product: product, + productCapabilityValues: getProductCapabilities(providerId, productType, product.capabilityKeys) + }); } - /// @notice Get all providers that offer a specific product type with pagination + /// @notice Get providers that offer a specific product type with pagination /// @param productType The product type to filter by + /// @param onlyActive If true, return only active providers with active products /// @param offset Starting index for pagination (0-based) /// @param limit Maximum number of results to return /// @return result Paginated result containing provider details and hasMore flag - function getProvidersByProductType(ProductType productType, uint256 offset, uint256 limit) + function getProvidersByProductType(ProductType productType, bool onlyActive, uint256 offset, uint256 limit) external view returns (PaginatedProviders memory result) { - uint256 totalCount = productTypeProviderCount[productType]; + uint256 totalCount = + onlyActive ? activeProductTypeProviderCount[productType] : productTypeProviderCount[productType]; // Handle edge cases if (offset >= totalCount || limit == 0) { @@ -490,56 +500,11 @@ contract ServiceProviderRegistry is uint256 resultIndex = 0; for (uint256 i = 1; i <= numProviders && resultIndex < limit; i++) { - if (providerProducts[i][productType].isActive) { - if (currentIndex >= offset && currentIndex < offset + limit) { - ServiceProviderInfo storage provider = providers[i]; - ServiceProduct storage product = providerProducts[i][productType]; - result.providers[resultIndex] = ProviderWithProduct({ - providerId: i, - providerInfo: provider, - product: product, - productCapabilityValues: getProductCapabilities(i, productType, product.capabilityKeys) - }); - resultIndex++; - } - currentIndex++; - } - } - } - - /// @notice Get all active providers that offer a specific product type with pagination - /// @param productType The product type to filter by - /// @param offset Starting index for pagination (0-based) - /// @param limit Maximum number of results to return - /// @return result Paginated result containing provider details and hasMore flag - function getActiveProvidersByProductType(ProductType productType, uint256 offset, uint256 limit) - external - view - returns (PaginatedProviders memory result) - { - uint256 totalCount = activeProductTypeProviderCount[productType]; - - // Handle edge cases - if (offset >= totalCount || limit == 0) { - result.providers = new ProviderWithProduct[](0); - result.hasMore = false; - return result; - } + bool matches = onlyActive + ? (providers[i].isActive && providerProducts[i][productType].isActive) + : providerProducts[i][productType].isActive; - // Calculate actual items to return - if (offset + limit > totalCount) { - limit = totalCount - offset; - } - - result.providers = new ProviderWithProduct[](limit); - result.hasMore = (offset + limit) < totalCount; - - // Collect active providers - uint256 currentIndex = 0; - uint256 resultIndex = 0; - - for (uint256 i = 1; i <= numProviders && resultIndex < limit; i++) { - if (providers[i].isActive && providerProducts[i][productType].isActive) { + if (matches) { if (currentIndex >= offset && currentIndex < offset + limit) { ServiceProviderInfo storage provider = providers[i]; ServiceProduct storage product = providerProducts[i][productType]; diff --git a/service_contracts/test/PDPOffering.sol b/service_contracts/test/PDPOffering.sol index f993a23e..06e347a7 100644 --- a/service_contracts/test/PDPOffering.sol +++ b/service_contracts/test/PDPOffering.sol @@ -86,9 +86,10 @@ library PDPOffering { view returns (Schema memory schema, string[] memory keys, bool isActive) { - (keys, isActive) = registry.getProduct(providerId, ServiceProviderRegistryStorage.ProductType.PDP); - schema = fromCapabilities( - keys, registry.getProductCapabilities(providerId, ServiceProviderRegistryStorage.ProductType.PDP, keys) - ); + ServiceProviderRegistryStorage.ProviderWithProduct memory providerWithProduct = + registry.getProviderWithProduct(providerId, ServiceProviderRegistryStorage.ProductType.PDP); + keys = providerWithProduct.product.capabilityKeys; + isActive = providerWithProduct.product.isActive; + schema = fromCapabilities(keys, providerWithProduct.productCapabilityValues); } } diff --git a/service_contracts/test/ServiceProviderRegistry.t.sol b/service_contracts/test/ServiceProviderRegistry.t.sol index 90fd083b..e9fcd68d 100644 --- a/service_contracts/test/ServiceProviderRegistry.t.sol +++ b/service_contracts/test/ServiceProviderRegistry.t.sol @@ -99,14 +99,18 @@ contract ServiceProviderRegistryTest is MockFVMTest { assertTrue(registry.isRegisteredProvider(user1), "Should be registered"); // Verify capabilities were stored correctly - (string[] memory returnedKeys,) = - registry.getProduct(providerId, ServiceProviderRegistryStorage.ProductType.PDP); + ServiceProviderRegistryStorage.ProviderWithProduct memory providerWithProduct = + registry.getProviderWithProduct(providerId, ServiceProviderRegistryStorage.ProductType.PDP); - assertEq(returnedKeys.length, capabilityKeys.length, "Should have expected capability keys count"); + assertEq( + providerWithProduct.product.capabilityKeys.length, + capabilityKeys.length, + "Should have expected capability keys count" + ); - assertEq(returnedKeys[0], "region", "First key should be region"); - assertEq(returnedKeys[1], "tier", "Second key should be tier"); - assertEq(returnedKeys[2], "compliance", "Third key should be compliance"); + assertEq(providerWithProduct.product.capabilityKeys[0], "region", "First key should be region"); + assertEq(providerWithProduct.product.capabilityKeys[1], "tier", "Second key should be tier"); + assertEq(providerWithProduct.product.capabilityKeys[2], "compliance", "Third key should be compliance"); // Use the new query methods to verify values bytes memory region = diff --git a/service_contracts/test/ServiceProviderRegistryFull.t.sol b/service_contracts/test/ServiceProviderRegistryFull.t.sol index d66e083d..97af4ad0 100644 --- a/service_contracts/test/ServiceProviderRegistryFull.t.sol +++ b/service_contracts/test/ServiceProviderRegistryFull.t.sol @@ -194,12 +194,14 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { assertEq(values[2], "low", "Third value should be low"); assertEq(values[3], "ISO27001", "Fourth value should be ISO27001"); - // Also verify using getProduct - (string[] memory productKeys, bool productActive) = - registry.getProduct(providerId, ServiceProviderRegistryStorage.ProductType.PDP); - assertTrue(productActive, "Product should be active"); - assertEq(productKeys.length, capKeys.length, "Product should have 4 capability keys"); - assertEq(productKeys[0], "datacenter", "Product first key should be datacenter"); + // Also verify using getProviderWithProduct + ServiceProviderRegistryStorage.ProviderWithProduct memory providerWithProduct = + registry.getProviderWithProduct(providerId, ServiceProviderRegistryStorage.ProductType.PDP); + assertTrue(providerWithProduct.product.isActive, "Product should be active"); + assertEq( + providerWithProduct.product.capabilityKeys.length, capKeys.length, "Product should have 4 capability keys" + ); + assertEq(providerWithProduct.product.capabilityKeys[0], "datacenter", "Product first key should be datacenter"); // Verify value using direct mapping access bytes memory datacenterValue = @@ -658,7 +660,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { // Get providers by product type with pagination ServiceProviderRegistryStorage.PaginatedProviders memory result = - registry.getProvidersByProductType(ServiceProviderRegistryStorage.ProductType.PDP, 0, 10); + registry.getProvidersByProductType(ServiceProviderRegistryStorage.ProductType.PDP, false, 0, 10); assertEq(result.providers.length, 3, "Should have 3 providers with PDP"); assertEq(result.providers[0].providerId, 1, "First provider should be ID 1"); assertEq(result.providers[0].providerInfo.payee, provider1, "Unexpected provider payee"); @@ -725,7 +727,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { // Get active providers by product type with pagination ServiceProviderRegistryStorage.PaginatedProviders memory activeResult = - registry.getActiveProvidersByProductType(ServiceProviderRegistryStorage.ProductType.PDP, 0, 10); + registry.getProvidersByProductType(ServiceProviderRegistryStorage.ProductType.PDP, true, 0, 10); assertEq(activeResult.providers.length, 2, "Should have 2 active providers with PDP"); assertEq(activeResult.providers[0].providerId, 1, "First active should be ID 1"); assertEq( @@ -780,11 +782,11 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { values ); - (string[] memory getProductKeys, bool isActive) = - registry.getProduct(providerId, ServiceProviderRegistryStorage.ProductType.PDP); - assertTrue(isActive, "Product should be active"); - bytes[] memory getProductCapabilities = - registry.getProductCapabilities(1, ServiceProviderRegistryStorage.ProductType.PDP, getProductKeys); + ServiceProviderRegistryStorage.ProviderWithProduct memory providerWithProduct = + registry.getProviderWithProduct(providerId, ServiceProviderRegistryStorage.ProductType.PDP); + assertTrue(providerWithProduct.product.isActive, "Product should be active"); + string[] memory getProductKeys = providerWithProduct.product.capabilityKeys; + bytes[] memory getProductCapabilities = providerWithProduct.productCapabilityValues; // Decode and verify PDPOffering.Schema memory decoded = PDPOffering.fromCapabilities(getProductKeys, getProductCapabilities); @@ -959,7 +961,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { registry.getProvider(1); vm.expectRevert("Provider does not exist"); - registry.getProduct(1, ServiceProviderRegistryStorage.ProductType.PDP); + registry.getProviderWithProduct(1, ServiceProviderRegistryStorage.ProductType.PDP); vm.expectRevert("Provider does not exist"); registry.isProviderActive(1); @@ -1181,8 +1183,10 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { ); // Get the product and verify capabilities - (string[] memory returnedKeys, bool isActive) = - registry.getProduct(providerId, ServiceProviderRegistryStorage.ProductType.PDP); + ServiceProviderRegistryStorage.ProviderWithProduct memory providerWithProduct = + registry.getProviderWithProduct(providerId, ServiceProviderRegistryStorage.ProductType.PDP); + string[] memory returnedKeys = providerWithProduct.product.capabilityKeys; + bool isActive = providerWithProduct.product.isActive; assertEq(returnedKeys.length, capKeys.length, "Should have same number of capability keys"); assertEq(returnedKeys[0], "region", "First key should be region"); @@ -1224,7 +1228,9 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { registry.updateProduct(ServiceProviderRegistryStorage.ProductType.PDP, updatedKeys, updatedValues); // Verify capabilities updated - (string[] memory returnedKeys,) = registry.getProduct(1, ServiceProviderRegistryStorage.ProductType.PDP); + ServiceProviderRegistryStorage.ProviderWithProduct memory updatedProviderWithProduct = + registry.getProviderWithProduct(1, ServiceProviderRegistryStorage.ProductType.PDP); + string[] memory returnedKeys = updatedProviderWithProduct.product.capabilityKeys; assertEq(returnedKeys.length, updatedKeys.length, "Should have 2 capability keys"); assertEq(returnedKeys[0], "support", "First key should be support"); @@ -1390,9 +1396,13 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { assertEq(providerId, 1, "Should register successfully with 10 capabilities"); // Verify all 10 capabilities were stored - (string[] memory returnedKeys,) = - registry.getProduct(providerId, ServiceProviderRegistryStorage.ProductType.PDP); - assertEq(returnedKeys.length, capKeys.length, "Should have the same number of keys"); + ServiceProviderRegistryStorage.ProviderWithProduct memory maxCapProviderWithProduct = + registry.getProviderWithProduct(providerId, ServiceProviderRegistryStorage.ProductType.PDP); + assertEq( + maxCapProviderWithProduct.product.capabilityKeys.length, + capKeys.length, + "Should have the same number of keys" + ); } // ========== New Capability Query Methods Tests ========== From 1628fde4f7c30c0799d07a81f4820a420240b5b2 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Fri, 24 Oct 2025 13:49:30 -0500 Subject: [PATCH 23/25] Update service_contracts/src/lib/BloomSet.sol Co-authored-by: Jakub Sztandera --- service_contracts/src/lib/BloomSet.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service_contracts/src/lib/BloomSet.sol b/service_contracts/src/lib/BloomSet.sol index 0a091690..247c1245 100644 --- a/service_contracts/src/lib/BloomSet.sol +++ b/service_contracts/src/lib/BloomSet.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.30; /// @notice Bloom Filter with fixed params: /// @notice k = 16 bits per item /// @notice m = 256 bits per set -/// @dev probability of false positives by number of items: (via https://hur.st/bloomfilter/?m=256&k=32) +/// @dev probability of false positives by number of items: (via https://hur.st/bloomfilter/?m=256&k=16) /// @dev 7: 0.000000062 /// @dev 8: 0.00000033 /// @dev 9: 0.000001377 From 3c2845157cd807d78df94d4e9b0b7e61957032f4 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Fri, 24 Oct 2025 17:10:10 -0500 Subject: [PATCH 24/25] BigEndian library --- service_contracts/src/lib/BigEndian.sol | 27 ++++++++++ service_contracts/test/BigEndian.t.sol | 71 +++++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 service_contracts/src/lib/BigEndian.sol create mode 100644 service_contracts/test/BigEndian.t.sol diff --git a/service_contracts/src/lib/BigEndian.sol b/service_contracts/src/lib/BigEndian.sol new file mode 100644 index 00000000..5aaa81da --- /dev/null +++ b/service_contracts/src/lib/BigEndian.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT +pragma solidity ^0.8.30; + +library BigEndian { + function encode(uint256 decoded) internal pure returns (bytes memory encoded) { + assembly ("memory-safe") { + // binary search + let size := shl(7, lt(0xffffffffffffffffffffffffffffffff, decoded)) + size := or(size, shl(6, lt(0xffffffffffffffff, shr(size, decoded)))) + size := or(size, shl(5, lt(0xffffffff, shr(size, decoded)))) + size := or(size, shl(4, lt(0xffff, shr(size, decoded)))) + size := or(size, shl(3, lt(0xff, shr(size, decoded)))) + size := add(shr(3, size), lt(0, shr(size, decoded))) + + encoded := mload(0x40) + mstore(add(size, encoded), decoded) + mstore(encoded, size) + mstore(0x40, add(64, encoded)) + } + } + + function decode(bytes memory encoded) internal pure returns (uint256 decoded) { + assembly ("memory-safe") { + decoded := shr(shl(3, sub(0x20, mload(encoded))), mload(add(encoded, 0x20))) + } + } +} diff --git a/service_contracts/test/BigEndian.t.sol b/service_contracts/test/BigEndian.t.sol new file mode 100644 index 00000000..85d5d4d8 --- /dev/null +++ b/service_contracts/test/BigEndian.t.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT +pragma solidity ^0.8.30; + +import {BigEndian} from "../src/lib/BigEndian.sol"; +import {Test} from "forge-std/Test.sol"; + +contract BigEndianTest is Test { + using BigEndian for bytes; + + function test_decode() public pure { + bytes memory test; + + test = hex""; + assertEq(test.decode(), 0); + test = hex"00"; + assertEq(test.decode(), 0); + test = hex"01"; + assertEq(test.decode(), 1); + test = hex"11"; + assertEq(test.decode(), 17); + test = hex"1100"; + assertEq(test.decode(), 4352); + test = hex"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; + assertEq(test.decode(), 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); + } + + using BigEndian for uint256; + + function test_encode() public pure { + uint256 test; + bytes memory encoded; + + test = 0; + encoded = test.encode(); + assertEq(encoded.length, 0); + assertEq(encoded.decode(), test); + + test = 1; + encoded = test.encode(); + assertEq(encoded.length, 1); + assertEq(encoded.decode(), test); + + test = 2; + encoded = test.encode(); + assertEq(encoded.length, 1); + assertEq(encoded.decode(), test); + + test = 1; + uint256 expectedSize = 0; + while (++expectedSize <= 32) { + for (uint256 i = 0; i < 4; i++) { + encoded = test.encode(); + assertEq(encoded.length, expectedSize); + assertEq(encoded.decode(), test); + test <<= 1; + } + + encoded = test.encode(); + assertEq(encoded.length, expectedSize); + assertEq(encoded.decode(), test); + test ^= expectedSize; + + for (uint256 i = 0; i < 4; i++) { + encoded = test.encode(); + assertEq(encoded.length, expectedSize); + assertEq(encoded.decode(), test); + test <<= 1; + } + } + } +} From 94eed74e000840911b735a48bef0adaeaf8e3518 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Fri, 24 Oct 2025 17:38:31 -0500 Subject: [PATCH 25/25] use big endian encoding in test --- service_contracts/test/PDPOffering.sol | 52 +++++++++++-------- .../test/ServiceProviderRegistryFull.t.sol | 4 +- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/service_contracts/test/PDPOffering.sol b/service_contracts/test/PDPOffering.sol index 06e347a7..461ff5ea 100644 --- a/service_contracts/test/PDPOffering.sol +++ b/service_contracts/test/PDPOffering.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.30; +import {BigEndian} from "../src/lib/BigEndian.sol"; import {ServiceProviderRegistry} from "../src/ServiceProviderRegistry.sol"; import {ServiceProviderRegistryStorage} from "../src/ServiceProviderRegistryStorage.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; @@ -30,21 +31,21 @@ library PDPOffering { if (hash == keccak256("serviceURL")) { schema.serviceURL = string(values[i]); } else if (hash == keccak256("minPieceSizeInBytes")) { - schema.minPieceSizeInBytes = abi.decode(values[i], (uint256)); + schema.minPieceSizeInBytes = BigEndian.decode(values[i]); } else if (hash == keccak256("maxPieceSizeInBytes")) { - schema.maxPieceSizeInBytes = abi.decode(values[i], (uint256)); + schema.maxPieceSizeInBytes = BigEndian.decode(values[i]); } else if (hash == keccak256("ipniPiece")) { - schema.ipniPiece = abi.decode(values[i], (bool)); + schema.ipniPiece = BigEndian.decode(values[i]) > 0; } else if (hash == keccak256("ipniIpfs")) { - schema.ipniIpfs = abi.decode(values[i], (bool)); + schema.ipniIpfs = BigEndian.decode(values[i]) > 0; } else if (hash == keccak256("storagePricePerTibPerDay")) { - schema.storagePricePerTibPerDay = abi.decode(values[i], (uint256)); + schema.storagePricePerTibPerDay = BigEndian.decode(values[i]); } else if (hash == keccak256("minProvingPeriodInEpochs")) { - schema.minProvingPeriodInEpochs = abi.decode(values[i], (uint256)); + schema.minProvingPeriodInEpochs = BigEndian.decode(values[i]); } else if (hash == keccak256("location")) { schema.location = string(values[i]); } else if (hash == keccak256("paymentTokenAddress")) { - schema.paymentTokenAddress = abi.decode(values[i], (IERC20)); + schema.paymentTokenAddress = IERC20(address(uint160(BigEndian.decode(values[i])))); } } return schema; @@ -55,26 +56,31 @@ library PDPOffering { pure returns (string[] memory keys, bytes[] memory values) { - keys = new string[](9 + extraSize); - values = new bytes[](9 + extraSize); + uint256 normalSize = 7 + (schema.ipniPiece ? 1 : 0) + (schema.ipniIpfs ? 1 : 0); + keys = new string[](normalSize + extraSize); + values = new bytes[](normalSize + extraSize); keys[extraSize] = "serviceURL"; values[extraSize] = bytes(schema.serviceURL); keys[extraSize + 1] = "minPieceSizeInBytes"; - values[extraSize + 1] = abi.encode(schema.minPieceSizeInBytes); + values[extraSize + 1] = BigEndian.encode(schema.minPieceSizeInBytes); keys[extraSize + 2] = "maxPieceSizeInBytes"; - values[extraSize + 2] = abi.encode(schema.maxPieceSizeInBytes); - keys[extraSize + 3] = "ipniPiece"; - values[extraSize + 3] = abi.encode(schema.ipniPiece); - keys[extraSize + 4] = "ipniIpfs"; - values[extraSize + 4] = abi.encode(schema.ipniIpfs); - keys[extraSize + 5] = "storagePricePerTibPerDay"; - values[extraSize + 5] = abi.encode(schema.storagePricePerTibPerDay); - keys[extraSize + 6] = "minProvingPeriodInEpochs"; - values[extraSize + 6] = abi.encode(schema.minProvingPeriodInEpochs); - keys[extraSize + 7] = "location"; - values[extraSize + 7] = bytes(schema.location); - keys[extraSize + 8] = "paymentTokenAddress"; - values[extraSize + 8] = abi.encode(schema.paymentTokenAddress); + values[extraSize + 2] = BigEndian.encode(schema.maxPieceSizeInBytes); + keys[extraSize + 3] = "storagePricePerTibPerDay"; + values[extraSize + 3] = BigEndian.encode(schema.storagePricePerTibPerDay); + keys[extraSize + 4] = "minProvingPeriodInEpochs"; + values[extraSize + 4] = BigEndian.encode(schema.minProvingPeriodInEpochs); + keys[extraSize + 5] = "location"; + values[extraSize + 5] = bytes(schema.location); + keys[extraSize + 6] = "paymentTokenAddress"; + values[extraSize + 6] = abi.encodePacked(schema.paymentTokenAddress); + if (schema.ipniPiece) { + keys[extraSize + 7] = "ipniPiece"; + values[extraSize + 7] = BigEndian.encode(1); + } + if (schema.ipniIpfs) { + keys[keys.length - 1] = "ipniIpfs"; + values[keys.length - 1] = BigEndian.encode(1); + } } function toCapabilities(Schema memory schema) internal pure returns (string[] memory keys, bytes[] memory values) { diff --git a/service_contracts/test/ServiceProviderRegistryFull.t.sol b/service_contracts/test/ServiceProviderRegistryFull.t.sol index 97af4ad0..b7f386a3 100644 --- a/service_contracts/test/ServiceProviderRegistryFull.t.sol +++ b/service_contracts/test/ServiceProviderRegistryFull.t.sol @@ -1374,9 +1374,9 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { } function testMaxCapabilitiesAllowed() public { - (string[] memory capKeys, bytes[] memory capValues) = defaultPDPData.toCapabilities(15); + (string[] memory capKeys, bytes[] memory capValues) = defaultPDPData.toCapabilities(16); - for (uint256 i = 0; i < 15; i++) { + for (uint256 i = 0; i < 16; i++) { capKeys[i] = string(abi.encodePacked("key", vm.toString(i))); capValues[i] = abi.encodePacked("value", vm.toString(i)); }