From d27dcf0bbe3cee4e204e22602eff11cb852d4c63 Mon Sep 17 00:00:00 2001 From: Sambhav Jain <136801346+DarkLord017@users.noreply.github.com> Date: Sat, 15 Nov 2025 02:17:27 +0530 Subject: [PATCH 1/3] feat(piecesAdded):validatePayerFunds --- .../src/FilecoinWarmStorageService.sol | 59 ++++++++++++++ .../test/FilecoinWarmStorageService.t.sol | 79 ++++++++++++++++--- service_contracts/test/mocks/SharedMocks.sol | 28 +++++++ 3 files changed, 157 insertions(+), 9 deletions(-) diff --git a/service_contracts/src/FilecoinWarmStorageService.sol b/service_contracts/src/FilecoinWarmStorageService.sol index 01ab88f8..528768dc 100644 --- a/service_contracts/src/FilecoinWarmStorageService.sol +++ b/service_contracts/src/FilecoinWarmStorageService.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.20; import {PDPListener} from "@pdp/PDPVerifier.sol"; +import {IPDPVerifier} from "@pdp/interfaces/IPDPVerifier.sol"; import {Cids} from "@pdp/Cids.sol"; import {SessionKeyRegistry} from "@session-key-registry/SessionKeyRegistry.sol"; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; @@ -834,6 +835,13 @@ contract FilecoinWarmStorageService is // Verify the signature verifyAddPiecesSignature(payer, info.clientDataSetId, pieceData, nonce, metadataKeys, metadataValues, signature); + // Validate payer/operator approvals and available funds for the new pieces + // This checks the payer has sufficient available funds and operator allowances + // to cover the increased per-epoch rate and the 30-day lockup implied by the + // new total leaf count after adding these pieces. + FilecoinPayV1 payments = FilecoinPayV1(paymentsContractAddress); + validatePayerOperatorApprovalAndFundsForPieces(payments, payer, dataSetId, pieceData); + // Store metadata for each new piece for (uint256 i = 0; i < pieceData.length; i++) { uint256 pieceId = firstAdded + i; @@ -873,6 +881,57 @@ contract FilecoinWarmStorageService is } } + /// @notice Validate payer/operator approvals and funds for adding pieces + /// @dev Computes the new storage rate and corresponding 30-day lockup after adding `pieceData` + /// and validates the payer has sufficient available funds and operator allowances. + function validatePayerOperatorApprovalAndFundsForPieces( + FilecoinPayV1 payments, + address payer, + uint256 dataSetId, + Cids.Cid[] memory pieceData + ) internal view { + // Get existing leaf count from the PDP verifier + uint256 leaves = IPDPVerifier(pdpVerifierAddress).getDataSetLeafCount(dataSetId); + + uint256 totalBytes = leaves * BYTES_PER_LEAF; + uint256 storageRatePerEpoch = _calculateStorageRate(totalBytes); + uint256 lockupRequired = storageRatePerEpoch * DEFAULT_LOCKUP_PERIOD; + + // Check available funds + (,, uint256 availableFunds,) = payments.getAccountInfoIfSettled(usdfcTokenAddress, payer); + require(availableFunds >= lockupRequired, Errors.InsufficientLockupFunds(payer, lockupRequired, availableFunds)); + + // Check operator approvals for this contract + ( + bool isApproved, + uint256 rateAllowance, + uint256 lockupAllowance, + uint256 rateUsage, + uint256 lockupUsage, + uint256 maxLockupPeriod + ) = payments.operatorApprovals(usdfcTokenAddress, payer, address(this)); + + require(isApproved, Errors.OperatorNotApproved(payer, address(this))); + + // Verify rate allowance is sufficient for the new per-epoch rate + require( + rateAllowance >= rateUsage + storageRatePerEpoch, + Errors.InsufficientRateAllowance(payer, address(this), rateAllowance, rateUsage, storageRatePerEpoch) + ); + + // Verify lockup allowance is sufficient for the new 30-day lockup amount + require( + lockupAllowance >= lockupUsage + lockupRequired, + Errors.InsufficientLockupAllowance(payer, address(this), lockupAllowance, lockupUsage, lockupRequired) + ); + + // Verify max lockup period + require( + maxLockupPeriod >= DEFAULT_LOCKUP_PERIOD, + Errors.InsufficientMaxLockupPeriod(payer, address(this), maxLockupPeriod, DEFAULT_LOCKUP_PERIOD) + ); + } + function piecesScheduledRemove(uint256 dataSetId, uint256[] memory pieceIds, bytes calldata extraData) external onlyPDPVerifier diff --git a/service_contracts/test/FilecoinWarmStorageService.t.sol b/service_contracts/test/FilecoinWarmStorageService.t.sol index ff8d8216..a44e40d7 100644 --- a/service_contracts/test/FilecoinWarmStorageService.t.sol +++ b/service_contracts/test/FilecoinWarmStorageService.t.sol @@ -803,9 +803,9 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest { // First batch (3 pieces) with key "meta" => metadataShort Cids.Cid[] memory pieceData1 = new Cids.Cid[](3); - pieceData1[0].data = bytes("1_0:1111"); - pieceData1[1].data = bytes("1_1:111100000"); - pieceData1[2].data = bytes("1_2:11110000000000"); + pieceData1[0] = Cids.CommPv2FromDigest(0, 4, keccak256(abi.encodePacked("1_0:1111"))); + pieceData1[1] = Cids.CommPv2FromDigest(0, 4, keccak256(abi.encodePacked("1_1:111100000"))); + pieceData1[2] = Cids.CommPv2FromDigest(0, 4, keccak256(abi.encodePacked("1_2:11110000000000"))); string[] memory keys1 = new string[](1); string[] memory values1 = new string[](1); keys1[0] = "meta"; @@ -817,8 +817,10 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest { // Second batch (2 pieces) with key "meta" => metadataLong Cids.Cid[] memory pieceData2 = new Cids.Cid[](2); - pieceData2[0].data = bytes("2_0:22222222222222222222"); - pieceData2[1].data = bytes("2_1:222222222222222222220000000000000000000000000000000000000000"); + pieceData2[0] = Cids.CommPv2FromDigest(0, 4, keccak256(abi.encodePacked("2_0:22222222222222222222"))); + pieceData2[1] = Cids.CommPv2FromDigest( + 0, 4, keccak256(abi.encodePacked("2_1:222222222222222222220000000000000000000000000000000000000000000")) + ); string[] memory keys2 = new string[](1); string[] memory values2 = new string[](1); keys2[0] = "meta"; @@ -1161,6 +1163,65 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest { assertEq(dataSetId, 1, "Dataset should be created with above-minimum funds"); } + // function testInsufficientFunds_AddPieces() public { + // // Setup: Client with exactly the minimum funds (0.06 USDFC) + // address exactClient = makeAddr("exactClient"); + // uint256 exactAmount = 6e16; // Exactly 0.06 USDFC + + // // Transfer tokens from test contract to the test client + // mockUSDFC.safeTransfer(exactClient, exactAmount); + + // vm.startPrank(exactClient); + // payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e18, 1000e18, 365 days); + // mockUSDFC.approve(address(payments), exactAmount); + // payments.deposit(mockUSDFC, exactClient, exactAmount); + // vm.stopPrank(); + + // // Prepare dataset creation data + // (string[] memory dsKeys, string[] memory dsValues) = _getSingleMetadataKV("label", "Exact Minimum Test"); + // FilecoinWarmStorageService.DataSetCreateData memory createData = FilecoinWarmStorageService.DataSetCreateData({ + // payer: exactClient, + // clientDataSetId: 1000, + // metadataKeys: dsKeys, + // metadataValues: dsValues, + // signature: FAKE_SIGNATURE + // }); + + // bytes memory encodedCreateData = abi.encode( + // createData.payer, + // createData.clientDataSetId, + // createData.metadataKeys, + // createData.metadataValues, + // createData.signature + // ); + + // // Should succeed with exact minimum + // makeSignaturePass(exactClient); + // vm.prank(serviceProvider); + // uint256 dataSetId = mockPDPVerifier.createDataSet(pdpServiceWithPayments, encodedCreateData); + + // vm.prank(exactClient); + // payments.withdraw(mockUSDFC, 59999999999961600); // Withdraw all funds, leaving 0 balance + + // // Prepare piece addition data + // Cids.Cid[] memory pieceData = new Cids.Cid[](1); + // pieceData[0] = Cids.CommPv2FromDigest(0, 4, keccak256(abi.encodePacked("piece:belowMinimum"))); + // string[] memory keys = new string[](0); + // string[] memory values = new string[](0); + // uint256 firstAdded = 0; + // // Expect revert when adding pieces due to insufficient funds + // makeSignaturePass(exactClient); + // vm.expectRevert( + // abi.encodeWithSelector( + // Errors.InsufficientLockupFunds.selector, exactClient, 6e16, 5e16 + // ) + // ); + // vm.prank(serviceProvider); + // mockPDPVerifier.addPieces( + // pdpServiceWithPayments, dataSetId, firstAdded, pieceData, 0, FAKE_SIGNATURE, keys, values + // ); + // } + // Operator Approval Validation Tests function testOperatorApproval_NotApproved() public { // Setup: Client with sufficient funds but no operator approval @@ -4558,7 +4619,7 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest { // Prepare piece data Cids.Cid[] memory pieceData = new Cids.Cid[](1); - pieceData[0].data = bytes("test_piece_1"); + pieceData[0] = Cids.CommPv2FromDigest(0, 4, keccak256(abi.encodePacked("test_piece_1"))); string[] memory keys = new string[](0); string[] memory values = new string[](0); @@ -4604,7 +4665,7 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest { // Prepare piece data Cids.Cid[] memory pieceData = new Cids.Cid[](1); - pieceData[0].data = bytes("test_piece"); + pieceData[0] = Cids.CommPv2FromDigest(0, 4, keccak256(abi.encodePacked("test_piece_1"))); string[] memory keys = new string[](0); string[] memory values = new string[](0); @@ -4678,7 +4739,7 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest { // Prepare piece data Cids.Cid[] memory pieceData = new Cids.Cid[](1); - pieceData[0].data = bytes("test"); + pieceData[0] = Cids.CommPv2FromDigest(0, 4, keccak256(abi.encodePacked("test_piece_1"))); string[] memory keys = new string[](0); string[] memory values = new string[](0); @@ -4725,7 +4786,7 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest { // Prepare piece data Cids.Cid[] memory pieceData = new Cids.Cid[](1); - pieceData[0].data = bytes("test"); + pieceData[0] = Cids.CommPv2FromDigest(0, 4, keccak256(abi.encodePacked("test_piece_1"))); string[] memory keys = new string[](0); string[] memory values = new string[](0); diff --git a/service_contracts/test/mocks/SharedMocks.sol b/service_contracts/test/mocks/SharedMocks.sol index 39e0b267..1b2f086b 100644 --- a/service_contracts/test/mocks/SharedMocks.sol +++ b/service_contracts/test/mocks/SharedMocks.sol @@ -99,6 +99,8 @@ contract MockPDPVerifier { // Track data set service providers for testing mapping(uint256 => address) public dataSetServiceProviders; + // Track simple leaf counts per data set for tests (approximate via bytes length) + mapping(uint256 => uint256) public dataSetLeafCount; event DataSetCreated(uint256 indexed setId, address indexed owner); event DataSetServiceProviderChanged( @@ -118,6 +120,9 @@ contract MockPDPVerifier { // Track service provider dataSetServiceProviders[setId] = msg.sender; + // initialize leaf count to 0 + dataSetLeafCount[setId] = 0; + emit DataSetCreated(setId, msg.sender); return setId; } @@ -128,6 +133,7 @@ contract MockPDPVerifier { } delete dataSetServiceProviders[setId]; + delete dataSetLeafCount[setId]; emit DataSetDeleted(setId, 0); } @@ -150,9 +156,31 @@ contract MockPDPVerifier { } bytes memory extraData = abi.encode(nonce, allKeys, allValues, signature); + + // // Update simple leaf count estimate for each piece using bytes length -> 32-byte leaves + // uint256 addedLeaves = 0; + // for (uint256 i = 0; i < pieceData.length; i++) { + // uint256 dataLen = pieceData[i].data.length; + // // estimate leaf count as ceil(dataLen / 32), minimum 1 + // uint256 leaves = dataLen == 0 ? 1 : (dataLen + 31) / 32; + // addedLeaves += leaves; + // } + // dataSetLeafCount[dataSetId] += addedLeaves; + uint256 leafCount = 0; + for (uint256 i = 0; i < pieceData.length; i++) { + (uint256 padding, uint8 height,) = Cids.validateCommPv2(pieceData[i]); + leafCount += Cids.leafCount(padding, height); + } + dataSetLeafCount[dataSetId] += leafCount; + listenerAddr.piecesAdded(dataSetId, firstAdded, pieceData, extraData); } + // Expose leaf count similar to real PDPVerifier + function getDataSetLeafCount(uint256 setId) external view returns (uint256) { + return dataSetLeafCount[setId]; + } + /** * @notice Simulates service provider change for testing purposes * @dev This function mimics the PDPVerifier's claimDataSetOwnership functionality From f30ac5ce8db4120b993d9bc125aa7fea5889d475 Mon Sep 17 00:00:00 2001 From: Sambhav Jain <136801346+DarkLord017@users.noreply.github.com> Date: Sat, 6 Dec 2025 03:04:00 +0530 Subject: [PATCH 2/3] feat: add functionality for pieces in validatePayerOperator --- .../src/FilecoinWarmStorageService.sol | 85 ++---- .../test/FilecoinWarmStorageService.t.sol | 285 ++++++++++++++---- service_contracts/test/mocks/SharedMocks.sol | 9 - 3 files changed, 244 insertions(+), 135 deletions(-) diff --git a/service_contracts/src/FilecoinWarmStorageService.sol b/service_contracts/src/FilecoinWarmStorageService.sol index 528768dc..bfc06c0d 100644 --- a/service_contracts/src/FilecoinWarmStorageService.sol +++ b/service_contracts/src/FilecoinWarmStorageService.sol @@ -664,7 +664,7 @@ contract FilecoinWarmStorageService is // Validate payer has sufficient funds and operator approvals for minimum pricing // If CDN is enabled, validation must account for the additional fixed lockup amounts - validatePayerOperatorApprovalAndFunds(payments, createData.payer, hasCDN); + validatePayerOperatorApprovalAndFunds(payments, createData.payer, hasCDN , 0 , new Cids.Cid[](0)); uint256 pdpRailId = payments.createRail( usdfcTokenAddress, // token address @@ -840,7 +840,7 @@ contract FilecoinWarmStorageService is // to cover the increased per-epoch rate and the 30-day lockup implied by the // new total leaf count after adding these pieces. FilecoinPayV1 payments = FilecoinPayV1(paymentsContractAddress); - validatePayerOperatorApprovalAndFundsForPieces(payments, payer, dataSetId, pieceData); + validatePayerOperatorApprovalAndFunds(payments, payer, false , dataSetId , pieceData); // Store metadata for each new piece for (uint256 i = 0; i < pieceData.length; i++) { @@ -881,57 +881,6 @@ contract FilecoinWarmStorageService is } } - /// @notice Validate payer/operator approvals and funds for adding pieces - /// @dev Computes the new storage rate and corresponding 30-day lockup after adding `pieceData` - /// and validates the payer has sufficient available funds and operator allowances. - function validatePayerOperatorApprovalAndFundsForPieces( - FilecoinPayV1 payments, - address payer, - uint256 dataSetId, - Cids.Cid[] memory pieceData - ) internal view { - // Get existing leaf count from the PDP verifier - uint256 leaves = IPDPVerifier(pdpVerifierAddress).getDataSetLeafCount(dataSetId); - - uint256 totalBytes = leaves * BYTES_PER_LEAF; - uint256 storageRatePerEpoch = _calculateStorageRate(totalBytes); - uint256 lockupRequired = storageRatePerEpoch * DEFAULT_LOCKUP_PERIOD; - - // Check available funds - (,, uint256 availableFunds,) = payments.getAccountInfoIfSettled(usdfcTokenAddress, payer); - require(availableFunds >= lockupRequired, Errors.InsufficientLockupFunds(payer, lockupRequired, availableFunds)); - - // Check operator approvals for this contract - ( - bool isApproved, - uint256 rateAllowance, - uint256 lockupAllowance, - uint256 rateUsage, - uint256 lockupUsage, - uint256 maxLockupPeriod - ) = payments.operatorApprovals(usdfcTokenAddress, payer, address(this)); - - require(isApproved, Errors.OperatorNotApproved(payer, address(this))); - - // Verify rate allowance is sufficient for the new per-epoch rate - require( - rateAllowance >= rateUsage + storageRatePerEpoch, - Errors.InsufficientRateAllowance(payer, address(this), rateAllowance, rateUsage, storageRatePerEpoch) - ); - - // Verify lockup allowance is sufficient for the new 30-day lockup amount - require( - lockupAllowance >= lockupUsage + lockupRequired, - Errors.InsufficientLockupAllowance(payer, address(this), lockupAllowance, lockupUsage, lockupRequired) - ); - - // Verify max lockup period - require( - maxLockupPeriod >= DEFAULT_LOCKUP_PERIOD, - Errors.InsufficientMaxLockupPeriod(payer, address(this), maxLockupPeriod, DEFAULT_LOCKUP_PERIOD) - ); - } - function piecesScheduledRemove(uint256 dataSetId, uint256[] memory pieceIds, bytes calldata extraData) external onlyPDPVerifier @@ -1236,14 +1185,22 @@ contract FilecoinWarmStorageService is /// @notice Validates that the payer has sufficient funds and operator approvals for minimum pricing /// @param payments The FilecoinPayV1 contract instance /// @param payer The address of the payer - function validatePayerOperatorApprovalAndFunds(FilecoinPayV1 payments, address payer, bool includeCDN) - internal - view - { - // Calculate required lockup for minimum pricing - uint256 minimumLockupRequired = (minimumStorageRatePerMonth * DEFAULT_LOCKUP_PERIOD) / EPOCHS_PER_MONTH; + function validatePayerOperatorApprovalAndFunds( + FilecoinPayV1 payments, + address payer, + bool includeCDN, + uint256 dataSetId, + Cids.Cid[] memory pieceData + ) internal view { + uint256 totalBytes = 0; + if (dataSetId != 0 && pieceData.length != 0) { + totalBytes = IPDPVerifier(pdpVerifierAddress).getDataSetLeafCount(dataSetId) * BYTES_PER_LEAF; + } - // If CDN is enabled, include the fixed cache-miss and CDN lockup amounts + // Calculate the minimum storage rate per epoch based on total bytes + uint256 minimumStorageRatePerEpoch = _calculateStorageRate(totalBytes); + // Calculate the minimum lockup required for the payer + uint256 minimumLockupRequired = minimumStorageRatePerEpoch * DEFAULT_LOCKUP_PERIOD; if (includeCDN) { minimumLockupRequired += DEFAULT_CACHE_MISS_LOCKUP_AMOUNT + DEFAULT_CDN_LOCKUP_AMOUNT; } @@ -1267,14 +1224,10 @@ contract FilecoinWarmStorageService is // Verify operator is approved require(isApproved, Errors.OperatorNotApproved(payer, address(this))); - - // Calculate minimum rate per epoch - uint256 minimumRatePerEpoch = minimumStorageRatePerMonth / EPOCHS_PER_MONTH; - // Verify rate allowance is sufficient require( - rateAllowance >= rateUsage + minimumRatePerEpoch, - Errors.InsufficientRateAllowance(payer, address(this), rateAllowance, rateUsage, minimumRatePerEpoch) + rateAllowance >= rateUsage + minimumStorageRatePerEpoch, + Errors.InsufficientRateAllowance(payer, address(this), rateAllowance, rateUsage, minimumStorageRatePerEpoch) ); // Verify lockup allowance is sufficient (include CDN extras when applicable) diff --git a/service_contracts/test/FilecoinWarmStorageService.t.sol b/service_contracts/test/FilecoinWarmStorageService.t.sol index a44e40d7..7efc91c2 100644 --- a/service_contracts/test/FilecoinWarmStorageService.t.sol +++ b/service_contracts/test/FilecoinWarmStorageService.t.sol @@ -1068,7 +1068,8 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest { ); // Expected minimum: (0.06 USDFC * 86400) / 86400 = 0.06 USDFC = 6e16 - uint256 minimumRequired = 6e16; + uint256 minimumRequiredPerEpoch = (uint256(6e16) / (2880*30)); + uint256 minimumRequired = minimumRequiredPerEpoch * (2880 * 30); // Expect revert with InsufficientLockupFunds error makeSignaturePass(insufficientClient); @@ -1163,64 +1164,227 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest { assertEq(dataSetId, 1, "Dataset should be created with above-minimum funds"); } - // function testInsufficientFunds_AddPieces() public { - // // Setup: Client with exactly the minimum funds (0.06 USDFC) - // address exactClient = makeAddr("exactClient"); - // uint256 exactAmount = 6e16; // Exactly 0.06 USDFC - - // // Transfer tokens from test contract to the test client - // mockUSDFC.safeTransfer(exactClient, exactAmount); - - // vm.startPrank(exactClient); - // payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e18, 1000e18, 365 days); - // mockUSDFC.approve(address(payments), exactAmount); - // payments.deposit(mockUSDFC, exactClient, exactAmount); - // vm.stopPrank(); - - // // Prepare dataset creation data - // (string[] memory dsKeys, string[] memory dsValues) = _getSingleMetadataKV("label", "Exact Minimum Test"); - // FilecoinWarmStorageService.DataSetCreateData memory createData = FilecoinWarmStorageService.DataSetCreateData({ - // payer: exactClient, - // clientDataSetId: 1000, - // metadataKeys: dsKeys, - // metadataValues: dsValues, - // signature: FAKE_SIGNATURE - // }); - - // bytes memory encodedCreateData = abi.encode( - // createData.payer, - // createData.clientDataSetId, - // createData.metadataKeys, - // createData.metadataValues, - // createData.signature - // ); - - // // Should succeed with exact minimum - // makeSignaturePass(exactClient); - // vm.prank(serviceProvider); - // uint256 dataSetId = mockPDPVerifier.createDataSet(pdpServiceWithPayments, encodedCreateData); - - // vm.prank(exactClient); - // payments.withdraw(mockUSDFC, 59999999999961600); // Withdraw all funds, leaving 0 balance - - // // Prepare piece addition data - // Cids.Cid[] memory pieceData = new Cids.Cid[](1); - // pieceData[0] = Cids.CommPv2FromDigest(0, 4, keccak256(abi.encodePacked("piece:belowMinimum"))); - // string[] memory keys = new string[](0); - // string[] memory values = new string[](0); - // uint256 firstAdded = 0; - // // Expect revert when adding pieces due to insufficient funds - // makeSignaturePass(exactClient); - // vm.expectRevert( - // abi.encodeWithSelector( - // Errors.InsufficientLockupFunds.selector, exactClient, 6e16, 5e16 - // ) - // ); - // vm.prank(serviceProvider); - // mockPDPVerifier.addPieces( - // pdpServiceWithPayments, dataSetId, firstAdded, pieceData, 0, FAKE_SIGNATURE, keys, values - // ); - // } + function testAddPiecesFundingValidation() public { + uint256 minimumAmount = 6e16; // 0.06 USDFC + + // Test 1: Insufficient funds with small piece (below minimum rate) + address client1 = makeAddr("client1"); + mockUSDFC.safeTransfer(client1, minimumAmount); + + vm.startPrank(client1); + payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e18, 1000e18, 365 days); + mockUSDFC.approve(address(payments), minimumAmount); + payments.deposit(mockUSDFC, client1, minimumAmount); + vm.stopPrank(); + + (string[] memory dsKeys, string[] memory dsValues) = _getSingleMetadataKV("label", "Test"); + FilecoinWarmStorageService.DataSetCreateData memory createData = FilecoinWarmStorageService.DataSetCreateData({ + payer: client1, + clientDataSetId: 1001, + metadataKeys: dsKeys, + metadataValues: dsValues, + signature: FAKE_SIGNATURE + }); + + bytes memory encodedCreateData = abi.encode( + createData.payer, + createData.clientDataSetId, + createData.metadataKeys, + createData.metadataValues, + createData.signature + ); + + makeSignaturePass(client1); + vm.prank(serviceProvider); + uint256 dataSetId1 = mockPDPVerifier.createDataSet(pdpServiceWithPayments, encodedCreateData); + + vm.prank(client1); + payments.withdraw(mockUSDFC, 59999999999961600); // Withdraw almost all funds + + Cids.Cid[] memory pieceData1 = new Cids.Cid[](1); + pieceData1[0] = Cids.CommPv2FromDigest(0, 4, keccak256(abi.encodePacked("piece1"))); + string[] memory emptyKeys = new string[](0); + string[] memory emptyValues = new string[](0); + + makeSignaturePass(client1); + uint256 leafCount1 = 0; + for (uint256 i = 0; i < pieceData1.length; i++) { + (uint256 padding, uint8 height,) = Cids.validateCommPv2(pieceData1[i]); + leafCount1 += Cids.leafCount(padding, height); + } + uint256 lockupRequired1 = pdpServiceWithPayments.calculateRatePerEpoch(leafCount1 * 32) * 2880 * 30; + (uint256 availableFunds1,) = getAccountInfo(mockUSDFC, client1); + + vm.expectRevert( + abi.encodeWithSelector( + Errors.InsufficientLockupFunds.selector, client1, lockupRequired1, availableFunds1 + ) + ); + vm.prank(serviceProvider); + mockPDPVerifier.addPieces( + pdpServiceWithPayments, dataSetId1, 0, pieceData1, 0, FAKE_SIGNATURE, emptyKeys, emptyValues + ); + + // Test 2: Sufficient funds with small piece (below minimum rate) + address client2 = makeAddr("client2"); + mockUSDFC.safeTransfer(client2, minimumAmount); + + vm.startPrank(client2); + payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e18, 1000e18, 365 days); + mockUSDFC.approve(address(payments), minimumAmount); + payments.deposit(mockUSDFC, client2, minimumAmount); + vm.stopPrank(); + + createData.payer = client2; + createData.clientDataSetId = 1002; + encodedCreateData = abi.encode( + createData.payer, + createData.clientDataSetId, + createData.metadataKeys, + createData.metadataValues, + createData.signature + ); + + makeSignaturePass(client2); + vm.prank(serviceProvider); + uint256 dataSetId2 = mockPDPVerifier.createDataSet(pdpServiceWithPayments, encodedCreateData); + + Cids.Cid[] memory pieceData2 = new Cids.Cid[](1); + pieceData2[0] = Cids.CommPv2FromDigest(0, 4, keccak256(abi.encodePacked("piece2"))); + + vm.prank(serviceProvider); + mockPDPVerifier.addPieces( + pdpServiceWithPayments, dataSetId2, 0, pieceData2, 0, FAKE_SIGNATURE, emptyKeys, emptyValues + ); + + // Test 3: Insufficient funds with large pieces (above minimum rate) + address client3 = makeAddr("client3"); + mockUSDFC.safeTransfer(client3, minimumAmount); + + vm.startPrank(client3); + payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e18, 1000e18, 365 days); + mockUSDFC.approve(address(payments), minimumAmount); + payments.deposit(mockUSDFC, client3, minimumAmount); + vm.stopPrank(); + + createData.payer = client3; + createData.clientDataSetId = 1003; + encodedCreateData = abi.encode( + createData.payer, + createData.clientDataSetId, + createData.metadataKeys, + createData.metadataValues, + createData.signature + ); + + makeSignaturePass(client3); + vm.prank(serviceProvider); + uint256 dataSetId3 = mockPDPVerifier.createDataSet(pdpServiceWithPayments, encodedCreateData); + + // Construct 5 large pieces + uint256 totalPieces = 5; + Cids.Cid[] memory pieceData3 = new Cids.Cid[](totalPieces); + string[] memory keys3 = new string[](MAX_KEYS_PER_PIECE); + string[] memory values3 = new string[](MAX_KEYS_PER_PIECE); + + for (uint256 p = 0; p < totalPieces; p++) { + pieceData3[p] = Cids.CommPv2FromDigest( + 0, + 28, + keccak256(abi.encodePacked("file", Strings.toString(p))) + ); + } + for (uint256 k = 0; k < MAX_KEYS_PER_PIECE; k++) { + keys3[k] = _generateKey(k); + values3[k] = _makeStringOfLength(128); + } + + makeSignaturePass(client3); + + uint256 leafCount3 = 0; + for (uint256 i = 0; i < pieceData3.length; i++) { + (uint256 padding, uint8 height,) = Cids.validateCommPv2(pieceData3[i]); + leafCount3 += Cids.leafCount(padding, height); + } + + uint256 lockupRequired3 = pdpServiceWithPayments.calculateRatePerEpoch(leafCount3 * 32) * 2880 * 30; + (uint256 availableFunds3,) = getAccountInfo(mockUSDFC, client3); + + vm.expectRevert( + abi.encodeWithSelector( + Errors.InsufficientLockupFunds.selector, + client3, + lockupRequired3, + availableFunds3 + ) + ); + vm.prank(serviceProvider); + mockPDPVerifier.addPieces( + pdpServiceWithPayments, + dataSetId3, + 0, + pieceData3, + 0, + FAKE_SIGNATURE, + keys3, + values3 + ); + + // Test 4: Sufficient funds with large pieces (above minimum rate) + address client4 = makeAddr("client4"); + uint256 sufficientAmount = 10e16; // 0.10 USDFC + mockUSDFC.safeTransfer(client4, sufficientAmount); + + vm.startPrank(client4); + payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e18, 1000e18, 365 days); + mockUSDFC.approve(address(payments), sufficientAmount); + payments.deposit(mockUSDFC, client4, sufficientAmount); + vm.stopPrank(); + + createData.payer = client4; + createData.clientDataSetId = 1004; + encodedCreateData = abi.encode( + createData.payer, + createData.clientDataSetId, + createData.metadataKeys, + createData.metadataValues, + createData.signature + ); + + makeSignaturePass(client4); + vm.prank(serviceProvider); + uint256 dataSetId4 = mockPDPVerifier.createDataSet(pdpServiceWithPayments, encodedCreateData); + + Cids.Cid[] memory pieceData4 = new Cids.Cid[](totalPieces); + string[] memory keys4 = new string[](MAX_KEYS_PER_PIECE); + string[] memory values4 = new string[](MAX_KEYS_PER_PIECE); + + for (uint256 p = 0; p < totalPieces; p++) { + pieceData4[p] = Cids.CommPv2FromDigest( + 0, + 28, + keccak256(abi.encodePacked("file", Strings.toString(p))) + ); + } + for (uint256 k = 0; k < MAX_KEYS_PER_PIECE; k++) { + keys4[k] = _generateKey(k); + values4[k] = _makeStringOfLength(128); + } + + makeSignaturePass(client4); + vm.prank(serviceProvider); + mockPDPVerifier.addPieces( + pdpServiceWithPayments, + dataSetId4, + 0, + pieceData4, + 0, + FAKE_SIGNATURE, + keys4, + values4 + ); + } + // Operator Approval Validation Tests function testOperatorApproval_NotApproved() public { @@ -1338,7 +1502,8 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest { // DEFAULT_LOCKUP_PERIOD = 86400 // EPOCHS_PER_MONTH = 86400 // minimumLockupRequired = (6e16 * 86400) / 86400 = 6e16 - uint256 minimumLockupRequired = 6e16; + uint256 minimumLockupRatePerEpoch = (uint256(6e16) / (2880*30)); + uint256 minimumLockupRequired = minimumLockupRatePerEpoch * (2880 * 30); uint256 insufficientLockupAllowance = minimumLockupRequired - 1; // Just below minimum // Transfer tokens and set up approvals diff --git a/service_contracts/test/mocks/SharedMocks.sol b/service_contracts/test/mocks/SharedMocks.sol index 1b2f086b..594c2f5a 100644 --- a/service_contracts/test/mocks/SharedMocks.sol +++ b/service_contracts/test/mocks/SharedMocks.sol @@ -157,15 +157,6 @@ contract MockPDPVerifier { bytes memory extraData = abi.encode(nonce, allKeys, allValues, signature); - // // Update simple leaf count estimate for each piece using bytes length -> 32-byte leaves - // uint256 addedLeaves = 0; - // for (uint256 i = 0; i < pieceData.length; i++) { - // uint256 dataLen = pieceData[i].data.length; - // // estimate leaf count as ceil(dataLen / 32), minimum 1 - // uint256 leaves = dataLen == 0 ? 1 : (dataLen + 31) / 32; - // addedLeaves += leaves; - // } - // dataSetLeafCount[dataSetId] += addedLeaves; uint256 leafCount = 0; for (uint256 i = 0; i < pieceData.length; i++) { (uint256 padding, uint8 height,) = Cids.validateCommPv2(pieceData[i]); From 6c11e8b46909aed083ef537471b51bb6ff22c1b7 Mon Sep 17 00:00:00 2001 From: Sambhav Jain <136801346+DarkLord017@users.noreply.github.com> Date: Sat, 6 Dec 2025 03:18:54 +0530 Subject: [PATCH 3/3] forge fmt --- .../src/FilecoinWarmStorageService.sol | 4 +- .../test/FilecoinWarmStorageService.t.sol | 62 +++++-------------- 2 files changed, 16 insertions(+), 50 deletions(-) diff --git a/service_contracts/src/FilecoinWarmStorageService.sol b/service_contracts/src/FilecoinWarmStorageService.sol index bfc06c0d..1ac62651 100644 --- a/service_contracts/src/FilecoinWarmStorageService.sol +++ b/service_contracts/src/FilecoinWarmStorageService.sol @@ -664,7 +664,7 @@ contract FilecoinWarmStorageService is // Validate payer has sufficient funds and operator approvals for minimum pricing // If CDN is enabled, validation must account for the additional fixed lockup amounts - validatePayerOperatorApprovalAndFunds(payments, createData.payer, hasCDN , 0 , new Cids.Cid[](0)); + validatePayerOperatorApprovalAndFunds(payments, createData.payer, hasCDN, 0, new Cids.Cid[](0)); uint256 pdpRailId = payments.createRail( usdfcTokenAddress, // token address @@ -840,7 +840,7 @@ contract FilecoinWarmStorageService is // to cover the increased per-epoch rate and the 30-day lockup implied by the // new total leaf count after adding these pieces. FilecoinPayV1 payments = FilecoinPayV1(paymentsContractAddress); - validatePayerOperatorApprovalAndFunds(payments, payer, false , dataSetId , pieceData); + validatePayerOperatorApprovalAndFunds(payments, payer, false, dataSetId, pieceData); // Store metadata for each new piece for (uint256 i = 0; i < pieceData.length; i++) { diff --git a/service_contracts/test/FilecoinWarmStorageService.t.sol b/service_contracts/test/FilecoinWarmStorageService.t.sol index 7efc91c2..8aaa1b54 100644 --- a/service_contracts/test/FilecoinWarmStorageService.t.sol +++ b/service_contracts/test/FilecoinWarmStorageService.t.sol @@ -1068,7 +1068,7 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest { ); // Expected minimum: (0.06 USDFC * 86400) / 86400 = 0.06 USDFC = 6e16 - uint256 minimumRequiredPerEpoch = (uint256(6e16) / (2880*30)); + uint256 minimumRequiredPerEpoch = (uint256(6e16) / (2880 * 30)); uint256 minimumRequired = minimumRequiredPerEpoch * (2880 * 30); // Expect revert with InsufficientLockupFunds error @@ -1170,7 +1170,7 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest { // Test 1: Insufficient funds with small piece (below minimum rate) address client1 = makeAddr("client1"); mockUSDFC.safeTransfer(client1, minimumAmount); - + vm.startPrank(client1); payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e18, 1000e18, 365 days); mockUSDFC.approve(address(payments), minimumAmount); @@ -1214,11 +1214,9 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest { } uint256 lockupRequired1 = pdpServiceWithPayments.calculateRatePerEpoch(leafCount1 * 32) * 2880 * 30; (uint256 availableFunds1,) = getAccountInfo(mockUSDFC, client1); - + vm.expectRevert( - abi.encodeWithSelector( - Errors.InsufficientLockupFunds.selector, client1, lockupRequired1, availableFunds1 - ) + abi.encodeWithSelector(Errors.InsufficientLockupFunds.selector, client1, lockupRequired1, availableFunds1) ); vm.prank(serviceProvider); mockPDPVerifier.addPieces( @@ -1228,7 +1226,7 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest { // Test 2: Sufficient funds with small piece (below minimum rate) address client2 = makeAddr("client2"); mockUSDFC.safeTransfer(client2, minimumAmount); - + vm.startPrank(client2); payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e18, 1000e18, 365 days); mockUSDFC.approve(address(payments), minimumAmount); @@ -1260,7 +1258,7 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest { // Test 3: Insufficient funds with large pieces (above minimum rate) address client3 = makeAddr("client3"); mockUSDFC.safeTransfer(client3, minimumAmount); - + vm.startPrank(client3); payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e18, 1000e18, 365 days); mockUSDFC.approve(address(payments), minimumAmount); @@ -1288,11 +1286,7 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest { string[] memory values3 = new string[](MAX_KEYS_PER_PIECE); for (uint256 p = 0; p < totalPieces; p++) { - pieceData3[p] = Cids.CommPv2FromDigest( - 0, - 28, - keccak256(abi.encodePacked("file", Strings.toString(p))) - ); + pieceData3[p] = Cids.CommPv2FromDigest(0, 28, keccak256(abi.encodePacked("file", Strings.toString(p)))); } for (uint256 k = 0; k < MAX_KEYS_PER_PIECE; k++) { keys3[k] = _generateKey(k); @@ -1309,32 +1303,18 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest { uint256 lockupRequired3 = pdpServiceWithPayments.calculateRatePerEpoch(leafCount3 * 32) * 2880 * 30; (uint256 availableFunds3,) = getAccountInfo(mockUSDFC, client3); - + vm.expectRevert( - abi.encodeWithSelector( - Errors.InsufficientLockupFunds.selector, - client3, - lockupRequired3, - availableFunds3 - ) + abi.encodeWithSelector(Errors.InsufficientLockupFunds.selector, client3, lockupRequired3, availableFunds3) ); vm.prank(serviceProvider); - mockPDPVerifier.addPieces( - pdpServiceWithPayments, - dataSetId3, - 0, - pieceData3, - 0, - FAKE_SIGNATURE, - keys3, - values3 - ); + mockPDPVerifier.addPieces(pdpServiceWithPayments, dataSetId3, 0, pieceData3, 0, FAKE_SIGNATURE, keys3, values3); // Test 4: Sufficient funds with large pieces (above minimum rate) address client4 = makeAddr("client4"); uint256 sufficientAmount = 10e16; // 0.10 USDFC mockUSDFC.safeTransfer(client4, sufficientAmount); - + vm.startPrank(client4); payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e18, 1000e18, 365 days); mockUSDFC.approve(address(payments), sufficientAmount); @@ -1360,11 +1340,7 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest { string[] memory values4 = new string[](MAX_KEYS_PER_PIECE); for (uint256 p = 0; p < totalPieces; p++) { - pieceData4[p] = Cids.CommPv2FromDigest( - 0, - 28, - keccak256(abi.encodePacked("file", Strings.toString(p))) - ); + pieceData4[p] = Cids.CommPv2FromDigest(0, 28, keccak256(abi.encodePacked("file", Strings.toString(p)))); } for (uint256 k = 0; k < MAX_KEYS_PER_PIECE; k++) { keys4[k] = _generateKey(k); @@ -1373,19 +1349,9 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest { makeSignaturePass(client4); vm.prank(serviceProvider); - mockPDPVerifier.addPieces( - pdpServiceWithPayments, - dataSetId4, - 0, - pieceData4, - 0, - FAKE_SIGNATURE, - keys4, - values4 - ); + mockPDPVerifier.addPieces(pdpServiceWithPayments, dataSetId4, 0, pieceData4, 0, FAKE_SIGNATURE, keys4, values4); } - // Operator Approval Validation Tests function testOperatorApproval_NotApproved() public { // Setup: Client with sufficient funds but no operator approval @@ -1502,7 +1468,7 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest { // DEFAULT_LOCKUP_PERIOD = 86400 // EPOCHS_PER_MONTH = 86400 // minimumLockupRequired = (6e16 * 86400) / 86400 = 6e16 - uint256 minimumLockupRatePerEpoch = (uint256(6e16) / (2880*30)); + uint256 minimumLockupRatePerEpoch = (uint256(6e16) / (2880 * 30)); uint256 minimumLockupRequired = minimumLockupRatePerEpoch * (2880 * 30); uint256 insufficientLockupAllowance = minimumLockupRequired - 1; // Just below minimum