Skip to content

Commit 5062f41

Browse files
committed
feat: floor price set to the equivalent of 24 GiB
Amounts below this pay as if they are storing 24 GiB, which is 0.05859375 USDFC / month. Closes: #319
1 parent 5c5d22b commit 5062f41

File tree

5 files changed

+163
-58
lines changed

5 files changed

+163
-58
lines changed

service_contracts/src/FilecoinWarmStorageService.sol

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ contract FilecoinWarmStorageService is
177177
uint256 private constant GIB_IN_BYTES = MIB_IN_BYTES * 1024; // 1 GiB in bytes
178178
uint256 private constant TIB_IN_BYTES = GIB_IN_BYTES * 1024; // 1 TiB in bytes
179179
uint256 private constant EPOCHS_PER_MONTH = 2880 * 30;
180+
uint256 private constant MINIMUM_PRICING_THRESHOLD_BYTES = 24 * GIB_IN_BYTES; // 24 GiB minimum pricing threshold
180181

181182
// Metadata size and count limits
182183
uint256 private constant MAX_KEY_LENGTH = 32;
@@ -1217,7 +1218,8 @@ contract FilecoinWarmStorageService is
12171218

12181219
/**
12191220
* @notice Calculate all per-epoch rates based on total storage size
1220-
* @dev Returns storage, cache miss, and CDN rates per TiB per month
1221+
* @dev Returns storage, cache miss, and CDN rates per TiB per month.
1222+
* Storage rate includes minimum pricing floor.
12211223
* @param totalBytes Total size of the stored data in bytes
12221224
* @return storageRate The PDP storage rate per epoch
12231225
* @return cacheMissRate The cache miss rate per epoch
@@ -1228,17 +1230,26 @@ contract FilecoinWarmStorageService is
12281230
view
12291231
returns (uint256 storageRate, uint256 cacheMissRate, uint256 cdnRate)
12301232
{
1231-
storageRate = calculateStorageSizeBasedRatePerEpoch(totalBytes, STORAGE_PRICE_PER_TIB_PER_MONTH);
1233+
storageRate = _calculateStorageRate(totalBytes);
12321234
cacheMissRate = calculateStorageSizeBasedRatePerEpoch(totalBytes, CACHE_MISS_PRICE_PER_TIB_PER_MONTH);
12331235
cdnRate = calculateStorageSizeBasedRatePerEpoch(totalBytes, CDN_PRICE_PER_TIB_PER_MONTH);
12341236
}
12351237

12361238
/**
12371239
* @notice Calculate the storage rate per epoch (internal use)
1240+
* @dev Implements minimum pricing floor: data sets smaller than 24 GiB are charged
1241+
* the same rate as a 24 GiB data set
12381242
* @param totalBytes Total size of the stored data in bytes
12391243
* @return The storage rate per epoch
12401244
*/
12411245
function _calculateStorageRate(uint256 totalBytes) internal view returns (uint256) {
1246+
// Apply minimum pricing floor: data sets < 24 GiB pay the same as 24 GiB
1247+
if (totalBytes < MINIMUM_PRICING_THRESHOLD_BYTES) {
1248+
return calculateStorageSizeBasedRatePerEpoch(
1249+
MINIMUM_PRICING_THRESHOLD_BYTES,
1250+
STORAGE_PRICE_PER_TIB_PER_MONTH
1251+
);
1252+
}
12421253
return calculateStorageSizeBasedRatePerEpoch(totalBytes, STORAGE_PRICE_PER_TIB_PER_MONTH);
12431254
}
12441255

service_contracts/test/FilecoinWarmStorageService.t.sol

Lines changed: 135 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -557,13 +557,13 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
557557
mockUSDFC,
558558
address(pdpServiceWithPayments),
559559
true, // approved
560-
1000e6, // rate allowance (1000 USDFC)
561-
1000e6, // lockup allowance (1000 USDFC)
560+
1000e18, // rate allowance (1000 USDFC)
561+
1000e18, // lockup allowance (1000 USDFC)
562562
365 days // max lockup period
563563
);
564564

565565
// Client deposits funds to the FilecoinPayV1 contract for future payments
566-
uint256 depositAmount = 10e6; // Sufficient funds for initial lockup and future operations
566+
uint256 depositAmount = 10e18; // Sufficient funds for initial lockup and future operations
567567
mockUSDFC.approve(address(payments), depositAmount);
568568
payments.deposit(mockUSDFC, client, depositAmount);
569569
vm.stopPrank();
@@ -682,13 +682,13 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
682682
mockUSDFC,
683683
address(pdpServiceWithPayments),
684684
true, // approved
685-
1000e6, // rate allowance (1000 USDFC)
686-
1000e6, // lockup allowance (1000 USDFC)
685+
1000e18, // rate allowance (1000 USDFC)
686+
1000e18, // lockup allowance (1000 USDFC)
687687
365 days // max lockup period
688688
);
689689

690690
// Client deposits funds to the FilecoinPayV1 contract for future payments
691-
uint256 depositAmount = 10e6; // Sufficient funds for initial lockup and future operations
691+
uint256 depositAmount = 10e18; // Sufficient funds for initial lockup and future operations
692692
mockUSDFC.approve(address(payments), depositAmount);
693693
payments.deposit(mockUSDFC, client, depositAmount);
694694
vm.stopPrank();
@@ -795,11 +795,11 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
795795
mockUSDFC,
796796
address(pdpServiceWithPayments),
797797
true, // approved
798-
1000e6, // rate allowance (1000 USDFC)
799-
1000e6, // lockup allowance (1000 USDFC)
798+
1000e18, // rate allowance (1000 USDFC)
799+
1000e18, // lockup allowance (1000 USDFC)
800800
365 days // max lockup period
801801
);
802-
uint256 depositAmount = 10e6; // Sufficient funds for initial lockup and future operations
802+
uint256 depositAmount = 10e18; // Sufficient funds for initial lockup and future operations
803803
mockUSDFC.approve(address(payments), depositAmount);
804804
payments.deposit(mockUSDFC, client, depositAmount);
805805
vm.stopPrank();
@@ -909,38 +909,132 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
909909
// Test the values returned by getServicePrice
910910
FilecoinWarmStorageService.ServicePricing memory pricing = pdpServiceWithPayments.getServicePrice();
911911

912-
uint256 decimals = 6; // MockUSDFC uses 6 decimals in tests
913-
uint256 expectedNoCDN = 25 * 10 ** (decimals - 1); // 2.5 USDFC with 6 decimals
914-
uint256 expectedWithCDN = 3 * 10 ** decimals; // 3 USDFC with 6 decimals (2.5 + 0.5 CDN)
912+
uint256 decimals = 18; // MockUSDFC uses 18 decimals in tests
913+
uint256 expectedNoCDN = 25 * 10 ** (decimals - 1); // 2.5 USDFC with 18 decimals
914+
uint256 expectedWithCDN = 3 * 10 ** decimals; // 3 USDFC with 18 decimals (2.5 + 0.5 CDN)
915915

916916
assertEq(pricing.pricePerTiBPerMonthNoCDN, expectedNoCDN, "No CDN price should be 2.5 * 10^decimals");
917917
assertEq(pricing.pricePerTiBPerMonthWithCDN, expectedWithCDN, "With CDN price should be 3 * 10^decimals");
918918
assertEq(address(pricing.tokenAddress), address(mockUSDFC), "Token address should match USDFC");
919919
assertEq(pricing.epochsPerMonth, 86400, "Epochs per month should be 86400");
920920

921921
// Verify the values are in expected range
922-
assert(pricing.pricePerTiBPerMonthNoCDN < 10 ** 8); // Less than 10^8
923-
assert(pricing.pricePerTiBPerMonthWithCDN < 10 ** 8); // Less than 10^8
922+
assert(pricing.pricePerTiBPerMonthNoCDN < 10 ** 20); // Less than 10^20
923+
assert(pricing.pricePerTiBPerMonthWithCDN < 10 ** 20); // Less than 10^20
924924
}
925925

926926
function testGetEffectiveRatesValues() public view {
927927
// Test the values returned by getEffectiveRates
928928
(uint256 serviceFee, uint256 spPayment) = pdpServiceWithPayments.getEffectiveRates();
929929

930-
uint256 decimals = 6; // MockUSDFC uses 6 decimals in tests
931-
// Total is 2.5 USDFC with 6 decimals
930+
uint256 decimals = 18; // MockUSDFC uses 18 decimals in tests
931+
// Total is 2.5 USDFC with 18 decimals
932932
uint256 expectedTotal = 25 * 10 ** (decimals - 1);
933933

934934
// Test setup uses 0% commission
935935
uint256 expectedServiceFee = 0; // 0% commission
936936
uint256 expectedSpPayment = expectedTotal; // 100% goes to SP
937937

938938
assertEq(serviceFee, expectedServiceFee, "Service fee should be 0 with 0% commission");
939-
assertEq(spPayment, expectedSpPayment, "SP payment should be 2.5 * 10^6");
940-
assertEq(serviceFee + spPayment, expectedTotal, "Total should equal 2.5 * 10^6");
939+
assertEq(spPayment, expectedSpPayment, "SP payment should be 2.5 * 10^18");
940+
assertEq(serviceFee + spPayment, expectedTotal, "Total should equal 2.5 * 10^18");
941941

942942
// Verify the values are in expected range
943-
assert(serviceFee + spPayment < 10 ** 8); // Less than 10^8
943+
assert(serviceFee + spPayment < 10 ** 20); // Less than 10^20
944+
}
945+
946+
// Minimum Pricing Tests
947+
function testCalculateRatesPerEpoch_BelowMinimumThreshold() public view {
948+
// Test various sizes below 24 GiB threshold
949+
uint256 oneGiB = 1024 * 1024 * 1024;
950+
951+
// Calculate expected minimum rate (24 GiB at 2.5 USDFC/TiB/month)
952+
// 24 GiB / 1024 GiB * 2.5 USDFC = 0.05859375 USDFC/month
953+
uint256 twentyFourGiB = 24 * oneGiB;
954+
(uint256 expectedMinRate,,) = pdpServiceWithPayments.calculateRatesPerEpoch(twentyFourGiB);
955+
956+
// Test 0 bytes
957+
(uint256 rateZero,,) = pdpServiceWithPayments.calculateRatesPerEpoch(0);
958+
assertEq(rateZero, expectedMinRate, "0 bytes should return minimum rate");
959+
960+
// Test 1 GiB
961+
(uint256 rateOneGiB,,) = pdpServiceWithPayments.calculateRatesPerEpoch(oneGiB);
962+
assertEq(rateOneGiB, expectedMinRate, "1 GiB should return minimum rate");
963+
964+
// Test 12 GiB (half of threshold)
965+
(uint256 rateTwelveGiB,,) = pdpServiceWithPayments.calculateRatesPerEpoch(12 * oneGiB);
966+
assertEq(rateTwelveGiB, expectedMinRate, "12 GiB should return minimum rate");
967+
968+
// Test 23 GiB (just below threshold)
969+
(uint256 rateTwentyThreeGiB,,) = pdpServiceWithPayments.calculateRatesPerEpoch(23 * oneGiB);
970+
assertEq(rateTwentyThreeGiB, expectedMinRate, "23 GiB should return minimum rate");
971+
}
972+
973+
function testCalculateRatesPerEpoch_AtMinimumThreshold() public view {
974+
// Test exactly at 24 GiB threshold
975+
uint256 oneGiB = 1024 * 1024 * 1024;
976+
uint256 twentyFourGiB = 24 * oneGiB;
977+
978+
(uint256 rateAtThreshold,,) = pdpServiceWithPayments.calculateRatesPerEpoch(twentyFourGiB);
979+
980+
// At exactly 24 GiB, minimum pricing and proportional pricing should yield the same rate
981+
// This is the boundary condition
982+
assert(rateAtThreshold > 0);
983+
}
984+
985+
function testCalculateRatesPerEpoch_AboveMinimumThreshold() public view {
986+
// Test sizes above 24 GiB threshold - should use proportional pricing
987+
uint256 oneGiB = 1024 * 1024 * 1024;
988+
uint256 twentyFourGiB = 24 * oneGiB;
989+
990+
(uint256 minRate,,) = pdpServiceWithPayments.calculateRatesPerEpoch(twentyFourGiB);
991+
992+
// Test 25 GiB (just above threshold)
993+
(uint256 rateTwentyFiveGiB,,) = pdpServiceWithPayments.calculateRatesPerEpoch(25 * oneGiB);
994+
assert(rateTwentyFiveGiB > minRate);
995+
996+
// Test 48 GiB (double the threshold)
997+
(uint256 rateFourtyEightGiB,,) = pdpServiceWithPayments.calculateRatesPerEpoch(48 * oneGiB);
998+
assert(rateFourtyEightGiB > rateTwentyFiveGiB);
999+
1000+
// Test 1 TiB (much larger)
1001+
uint256 oneTiB = oneGiB * 1024;
1002+
(uint256 rateOneTiB,,) = pdpServiceWithPayments.calculateRatesPerEpoch(oneTiB);
1003+
assert(rateOneTiB > rateFourtyEightGiB);
1004+
}
1005+
1006+
function testMinimumPricing_ApproximatelyPoint06USDFC() public view {
1007+
// Verify that minimum pricing is approximately 0.06 USDFC/month
1008+
uint256 decimals = 18; // MockUSDFC uses 18 decimals in tests
1009+
uint256 oneGiB = 1024 * 1024 * 1024;
1010+
1011+
// Get rate per epoch for dataset below threshold
1012+
(uint256 ratePerEpoch,,) = pdpServiceWithPayments.calculateRatesPerEpoch(oneGiB);
1013+
1014+
// Convert to rate per month (86400 epochs per month)
1015+
uint256 ratePerMonth = ratePerEpoch * 86400;
1016+
1017+
// Expected: approximately 0.06 USDFC with 18 decimals = 60000000000000000
1018+
// Actual at 2.5 USDFC/TiB: 24/1024 * 2.5 = 0.05859375 USDFC = 58593750000000000 (with 18 decimals)
1019+
uint256 expectedApprox = 6 * 10 ** (decimals - 2); // 60000000000000000
1020+
uint256 tolerance = 2 * 10 ** (decimals - 2); // Allow 0.02 USDFC tolerance
1021+
1022+
assertApproxEqAbs(ratePerMonth, expectedApprox, tolerance, "Minimum rate should be ~0.06 USDFC/month");
1023+
}
1024+
1025+
function testMinimumPricing_CDNRatesUnaffected() public view {
1026+
// Verify that CDN rates remain proportional (not affected by minimum pricing)
1027+
uint256 oneGiB = 1024 * 1024 * 1024;
1028+
1029+
// Get rates for small dataset
1030+
(, uint256 cacheMissRateSmall, uint256 cdnRateSmall) = pdpServiceWithPayments.calculateRatesPerEpoch(oneGiB);
1031+
1032+
// Get rates for larger dataset (12 GiB)
1033+
(, uint256 cacheMissRateLarge, uint256 cdnRateLarge) = pdpServiceWithPayments.calculateRatesPerEpoch(12 * oneGiB);
1034+
1035+
// CDN rates should scale proportionally with size (allow small rounding tolerance)
1036+
assertApproxEqAbs(cacheMissRateLarge, cacheMissRateSmall * 12, 100, "Cache miss rate should scale proportionally");
1037+
assertApproxEqAbs(cdnRateLarge, cdnRateSmall * 12, 100, "CDN rate should scale proportionally");
9441038
}
9451039

9461040
uint256 nextClientDataSetId = 0;
@@ -971,9 +1065,9 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
9711065

9721066
// Setup client payment approval if not already done
9731067
vm.startPrank(clientAddress);
974-
payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e6, 1000e6, 365 days);
975-
mockUSDFC.approve(address(payments), 100e6);
976-
payments.deposit(mockUSDFC, clientAddress, 100e6);
1068+
payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e18, 1000e18, 365 days);
1069+
mockUSDFC.approve(address(payments), 100e18);
1070+
payments.deposit(mockUSDFC, clientAddress, 100e18);
9771071
vm.stopPrank();
9781072

9791073
// Create data set as approved provider
@@ -1157,9 +1251,9 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
11571251

11581252
// Setup client payment approval if not already done
11591253
vm.startPrank(clientAddress);
1160-
payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e6, 1000e6, 365 days);
1161-
mockUSDFC.approve(address(payments), 100e6);
1162-
payments.deposit(mockUSDFC, clientAddress, 100e6);
1254+
payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e18, 1000e18, 365 days);
1255+
mockUSDFC.approve(address(payments), 100e18);
1256+
payments.deposit(mockUSDFC, clientAddress, 100e18);
11631257
vm.stopPrank();
11641258

11651259
// Create data set as approved provider
@@ -1302,11 +1396,11 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
13021396
mockUSDFC,
13031397
address(pdpServiceWithPayments),
13041398
true,
1305-
1000e6, // rate allowance
1306-
1000e6, // lockup allowance
1399+
1000e18, // rate allowance
1400+
1000e18, // lockup allowance
13071401
365 days // max lockup period
13081402
);
1309-
uint256 depositAmount = 100e6;
1403+
uint256 depositAmount = 100e18;
13101404
mockUSDFC.approve(address(payments), depositAmount);
13111405
payments.deposit(mockUSDFC, client, depositAmount);
13121406
vm.stopPrank();
@@ -1482,11 +1576,11 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
14821576
mockUSDFC,
14831577
address(pdpServiceWithPayments),
14841578
true,
1485-
1000e6, // rate allowance
1486-
1000e6, // lockup allowance
1579+
1000e18, // rate allowance
1580+
1000e18, // lockup allowance
14871581
365 days // max lockup period
14881582
);
1489-
uint256 depositAmount = 100e6;
1583+
uint256 depositAmount = 100e18;
14901584
mockUSDFC.approve(address(payments), depositAmount);
14911585
payments.deposit(mockUSDFC, client, depositAmount);
14921586
vm.stopPrank();
@@ -1603,11 +1697,11 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
16031697
mockUSDFC,
16041698
address(pdpServiceWithPayments),
16051699
true,
1606-
1000e6, // rate allowance
1607-
1000e6, // lockup allowance
1700+
1000e18, // rate allowance
1701+
1000e18, // lockup allowance
16081702
365 days // max lockup period
16091703
);
1610-
uint256 depositAmount = 100e6;
1704+
uint256 depositAmount = 100e18;
16111705
mockUSDFC.approve(address(payments), depositAmount);
16121706
payments.deposit(mockUSDFC, client, depositAmount);
16131707
vm.stopPrank();
@@ -2814,8 +2908,8 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
28142908
);
28152909

28162910
vm.startPrank(client);
2817-
payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e6, 1000e6, 365 days);
2818-
uint256 depositAmount = 1e6;
2911+
payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e18, 1000e18, 365 days);
2912+
uint256 depositAmount = 1e18;
28192913
mockUSDFC.approve(address(payments), depositAmount);
28202914
payments.deposit(mockUSDFC, client, depositAmount);
28212915
vm.stopPrank();
@@ -2867,8 +2961,8 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
28672961
);
28682962

28692963
vm.startPrank(client);
2870-
payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e6, 1000e6, 365 days);
2871-
uint256 depositAmount = 1e6;
2964+
payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e18, 1000e18, 365 days);
2965+
uint256 depositAmount = 1e18;
28722966
mockUSDFC.approve(address(payments), depositAmount);
28732967
payments.deposit(mockUSDFC, client, depositAmount);
28742968
vm.stopPrank();
@@ -2918,8 +3012,8 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
29183012
);
29193013

29203014
vm.startPrank(client);
2921-
payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e6, 1000e6, 365 days);
2922-
uint256 depositAmount = 10e6;
3015+
payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e18, 1000e18, 365 days);
3016+
uint256 depositAmount = 10e18;
29233017
mockUSDFC.approve(address(payments), depositAmount);
29243018
payments.deposit(mockUSDFC, client, depositAmount);
29253019
vm.stopPrank();
@@ -3972,7 +4066,7 @@ contract FilecoinWarmStorageServiceSignatureTest is Test {
39724066
pdpService = SignatureCheckingService(address(serviceProxy));
39734067

39744068
// Fund the payer
3975-
mockUSDFC.safeTransfer(payer, 1000 * 10 ** 6); // 1000 USDFC
4069+
mockUSDFC.safeTransfer(payer, 1000 * 10 ** 18); // 1000 USDFC
39764070
}
39774071

39784072
// Test the recoverSigner function indirectly through signature verification

service_contracts/test/FilecoinWarmStorageServiceOwner.t.sol

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ contract FilecoinWarmStorageServiceOwnerTest is MockFVMTest {
124124
serviceContract.addApprovedProvider(providerId3);
125125

126126
// Setup USDFC tokens for client
127-
usdfcToken.safeTransfer(client, 10000e6);
127+
usdfcToken.safeTransfer(client, 10000e18);
128128

129129
// Make signatures pass
130130
makeSignaturePass(client);
@@ -190,9 +190,9 @@ contract FilecoinWarmStorageServiceOwnerTest is MockFVMTest {
190190

191191
// Setup payment approval
192192
vm.startPrank(payer);
193-
payments.setOperatorApproval(usdfcToken, address(serviceContract), true, 1000e6, 1000e6, 365 days);
194-
usdfcToken.approve(address(payments), 100e6);
195-
payments.deposit(usdfcToken, payer, 100e6);
193+
payments.setOperatorApproval(usdfcToken, address(serviceContract), true, 1000e18, 1000e18, 365 days);
194+
usdfcToken.approve(address(payments), 100e18);
195+
payments.deposit(usdfcToken, payer, 100e18);
196196
vm.stopPrank();
197197

198198
// Create data set

0 commit comments

Comments
 (0)