Skip to content

Commit d27dcf0

Browse files
committed
feat(piecesAdded):validatePayerFunds
1 parent 08f17e9 commit d27dcf0

File tree

3 files changed

+157
-9
lines changed

3 files changed

+157
-9
lines changed

service_contracts/src/FilecoinWarmStorageService.sol

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
pragma solidity ^0.8.20;
33

44
import {PDPListener} from "@pdp/PDPVerifier.sol";
5+
import {IPDPVerifier} from "@pdp/interfaces/IPDPVerifier.sol";
56
import {Cids} from "@pdp/Cids.sol";
67
import {SessionKeyRegistry} from "@session-key-registry/SessionKeyRegistry.sol";
78
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
@@ -834,6 +835,13 @@ contract FilecoinWarmStorageService is
834835
// Verify the signature
835836
verifyAddPiecesSignature(payer, info.clientDataSetId, pieceData, nonce, metadataKeys, metadataValues, signature);
836837

838+
// Validate payer/operator approvals and available funds for the new pieces
839+
// This checks the payer has sufficient available funds and operator allowances
840+
// to cover the increased per-epoch rate and the 30-day lockup implied by the
841+
// new total leaf count after adding these pieces.
842+
FilecoinPayV1 payments = FilecoinPayV1(paymentsContractAddress);
843+
validatePayerOperatorApprovalAndFundsForPieces(payments, payer, dataSetId, pieceData);
844+
837845
// Store metadata for each new piece
838846
for (uint256 i = 0; i < pieceData.length; i++) {
839847
uint256 pieceId = firstAdded + i;
@@ -873,6 +881,57 @@ contract FilecoinWarmStorageService is
873881
}
874882
}
875883

884+
/// @notice Validate payer/operator approvals and funds for adding pieces
885+
/// @dev Computes the new storage rate and corresponding 30-day lockup after adding `pieceData`
886+
/// and validates the payer has sufficient available funds and operator allowances.
887+
function validatePayerOperatorApprovalAndFundsForPieces(
888+
FilecoinPayV1 payments,
889+
address payer,
890+
uint256 dataSetId,
891+
Cids.Cid[] memory pieceData
892+
) internal view {
893+
// Get existing leaf count from the PDP verifier
894+
uint256 leaves = IPDPVerifier(pdpVerifierAddress).getDataSetLeafCount(dataSetId);
895+
896+
uint256 totalBytes = leaves * BYTES_PER_LEAF;
897+
uint256 storageRatePerEpoch = _calculateStorageRate(totalBytes);
898+
uint256 lockupRequired = storageRatePerEpoch * DEFAULT_LOCKUP_PERIOD;
899+
900+
// Check available funds
901+
(,, uint256 availableFunds,) = payments.getAccountInfoIfSettled(usdfcTokenAddress, payer);
902+
require(availableFunds >= lockupRequired, Errors.InsufficientLockupFunds(payer, lockupRequired, availableFunds));
903+
904+
// Check operator approvals for this contract
905+
(
906+
bool isApproved,
907+
uint256 rateAllowance,
908+
uint256 lockupAllowance,
909+
uint256 rateUsage,
910+
uint256 lockupUsage,
911+
uint256 maxLockupPeriod
912+
) = payments.operatorApprovals(usdfcTokenAddress, payer, address(this));
913+
914+
require(isApproved, Errors.OperatorNotApproved(payer, address(this)));
915+
916+
// Verify rate allowance is sufficient for the new per-epoch rate
917+
require(
918+
rateAllowance >= rateUsage + storageRatePerEpoch,
919+
Errors.InsufficientRateAllowance(payer, address(this), rateAllowance, rateUsage, storageRatePerEpoch)
920+
);
921+
922+
// Verify lockup allowance is sufficient for the new 30-day lockup amount
923+
require(
924+
lockupAllowance >= lockupUsage + lockupRequired,
925+
Errors.InsufficientLockupAllowance(payer, address(this), lockupAllowance, lockupUsage, lockupRequired)
926+
);
927+
928+
// Verify max lockup period
929+
require(
930+
maxLockupPeriod >= DEFAULT_LOCKUP_PERIOD,
931+
Errors.InsufficientMaxLockupPeriod(payer, address(this), maxLockupPeriod, DEFAULT_LOCKUP_PERIOD)
932+
);
933+
}
934+
876935
function piecesScheduledRemove(uint256 dataSetId, uint256[] memory pieceIds, bytes calldata extraData)
877936
external
878937
onlyPDPVerifier

service_contracts/test/FilecoinWarmStorageService.t.sol

Lines changed: 70 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -803,9 +803,9 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
803803

804804
// First batch (3 pieces) with key "meta" => metadataShort
805805
Cids.Cid[] memory pieceData1 = new Cids.Cid[](3);
806-
pieceData1[0].data = bytes("1_0:1111");
807-
pieceData1[1].data = bytes("1_1:111100000");
808-
pieceData1[2].data = bytes("1_2:11110000000000");
806+
pieceData1[0] = Cids.CommPv2FromDigest(0, 4, keccak256(abi.encodePacked("1_0:1111")));
807+
pieceData1[1] = Cids.CommPv2FromDigest(0, 4, keccak256(abi.encodePacked("1_1:111100000")));
808+
pieceData1[2] = Cids.CommPv2FromDigest(0, 4, keccak256(abi.encodePacked("1_2:11110000000000")));
809809
string[] memory keys1 = new string[](1);
810810
string[] memory values1 = new string[](1);
811811
keys1[0] = "meta";
@@ -817,8 +817,10 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
817817

818818
// Second batch (2 pieces) with key "meta" => metadataLong
819819
Cids.Cid[] memory pieceData2 = new Cids.Cid[](2);
820-
pieceData2[0].data = bytes("2_0:22222222222222222222");
821-
pieceData2[1].data = bytes("2_1:222222222222222222220000000000000000000000000000000000000000");
820+
pieceData2[0] = Cids.CommPv2FromDigest(0, 4, keccak256(abi.encodePacked("2_0:22222222222222222222")));
821+
pieceData2[1] = Cids.CommPv2FromDigest(
822+
0, 4, keccak256(abi.encodePacked("2_1:222222222222222222220000000000000000000000000000000000000000000"))
823+
);
822824
string[] memory keys2 = new string[](1);
823825
string[] memory values2 = new string[](1);
824826
keys2[0] = "meta";
@@ -1161,6 +1163,65 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
11611163
assertEq(dataSetId, 1, "Dataset should be created with above-minimum funds");
11621164
}
11631165

1166+
// function testInsufficientFunds_AddPieces() public {
1167+
// // Setup: Client with exactly the minimum funds (0.06 USDFC)
1168+
// address exactClient = makeAddr("exactClient");
1169+
// uint256 exactAmount = 6e16; // Exactly 0.06 USDFC
1170+
1171+
// // Transfer tokens from test contract to the test client
1172+
// mockUSDFC.safeTransfer(exactClient, exactAmount);
1173+
1174+
// vm.startPrank(exactClient);
1175+
// payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e18, 1000e18, 365 days);
1176+
// mockUSDFC.approve(address(payments), exactAmount);
1177+
// payments.deposit(mockUSDFC, exactClient, exactAmount);
1178+
// vm.stopPrank();
1179+
1180+
// // Prepare dataset creation data
1181+
// (string[] memory dsKeys, string[] memory dsValues) = _getSingleMetadataKV("label", "Exact Minimum Test");
1182+
// FilecoinWarmStorageService.DataSetCreateData memory createData = FilecoinWarmStorageService.DataSetCreateData({
1183+
// payer: exactClient,
1184+
// clientDataSetId: 1000,
1185+
// metadataKeys: dsKeys,
1186+
// metadataValues: dsValues,
1187+
// signature: FAKE_SIGNATURE
1188+
// });
1189+
1190+
// bytes memory encodedCreateData = abi.encode(
1191+
// createData.payer,
1192+
// createData.clientDataSetId,
1193+
// createData.metadataKeys,
1194+
// createData.metadataValues,
1195+
// createData.signature
1196+
// );
1197+
1198+
// // Should succeed with exact minimum
1199+
// makeSignaturePass(exactClient);
1200+
// vm.prank(serviceProvider);
1201+
// uint256 dataSetId = mockPDPVerifier.createDataSet(pdpServiceWithPayments, encodedCreateData);
1202+
1203+
// vm.prank(exactClient);
1204+
// payments.withdraw(mockUSDFC, 59999999999961600); // Withdraw all funds, leaving 0 balance
1205+
1206+
// // Prepare piece addition data
1207+
// Cids.Cid[] memory pieceData = new Cids.Cid[](1);
1208+
// pieceData[0] = Cids.CommPv2FromDigest(0, 4, keccak256(abi.encodePacked("piece:belowMinimum")));
1209+
// string[] memory keys = new string[](0);
1210+
// string[] memory values = new string[](0);
1211+
// uint256 firstAdded = 0;
1212+
// // Expect revert when adding pieces due to insufficient funds
1213+
// makeSignaturePass(exactClient);
1214+
// vm.expectRevert(
1215+
// abi.encodeWithSelector(
1216+
// Errors.InsufficientLockupFunds.selector, exactClient, 6e16, 5e16
1217+
// )
1218+
// );
1219+
// vm.prank(serviceProvider);
1220+
// mockPDPVerifier.addPieces(
1221+
// pdpServiceWithPayments, dataSetId, firstAdded, pieceData, 0, FAKE_SIGNATURE, keys, values
1222+
// );
1223+
// }
1224+
11641225
// Operator Approval Validation Tests
11651226
function testOperatorApproval_NotApproved() public {
11661227
// Setup: Client with sufficient funds but no operator approval
@@ -4558,7 +4619,7 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
45584619

45594620
// Prepare piece data
45604621
Cids.Cid[] memory pieceData = new Cids.Cid[](1);
4561-
pieceData[0].data = bytes("test_piece_1");
4622+
pieceData[0] = Cids.CommPv2FromDigest(0, 4, keccak256(abi.encodePacked("test_piece_1")));
45624623
string[] memory keys = new string[](0);
45634624
string[] memory values = new string[](0);
45644625

@@ -4604,7 +4665,7 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
46044665

46054666
// Prepare piece data
46064667
Cids.Cid[] memory pieceData = new Cids.Cid[](1);
4607-
pieceData[0].data = bytes("test_piece");
4668+
pieceData[0] = Cids.CommPv2FromDigest(0, 4, keccak256(abi.encodePacked("test_piece_1")));
46084669
string[] memory keys = new string[](0);
46094670
string[] memory values = new string[](0);
46104671

@@ -4678,7 +4739,7 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
46784739

46794740
// Prepare piece data
46804741
Cids.Cid[] memory pieceData = new Cids.Cid[](1);
4681-
pieceData[0].data = bytes("test");
4742+
pieceData[0] = Cids.CommPv2FromDigest(0, 4, keccak256(abi.encodePacked("test_piece_1")));
46824743
string[] memory keys = new string[](0);
46834744
string[] memory values = new string[](0);
46844745

@@ -4725,7 +4786,7 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
47254786

47264787
// Prepare piece data
47274788
Cids.Cid[] memory pieceData = new Cids.Cid[](1);
4728-
pieceData[0].data = bytes("test");
4789+
pieceData[0] = Cids.CommPv2FromDigest(0, 4, keccak256(abi.encodePacked("test_piece_1")));
47294790
string[] memory keys = new string[](0);
47304791
string[] memory values = new string[](0);
47314792

service_contracts/test/mocks/SharedMocks.sol

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ contract MockPDPVerifier {
9999

100100
// Track data set service providers for testing
101101
mapping(uint256 => address) public dataSetServiceProviders;
102+
// Track simple leaf counts per data set for tests (approximate via bytes length)
103+
mapping(uint256 => uint256) public dataSetLeafCount;
102104

103105
event DataSetCreated(uint256 indexed setId, address indexed owner);
104106
event DataSetServiceProviderChanged(
@@ -118,6 +120,9 @@ contract MockPDPVerifier {
118120
// Track service provider
119121
dataSetServiceProviders[setId] = msg.sender;
120122

123+
// initialize leaf count to 0
124+
dataSetLeafCount[setId] = 0;
125+
121126
emit DataSetCreated(setId, msg.sender);
122127
return setId;
123128
}
@@ -128,6 +133,7 @@ contract MockPDPVerifier {
128133
}
129134

130135
delete dataSetServiceProviders[setId];
136+
delete dataSetLeafCount[setId];
131137
emit DataSetDeleted(setId, 0);
132138
}
133139

@@ -150,9 +156,31 @@ contract MockPDPVerifier {
150156
}
151157

152158
bytes memory extraData = abi.encode(nonce, allKeys, allValues, signature);
159+
160+
// // Update simple leaf count estimate for each piece using bytes length -> 32-byte leaves
161+
// uint256 addedLeaves = 0;
162+
// for (uint256 i = 0; i < pieceData.length; i++) {
163+
// uint256 dataLen = pieceData[i].data.length;
164+
// // estimate leaf count as ceil(dataLen / 32), minimum 1
165+
// uint256 leaves = dataLen == 0 ? 1 : (dataLen + 31) / 32;
166+
// addedLeaves += leaves;
167+
// }
168+
// dataSetLeafCount[dataSetId] += addedLeaves;
169+
uint256 leafCount = 0;
170+
for (uint256 i = 0; i < pieceData.length; i++) {
171+
(uint256 padding, uint8 height,) = Cids.validateCommPv2(pieceData[i]);
172+
leafCount += Cids.leafCount(padding, height);
173+
}
174+
dataSetLeafCount[dataSetId] += leafCount;
175+
153176
listenerAddr.piecesAdded(dataSetId, firstAdded, pieceData, extraData);
154177
}
155178

179+
// Expose leaf count similar to real PDPVerifier
180+
function getDataSetLeafCount(uint256 setId) external view returns (uint256) {
181+
return dataSetLeafCount[setId];
182+
}
183+
156184
/**
157185
* @notice Simulates service provider change for testing purposes
158186
* @dev This function mimics the PDPVerifier's claimDataSetOwnership functionality

0 commit comments

Comments
 (0)