Skip to content

Commit 5c05de6

Browse files
committed
feat: check payer has minimum available balance before creating data set
1 parent cf04515 commit 5c05de6

File tree

3 files changed

+158
-12
lines changed

3 files changed

+158
-12
lines changed

service_contracts/src/Errors.sol

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,4 +286,10 @@ library Errors {
286286
/// @param dataSetId The data set ID
287287
/// @param pdpEndEpoch The end epoch when the PDP payment rail will finalize
288288
error PaymentRailsNotFinalized(uint256 dataSetId, uint256 pdpEndEpoch);
289+
290+
/// @notice Payer has insufficient available funds to cover the minimum storage rate
291+
/// @param payer The payer address
292+
/// @param minimumRequired The minimum lockup required to cover the minimum storage rate
293+
/// @param available The available funds in the payer's account
294+
error InsufficientFundsForMinimumRate(address payer, uint256 minimumRequired, uint256 available);
289295
}

service_contracts/src/FilecoinWarmStorageService.sol

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,17 @@ contract FilecoinWarmStorageService is
587587

588588
// Create the payment rails using the FilecoinPayV1 contract
589589
FilecoinPayV1 payments = FilecoinPayV1(paymentsContractAddress);
590+
591+
// Pre-check that payer has sufficient available funds to cover minimum storage rate
592+
// This provides early feedback but doesn't guarantee funds will be available when SP calls nextProvingPeriod
593+
(,, uint256 availableFunds,) = payments.getAccountInfoIfSettled(usdfcTokenAddress, createData.payer);
594+
// Calculate required lockup: multiply first to preserve precision
595+
uint256 minimumLockupRequired = (MINIMUM_STORAGE_RATE_PER_MONTH * DEFAULT_LOCKUP_PERIOD) / EPOCHS_PER_MONTH;
596+
require(
597+
availableFunds >= minimumLockupRequired,
598+
Errors.InsufficientFundsForMinimumRate(createData.payer, minimumLockupRequired, availableFunds)
599+
);
600+
590601
uint256 pdpRailId = payments.createRail(
591602
usdfcTokenAddress, // token address
592603
createData.payer, // from (payer)

service_contracts/test/FilecoinWarmStorageService.t.sol

Lines changed: 141 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,6 +1046,135 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
10461046
}
10471047

10481048

1049+
// Minimum Funds Validation Tests
1050+
function testInsufficientFunds_BelowMinimum() public {
1051+
// Setup: Client with insufficient funds (below 0.06 USDFC minimum)
1052+
address insufficientClient = makeAddr("insufficientClient");
1053+
uint256 insufficientAmount = 5e16; // 0.05 USDFC (below 0.06 minimum)
1054+
1055+
// Transfer tokens from test contract to the test client
1056+
mockUSDFC.safeTransfer(insufficientClient, insufficientAmount);
1057+
1058+
vm.startPrank(insufficientClient);
1059+
payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e18, 1000e18, 365 days);
1060+
mockUSDFC.approve(address(payments), insufficientAmount);
1061+
payments.deposit(mockUSDFC, insufficientClient, insufficientAmount);
1062+
vm.stopPrank();
1063+
1064+
// Prepare dataset creation data
1065+
(string[] memory dsKeys, string[] memory dsValues) = _getSingleMetadataKV("label", "Insufficient Test");
1066+
FilecoinWarmStorageService.DataSetCreateData memory createData = FilecoinWarmStorageService.DataSetCreateData({
1067+
payer: insufficientClient,
1068+
clientDataSetId: 999,
1069+
metadataKeys: dsKeys,
1070+
metadataValues: dsValues,
1071+
signature: FAKE_SIGNATURE
1072+
});
1073+
1074+
bytes memory encodedCreateData = abi.encode(
1075+
createData.payer,
1076+
createData.clientDataSetId,
1077+
createData.metadataKeys,
1078+
createData.metadataValues,
1079+
createData.signature
1080+
);
1081+
1082+
// Expected minimum: (0.06 USDFC * 86400) / 86400 = 0.06 USDFC = 6e16
1083+
uint256 minimumRequired = 6e16;
1084+
1085+
// Expect revert with InsufficientFundsForMinimumRate error
1086+
makeSignaturePass(insufficientClient);
1087+
vm.expectRevert(
1088+
abi.encodeWithSelector(
1089+
Errors.InsufficientFundsForMinimumRate.selector, insufficientClient, minimumRequired, insufficientAmount
1090+
)
1091+
);
1092+
vm.prank(serviceProvider);
1093+
mockPDPVerifier.createDataSet(pdpServiceWithPayments, encodedCreateData);
1094+
}
1095+
1096+
function testInsufficientFunds_ExactMinimum() public {
1097+
// Setup: Client with exactly the minimum funds (0.06 USDFC)
1098+
address exactClient = makeAddr("exactClient");
1099+
uint256 exactAmount = 6e16; // Exactly 0.06 USDFC
1100+
1101+
// Transfer tokens from test contract to the test client
1102+
mockUSDFC.safeTransfer(exactClient, exactAmount);
1103+
1104+
vm.startPrank(exactClient);
1105+
payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e18, 1000e18, 365 days);
1106+
mockUSDFC.approve(address(payments), exactAmount);
1107+
payments.deposit(mockUSDFC, exactClient, exactAmount);
1108+
vm.stopPrank();
1109+
1110+
// Prepare dataset creation data
1111+
(string[] memory dsKeys, string[] memory dsValues) = _getSingleMetadataKV("label", "Exact Minimum Test");
1112+
FilecoinWarmStorageService.DataSetCreateData memory createData = FilecoinWarmStorageService.DataSetCreateData({
1113+
payer: exactClient,
1114+
clientDataSetId: 1000,
1115+
metadataKeys: dsKeys,
1116+
metadataValues: dsValues,
1117+
signature: FAKE_SIGNATURE
1118+
});
1119+
1120+
bytes memory encodedCreateData = abi.encode(
1121+
createData.payer,
1122+
createData.clientDataSetId,
1123+
createData.metadataKeys,
1124+
createData.metadataValues,
1125+
createData.signature
1126+
);
1127+
1128+
// Should succeed with exact minimum
1129+
makeSignaturePass(exactClient);
1130+
vm.prank(serviceProvider);
1131+
uint256 dataSetId = mockPDPVerifier.createDataSet(pdpServiceWithPayments, encodedCreateData);
1132+
1133+
// Verify dataset was created
1134+
assertEq(dataSetId, 1, "Dataset should be created with exact minimum funds");
1135+
}
1136+
1137+
function testInsufficientFunds_JustAboveMinimum() public {
1138+
// Setup: Client with slightly more than minimum (0.07 USDFC)
1139+
address aboveMinClient = makeAddr("aboveMinClient");
1140+
uint256 aboveMinAmount = 7e16; // 0.07 USDFC (just above 0.06 minimum)
1141+
1142+
// Transfer tokens from test contract to the test client
1143+
mockUSDFC.safeTransfer(aboveMinClient, aboveMinAmount);
1144+
1145+
vm.startPrank(aboveMinClient);
1146+
payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e18, 1000e18, 365 days);
1147+
mockUSDFC.approve(address(payments), aboveMinAmount);
1148+
payments.deposit(mockUSDFC, aboveMinClient, aboveMinAmount);
1149+
vm.stopPrank();
1150+
1151+
// Prepare dataset creation data
1152+
(string[] memory dsKeys, string[] memory dsValues) = _getSingleMetadataKV("label", "Above Minimum Test");
1153+
FilecoinWarmStorageService.DataSetCreateData memory createData = FilecoinWarmStorageService.DataSetCreateData({
1154+
payer: aboveMinClient,
1155+
clientDataSetId: 1001,
1156+
metadataKeys: dsKeys,
1157+
metadataValues: dsValues,
1158+
signature: FAKE_SIGNATURE
1159+
});
1160+
1161+
bytes memory encodedCreateData = abi.encode(
1162+
createData.payer,
1163+
createData.clientDataSetId,
1164+
createData.metadataKeys,
1165+
createData.metadataValues,
1166+
createData.signature
1167+
);
1168+
1169+
// Should succeed with funds above minimum
1170+
makeSignaturePass(aboveMinClient);
1171+
vm.prank(serviceProvider);
1172+
uint256 dataSetId = mockPDPVerifier.createDataSet(pdpServiceWithPayments, encodedCreateData);
1173+
1174+
// Verify dataset was created
1175+
assertEq(dataSetId, 1, "Dataset should be created with above-minimum funds");
1176+
}
1177+
10491178
uint256 nextClientDataSetId = 0;
10501179

10511180
// Client-Data Set Tracking Tests
@@ -4008,9 +4137,9 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
40084137

40094138
// Setup approvals and deposit
40104139
vm.startPrank(client);
4011-
payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e6, 1000e6, 365 days);
4012-
mockUSDFC.approve(address(payments), 10e6);
4013-
payments.deposit(mockUSDFC, client, 10e6);
4140+
payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e18, 1000e18, 365 days);
4141+
mockUSDFC.approve(address(payments), 10e18);
4142+
payments.deposit(mockUSDFC, client, 10e18);
40144143
vm.stopPrank();
40154144

40164145
// Create dataset
@@ -4054,9 +4183,9 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
40544183

40554184
// Setup approvals and deposit
40564185
vm.startPrank(client);
4057-
payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e6, 1000e6, 365 days);
4058-
mockUSDFC.approve(address(payments), 10e6);
4059-
payments.deposit(mockUSDFC, client, 10e6);
4186+
payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e18, 1000e18, 365 days);
4187+
mockUSDFC.approve(address(payments), 10e18);
4188+
payments.deposit(mockUSDFC, client, 10e18);
40604189
vm.stopPrank();
40614190

40624191
// Create dataset
@@ -4088,9 +4217,9 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
40884217
function testAddPiecesNonceUniquePerPayer() public {
40894218
// Setup approvals and deposit
40904219
vm.startPrank(client);
4091-
payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e6, 1000e6, 365 days);
4092-
mockUSDFC.approve(address(payments), 20e6);
4093-
payments.deposit(mockUSDFC, client, 20e6);
4220+
payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e18, 1000e18, 365 days);
4221+
mockUSDFC.approve(address(payments), 20e18);
4222+
payments.deposit(mockUSDFC, client, 20e18);
40944223
vm.stopPrank();
40954224

40964225
(string[] memory dsKeys, string[] memory dsValues) = _getSingleMetadataKV("label", "Dataset 1");
@@ -4157,9 +4286,9 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
41574286
function testNonceCannotBeReusedAcrossOperations() public {
41584287
// Setup: Approvals and deposit
41594288
vm.startPrank(client);
4160-
payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e6, 1000e6, 365 days);
4161-
mockUSDFC.approve(address(payments), 10e6);
4162-
payments.deposit(mockUSDFC, client, 10e6);
4289+
payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e18, 1000e18, 365 days);
4290+
mockUSDFC.approve(address(payments), 10e18);
4291+
payments.deposit(mockUSDFC, client, 10e18);
41634292
vm.stopPrank();
41644293

41654294
// Use nonce 777 to create a dataset

0 commit comments

Comments
 (0)