Skip to content

Commit 9e3ac3d

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 9e3ac3d

File tree

5 files changed

+164
-58
lines changed

5 files changed

+164
-58
lines changed

service_contracts/src/FilecoinWarmStorageService.sol

Lines changed: 11 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,24 @@ 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
1249+
calculateStorageSizeBasedRatePerEpoch(MINIMUM_PRICING_THRESHOLD_BYTES, STORAGE_PRICE_PER_TIB_PER_MONTH);
1250+
}
12421251
return calculateStorageSizeBasedRatePerEpoch(totalBytes, STORAGE_PRICE_PER_TIB_PER_MONTH);
12431252
}
12441253

service_contracts/test/FilecoinWarmStorageService.t.sol

Lines changed: 138 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,135 @@ 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) =
1034+
pdpServiceWithPayments.calculateRatesPerEpoch(12 * oneGiB);
1035+
1036+
// CDN rates should scale proportionally with size (allow small rounding tolerance)
1037+
assertApproxEqAbs(
1038+
cacheMissRateLarge, cacheMissRateSmall * 12, 100, "Cache miss rate should scale proportionally"
1039+
);
1040+
assertApproxEqAbs(cdnRateLarge, cdnRateSmall * 12, 100, "CDN rate should scale proportionally");
9441041
}
9451042

9461043
uint256 nextClientDataSetId = 0;
@@ -971,9 +1068,9 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
9711068

9721069
// Setup client payment approval if not already done
9731070
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);
1071+
payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e18, 1000e18, 365 days);
1072+
mockUSDFC.approve(address(payments), 100e18);
1073+
payments.deposit(mockUSDFC, clientAddress, 100e18);
9771074
vm.stopPrank();
9781075

9791076
// Create data set as approved provider
@@ -1157,9 +1254,9 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
11571254

11581255
// Setup client payment approval if not already done
11591256
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);
1257+
payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e18, 1000e18, 365 days);
1258+
mockUSDFC.approve(address(payments), 100e18);
1259+
payments.deposit(mockUSDFC, clientAddress, 100e18);
11631260
vm.stopPrank();
11641261

11651262
// Create data set as approved provider
@@ -1302,11 +1399,11 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
13021399
mockUSDFC,
13031400
address(pdpServiceWithPayments),
13041401
true,
1305-
1000e6, // rate allowance
1306-
1000e6, // lockup allowance
1402+
1000e18, // rate allowance
1403+
1000e18, // lockup allowance
13071404
365 days // max lockup period
13081405
);
1309-
uint256 depositAmount = 100e6;
1406+
uint256 depositAmount = 100e18;
13101407
mockUSDFC.approve(address(payments), depositAmount);
13111408
payments.deposit(mockUSDFC, client, depositAmount);
13121409
vm.stopPrank();
@@ -1482,11 +1579,11 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
14821579
mockUSDFC,
14831580
address(pdpServiceWithPayments),
14841581
true,
1485-
1000e6, // rate allowance
1486-
1000e6, // lockup allowance
1582+
1000e18, // rate allowance
1583+
1000e18, // lockup allowance
14871584
365 days // max lockup period
14881585
);
1489-
uint256 depositAmount = 100e6;
1586+
uint256 depositAmount = 100e18;
14901587
mockUSDFC.approve(address(payments), depositAmount);
14911588
payments.deposit(mockUSDFC, client, depositAmount);
14921589
vm.stopPrank();
@@ -1603,11 +1700,11 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
16031700
mockUSDFC,
16041701
address(pdpServiceWithPayments),
16051702
true,
1606-
1000e6, // rate allowance
1607-
1000e6, // lockup allowance
1703+
1000e18, // rate allowance
1704+
1000e18, // lockup allowance
16081705
365 days // max lockup period
16091706
);
1610-
uint256 depositAmount = 100e6;
1707+
uint256 depositAmount = 100e18;
16111708
mockUSDFC.approve(address(payments), depositAmount);
16121709
payments.deposit(mockUSDFC, client, depositAmount);
16131710
vm.stopPrank();
@@ -2814,8 +2911,8 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
28142911
);
28152912

28162913
vm.startPrank(client);
2817-
payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e6, 1000e6, 365 days);
2818-
uint256 depositAmount = 1e6;
2914+
payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e18, 1000e18, 365 days);
2915+
uint256 depositAmount = 1e18;
28192916
mockUSDFC.approve(address(payments), depositAmount);
28202917
payments.deposit(mockUSDFC, client, depositAmount);
28212918
vm.stopPrank();
@@ -2867,8 +2964,8 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
28672964
);
28682965

28692966
vm.startPrank(client);
2870-
payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e6, 1000e6, 365 days);
2871-
uint256 depositAmount = 1e6;
2967+
payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e18, 1000e18, 365 days);
2968+
uint256 depositAmount = 1e18;
28722969
mockUSDFC.approve(address(payments), depositAmount);
28732970
payments.deposit(mockUSDFC, client, depositAmount);
28742971
vm.stopPrank();
@@ -2918,8 +3015,8 @@ contract FilecoinWarmStorageServiceTest is MockFVMTest {
29183015
);
29193016

29203017
vm.startPrank(client);
2921-
payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e6, 1000e6, 365 days);
2922-
uint256 depositAmount = 10e6;
3018+
payments.setOperatorApproval(mockUSDFC, address(pdpServiceWithPayments), true, 1000e18, 1000e18, 365 days);
3019+
uint256 depositAmount = 10e18;
29233020
mockUSDFC.approve(address(payments), depositAmount);
29243021
payments.deposit(mockUSDFC, client, depositAmount);
29253022
vm.stopPrank();
@@ -3972,7 +4069,7 @@ contract FilecoinWarmStorageServiceSignatureTest is Test {
39724069
pdpService = SignatureCheckingService(address(serviceProxy));
39734070

39744071
// Fund the payer
3975-
mockUSDFC.safeTransfer(payer, 1000 * 10 ** 6); // 1000 USDFC
4072+
mockUSDFC.safeTransfer(payer, 1000 * 10 ** 18); // 1000 USDFC
39764073
}
39774074

39784075
// 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)