Skip to content

Commit 6cb08ce

Browse files
authored
feat: floor price set to the 0.06 USDFC/month (#320)
_Edit: This got switched to having 0.06 USDFC as the floor, not the bytes; so now the bytes works out as ~24.576 GiB and we get to talk about it in terms of cents per month._ (PR on top of #316, that should be merged first) Amounts below this pay as if they are storing 24 GiB, which is 0.05859375 USDFC / month. Closes: #319 --- * 24 GiB = 24/1024 TiB = 0.0234375 TiB * At 2.5 USDFC/TiB/month, price = 0.0234375 * 2.5 = 0.05859375 USDFC/month Are we OK with 0.05859375? I have an option here of either setting 24GiB as the floor _size_ or 0.06 as the floor _price_. If we go with 0.06 as the floor then we're working with 24.576 GiB floor size. --- I also had to change the mock token from 6 decimal places to 18, cause there's already a floor rounding bit of code in here and it was getting stuck on a higher floor due to not enough decimal places, so everything came out wrong.
1 parent 21fa916 commit 6cb08ce

File tree

7 files changed

+815
-76
lines changed

7 files changed

+815
-76
lines changed

service_contracts/abi/FilecoinWarmStorageService.abi.json

Lines changed: 131 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@
101101
},
102102
{
103103
"type": "function",
104-
"name": "calculateRatesPerEpoch",
104+
"name": "calculateRatePerEpoch",
105105
"inputs": [
106106
{
107107
"name": "totalBytes",
@@ -357,6 +357,11 @@
357357
"name": "epochsPerMonth",
358358
"type": "uint256",
359359
"internalType": "uint256"
360+
},
361+
{
362+
"name": "minimumPricePerMonth",
363+
"type": "uint256",
364+
"internalType": "uint256"
360365
}
361366
]
362367
}
@@ -1681,6 +1686,115 @@
16811686
}
16821687
]
16831688
},
1689+
{
1690+
"type": "error",
1691+
"name": "InsufficientFundsForMinimumRate",
1692+
"inputs": [
1693+
{
1694+
"name": "payer",
1695+
"type": "address",
1696+
"internalType": "address"
1697+
},
1698+
{
1699+
"name": "minimumRequired",
1700+
"type": "uint256",
1701+
"internalType": "uint256"
1702+
},
1703+
{
1704+
"name": "available",
1705+
"type": "uint256",
1706+
"internalType": "uint256"
1707+
}
1708+
]
1709+
},
1710+
{
1711+
"type": "error",
1712+
"name": "InsufficientLockupAllowance",
1713+
"inputs": [
1714+
{
1715+
"name": "payer",
1716+
"type": "address",
1717+
"internalType": "address"
1718+
},
1719+
{
1720+
"name": "operator",
1721+
"type": "address",
1722+
"internalType": "address"
1723+
},
1724+
{
1725+
"name": "lockupAllowance",
1726+
"type": "uint256",
1727+
"internalType": "uint256"
1728+
},
1729+
{
1730+
"name": "lockupUsage",
1731+
"type": "uint256",
1732+
"internalType": "uint256"
1733+
},
1734+
{
1735+
"name": "minimumLockupRequired",
1736+
"type": "uint256",
1737+
"internalType": "uint256"
1738+
}
1739+
]
1740+
},
1741+
{
1742+
"type": "error",
1743+
"name": "InsufficientMaxLockupPeriod",
1744+
"inputs": [
1745+
{
1746+
"name": "payer",
1747+
"type": "address",
1748+
"internalType": "address"
1749+
},
1750+
{
1751+
"name": "operator",
1752+
"type": "address",
1753+
"internalType": "address"
1754+
},
1755+
{
1756+
"name": "maxLockupPeriod",
1757+
"type": "uint256",
1758+
"internalType": "uint256"
1759+
},
1760+
{
1761+
"name": "requiredLockupPeriod",
1762+
"type": "uint256",
1763+
"internalType": "uint256"
1764+
}
1765+
]
1766+
},
1767+
{
1768+
"type": "error",
1769+
"name": "InsufficientRateAllowance",
1770+
"inputs": [
1771+
{
1772+
"name": "payer",
1773+
"type": "address",
1774+
"internalType": "address"
1775+
},
1776+
{
1777+
"name": "operator",
1778+
"type": "address",
1779+
"internalType": "address"
1780+
},
1781+
{
1782+
"name": "rateAllowance",
1783+
"type": "uint256",
1784+
"internalType": "uint256"
1785+
},
1786+
{
1787+
"name": "rateUsage",
1788+
"type": "uint256",
1789+
"internalType": "uint256"
1790+
},
1791+
{
1792+
"name": "minimumRateRequired",
1793+
"type": "uint256",
1794+
"internalType": "uint256"
1795+
}
1796+
]
1797+
},
16841798
{
16851799
"type": "error",
16861800
"name": "InvalidChallengeCount",
@@ -1957,6 +2071,22 @@
19572071
}
19582072
]
19592073
},
2074+
{
2075+
"type": "error",
2076+
"name": "OperatorNotApproved",
2077+
"inputs": [
2078+
{
2079+
"name": "payer",
2080+
"type": "address",
2081+
"internalType": "address"
2082+
},
2083+
{
2084+
"name": "operator",
2085+
"type": "address",
2086+
"internalType": "address"
2087+
}
2088+
]
2089+
},
19602090
{
19612091
"type": "error",
19622092
"name": "OwnableInvalidOwner",

service_contracts/src/Errors.sol

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,4 +286,44 @@ 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);
295+
296+
/// @notice Operator is not approved for the payer
297+
/// @param payer The payer address
298+
/// @param operator The operator address (warm storage service)
299+
error OperatorNotApproved(address payer, address operator);
300+
301+
/// @notice Operator has insufficient rate allowance for the minimum storage rate
302+
/// @param payer The payer address
303+
/// @param operator The operator address (warm storage service)
304+
/// @param rateAllowance The total rate allowance approved
305+
/// @param rateUsage The current rate usage
306+
/// @param minimumRateRequired The minimum rate required per epoch
307+
error InsufficientRateAllowance(
308+
address payer, address operator, uint256 rateAllowance, uint256 rateUsage, uint256 minimumRateRequired
309+
);
310+
311+
/// @notice Operator has insufficient lockup allowance for the minimum lockup
312+
/// @param payer The payer address
313+
/// @param operator The operator address (warm storage service)
314+
/// @param lockupAllowance The total lockup allowance approved
315+
/// @param lockupUsage The current lockup usage
316+
/// @param minimumLockupRequired The minimum lockup required
317+
error InsufficientLockupAllowance(
318+
address payer, address operator, uint256 lockupAllowance, uint256 lockupUsage, uint256 minimumLockupRequired
319+
);
320+
321+
/// @notice Operator's max lockup period is insufficient for the default lockup period
322+
/// @param payer The payer address
323+
/// @param operator The operator address (warm storage service)
324+
/// @param maxLockupPeriod The maximum lockup period approved
325+
/// @param requiredLockupPeriod The required lockup period
326+
error InsufficientMaxLockupPeriod(
327+
address payer, address operator, uint256 maxLockupPeriod, uint256 requiredLockupPeriod
328+
);
289329
}

service_contracts/src/FilecoinWarmStorageService.sol

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ contract FilecoinWarmStorageService is
159159
uint256 pricePerTiBCacheMissEgress; // Cache miss egress price per TiB (usage-based)
160160
IERC20 tokenAddress; // Address of the USDFC token
161161
uint256 epochsPerMonth; // Number of epochs in a month
162+
uint256 minimumPricePerMonth; // Minimum monthly charge for any dataset size (0.06 USDFC)
162163
}
163164

164165
// Used for announcing upgrades, packed into one slot
@@ -197,6 +198,7 @@ contract FilecoinWarmStorageService is
197198
uint256 private immutable STORAGE_PRICE_PER_TIB_PER_MONTH; // 2.5 USDFC per TiB per month without CDN with correct decimals
198199
uint256 private immutable CDN_EGRESS_PRICE_PER_TIB; // 7 USDFC per TiB of CDN egress
199200
uint256 private immutable CACHE_MISS_EGRESS_PRICE_PER_TIB; // 7 USDFC per TiB of cache miss egress
201+
uint256 private immutable MINIMUM_STORAGE_RATE_PER_MONTH; // 0.06 USDFC per month minimum pricing floor
200202

201203
// Fixed lockup amounts for CDN rails
202204
uint256 private immutable DEFAULT_CDN_LOCKUP_AMOUNT; // 0.7 USDFC
@@ -331,6 +333,7 @@ contract FilecoinWarmStorageService is
331333
STORAGE_PRICE_PER_TIB_PER_MONTH = (5 * 10 ** TOKEN_DECIMALS) / 2; // 2.5 USDFC
332334
CDN_EGRESS_PRICE_PER_TIB = 7 * 10 ** TOKEN_DECIMALS; // 7 USDFC per TiB
333335
CACHE_MISS_EGRESS_PRICE_PER_TIB = 7 * 10 ** TOKEN_DECIMALS; // 7 USDFC per TiB
336+
MINIMUM_STORAGE_RATE_PER_MONTH = (6 * 10 ** TOKEN_DECIMALS) / 100; // 0.06 USDFC minimum
334337

335338
// Initialize the lockup constants based on the actual token decimals
336339
DEFAULT_CDN_LOCKUP_AMOUNT = (7 * 10 ** TOKEN_DECIMALS) / 10; // 0.7 USDFC
@@ -584,6 +587,10 @@ contract FilecoinWarmStorageService is
584587

585588
// Create the payment rails using the FilecoinPayV1 contract
586589
FilecoinPayV1 payments = FilecoinPayV1(paymentsContractAddress);
590+
591+
// Validate payer has sufficient funds and operator approvals for minimum pricing
592+
validatePayerOperatorApprovalAndFunds(payments, createData.payer);
593+
587594
uint256 pdpRailId = payments.createRail(
588595
usdfcTokenAddress, // token address
589596
createData.payer, // from (payer)
@@ -1096,15 +1103,65 @@ contract FilecoinWarmStorageService is
10961103
}
10971104
}
10981105

1106+
/// @notice Validates that the payer has sufficient funds and operator approvals for minimum pricing
1107+
/// @param payments The FilecoinPayV1 contract instance
1108+
/// @param payer The address of the payer
1109+
function validatePayerOperatorApprovalAndFunds(FilecoinPayV1 payments, address payer) internal view {
1110+
// Calculate required lockup for minimum pricing
1111+
uint256 minimumLockupRequired = (MINIMUM_STORAGE_RATE_PER_MONTH * DEFAULT_LOCKUP_PERIOD) / EPOCHS_PER_MONTH;
1112+
1113+
// Check that payer has sufficient available funds
1114+
(,, uint256 availableFunds,) = payments.getAccountInfoIfSettled(usdfcTokenAddress, payer);
1115+
require(
1116+
availableFunds >= minimumLockupRequired,
1117+
Errors.InsufficientFundsForMinimumRate(payer, minimumLockupRequired, availableFunds)
1118+
);
1119+
1120+
// Check operator approval settings
1121+
(
1122+
bool isApproved,
1123+
uint256 rateAllowance,
1124+
uint256 lockupAllowance,
1125+
uint256 rateUsage,
1126+
uint256 lockupUsage,
1127+
uint256 maxLockupPeriod
1128+
) = payments.operatorApprovals(usdfcTokenAddress, payer, address(this));
1129+
1130+
// Verify operator is approved
1131+
require(isApproved, Errors.OperatorNotApproved(payer, address(this)));
1132+
1133+
// Calculate minimum rate per epoch
1134+
uint256 minimumRatePerEpoch = MINIMUM_STORAGE_RATE_PER_MONTH / EPOCHS_PER_MONTH;
1135+
1136+
// Verify rate allowance is sufficient
1137+
require(
1138+
rateAllowance >= rateUsage + minimumRatePerEpoch,
1139+
Errors.InsufficientRateAllowance(payer, address(this), rateAllowance, rateUsage, minimumRatePerEpoch)
1140+
);
1141+
1142+
// Verify lockup allowance is sufficient
1143+
require(
1144+
lockupAllowance >= lockupUsage + minimumLockupRequired,
1145+
Errors.InsufficientLockupAllowance(
1146+
payer, address(this), lockupAllowance, lockupUsage, minimumLockupRequired
1147+
)
1148+
);
1149+
1150+
// Verify max lockup period is sufficient
1151+
require(
1152+
maxLockupPeriod >= DEFAULT_LOCKUP_PERIOD,
1153+
Errors.InsufficientMaxLockupPeriod(payer, address(this), maxLockupPeriod, DEFAULT_LOCKUP_PERIOD)
1154+
);
1155+
}
1156+
10991157
function updatePaymentRates(uint256 dataSetId, uint256 leafCount) internal {
11001158
// Revert if no payment rail is configured for this data set
11011159
require(dataSetInfo[dataSetId].pdpRailId != 0, Errors.NoPDPPaymentRail(dataSetId));
11021160

11031161
uint256 totalBytes = leafCount * BYTES_PER_LEAF;
11041162
FilecoinPayV1 payments = FilecoinPayV1(paymentsContractAddress);
11051163

1106-
// Update the PDP rail payment rate with the new rate and no one-time
1107-
// payment
1164+
// Update the PDP rail payment rate with the new rate and no one-time payment
11081165
uint256 pdpRailId = dataSetInfo[dataSetId].pdpRailId;
11091166
uint256 newStorageRatePerEpoch = _calculateStorageRate(totalBytes);
11101167
payments.modifyRailPayment(
@@ -1177,21 +1234,29 @@ contract FilecoinWarmStorageService is
11771234

11781235
/**
11791236
* @notice Calculate storage rate per epoch based on total storage size
1180-
* @dev Returns storage rate per TiB per month
1237+
* @dev Returns storage rate per TiB per month with minimum pricing floor applied
11811238
* @param totalBytes Total size of the stored data in bytes
11821239
* @return storageRate The PDP storage rate per epoch
11831240
*/
1184-
function calculateRatesPerEpoch(uint256 totalBytes) external view returns (uint256 storageRate) {
1185-
storageRate = calculateStorageSizeBasedRatePerEpoch(totalBytes, STORAGE_PRICE_PER_TIB_PER_MONTH);
1241+
function calculateRatePerEpoch(uint256 totalBytes) external view returns (uint256 storageRate) {
1242+
storageRate = _calculateStorageRate(totalBytes);
11861243
}
11871244

11881245
/**
11891246
* @notice Calculate the storage rate per epoch (internal use)
1247+
* @dev Implements minimum pricing floor and returns the higher of the natural size-based rate or the minimum rate.
11901248
* @param totalBytes Total size of the stored data in bytes
11911249
* @return The storage rate per epoch
11921250
*/
11931251
function _calculateStorageRate(uint256 totalBytes) internal view returns (uint256) {
1194-
return calculateStorageSizeBasedRatePerEpoch(totalBytes, STORAGE_PRICE_PER_TIB_PER_MONTH);
1252+
// Calculate natural size-based rate
1253+
uint256 naturalRate = calculateStorageSizeBasedRatePerEpoch(totalBytes, STORAGE_PRICE_PER_TIB_PER_MONTH);
1254+
1255+
// Calculate minimum rate (floor price converted to per-epoch)
1256+
uint256 minimumRate = MINIMUM_STORAGE_RATE_PER_MONTH / EPOCHS_PER_MONTH;
1257+
1258+
// Return whichever is higher: natural rate or minimum rate
1259+
return naturalRate > minimumRate ? naturalRate : minimumRate;
11951260
}
11961261

11971262
/**
@@ -1289,7 +1354,8 @@ contract FilecoinWarmStorageService is
12891354
pricePerTiBCdnEgress: CDN_EGRESS_PRICE_PER_TIB,
12901355
pricePerTiBCacheMissEgress: CACHE_MISS_EGRESS_PRICE_PER_TIB,
12911356
tokenAddress: usdfcTokenAddress,
1292-
epochsPerMonth: EPOCHS_PER_MONTH
1357+
epochsPerMonth: EPOCHS_PER_MONTH,
1358+
minimumPricePerMonth: MINIMUM_STORAGE_RATE_PER_MONTH
12931359
});
12941360
}
12951361

0 commit comments

Comments
 (0)