Skip to content
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
ba25df6
wip bloom schema
wjmelements Oct 20, 2025
8a503aa
only shift by 8
wjmelements Oct 20, 2025
934f14e
parameterize K
wjmelements Oct 20, 2025
393a5a3
Merge remote-tracking branch 'origin/main' into bloom-schema
wjmelements Oct 21, 2025
c08fb7a
test BloomSet16
wjmelements Oct 21, 2025
35dd6c3
Errors.InsufficientCapabilitiesForProduct, and restore prior PDPOffer…
wjmelements Oct 21, 2025
62fd4b2
Merge remote-tracking branch 'origin/main' into bloom-schema
wjmelements Oct 22, 2025
2fb481e
must tag as dev
wjmelements Oct 22, 2025
1fdfd06
bytes[] capability values
wjmelements Oct 23, 2025
11dc0ba
tests build but do not yet pass
wjmelements Oct 23, 2025
e317e43
tests pass
wjmelements Oct 23, 2025
fc80b4b
mv PDPOffering.sol to test
wjmelements Oct 23, 2025
3d6682d
make update-abi
wjmelements Oct 23, 2025
17bd9ec
update subgraph: ProductUpdated and create product
wjmelements Oct 23, 2025
8061aa6
subgraph: bytes capabilityValues
wjmelements Oct 23, 2025
621f789
remove misleading exists bools
wjmelements Oct 23, 2025
f565a89
make ipni fields optional and document ipniPeerId
wjmelements Oct 23, 2025
891e3b6
getAllProductCapabilities, and add capability values to getActiveProv…
wjmelements Oct 23, 2025
7a572f1
make update-abi
wjmelements Oct 23, 2025
5a3224c
else if
wjmelements Oct 23, 2025
2709451
doc getAllProductCapabilities
wjmelements Oct 24, 2025
99d9cf9
ban empty capability value
wjmelements Oct 24, 2025
efc9773
forge lint
wjmelements Oct 24, 2025
a4d0062
feat(registry): provide more useful accessors (#328)
rvagg Oct 24, 2025
1628fde
Update service_contracts/src/lib/BloomSet.sol
wjmelements Oct 24, 2025
602b0dc
Merge remote-tracking branch 'origin/main' into bloom-schema
wjmelements Oct 24, 2025
3c28451
BigEndian library
wjmelements Oct 24, 2025
94eed74
use big endian encoding in test
wjmelements Oct 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
502 changes: 142 additions & 360 deletions service_contracts/abi/ServiceProviderRegistry.abi.json

Large diffs are not rendered by default.

9 changes: 2 additions & 7 deletions service_contracts/abi/ServiceProviderRegistryStorage.abi.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@
"outputs": [
{
"name": "value",
"type": "string",
"internalType": "string"
"type": "bytes",
"internalType": "bytes"
}
],
"stateMutability": "view"
Expand Down Expand Up @@ -119,11 +119,6 @@
"type": "uint8",
"internalType": "enum ServiceProviderRegistryStorage.ProductType"
},
{
"name": "productData",
"type": "bytes",
"internalType": "bytes"
},
{
"name": "isActive",
"type": "bool",
Expand Down
6 changes: 6 additions & 0 deletions service_contracts/src/Errors.sol
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -286,4 +288,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);
}
310 changes: 114 additions & 196 deletions service_contracts/src/ServiceProviderRegistry.sol

Large diffs are not rendered by default.

19 changes: 2 additions & 17 deletions service_contracts/src/ServiceProviderRegistryStorage.sol
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -28,29 +26,16 @@ 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;
ServiceProviderInfo providerInfo;
ServiceProduct product;
bytes[] productCapabilityValues;
}

/// @notice Paginated result for provider queries
Expand All @@ -75,7 +60,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
Expand Down
38 changes: 38 additions & 0 deletions service_contracts/src/lib/BloomSet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// 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=16)
/// @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 {
uint256 private constant K = 16;
uint256 internal constant EMPTY = 0;

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 < K; i++) {
item |= 1 << (hash & 0xff);
hash >>= 8;
}
}

/// @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;
}
}
52 changes: 52 additions & 0 deletions service_contracts/test/BloomSet.t.sol
Original file line number Diff line number Diff line change
@@ -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()));
}
}
86 changes: 26 additions & 60 deletions service_contracts/test/FilecoinWarmStorageService.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 "./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

Expand Down Expand Up @@ -134,97 +136,61 @@ 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}(
serviceProvider, // payee
"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)
Expand Down
29 changes: 14 additions & 15 deletions service_contracts/test/FilecoinWarmStorageServiceOwner.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 "./PDPOffering.sol";
import {ServiceProviderRegistry} from "../src/ServiceProviderRegistry.sol";
import {ServiceProviderRegistryStorage} from "../src/ServiceProviderRegistryStorage.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
Expand All @@ -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
Expand Down Expand Up @@ -131,28 +133,25 @@ 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}(
provider, // payee
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
);
Expand Down
Loading