From 517cfa3d1f92910b55be20b24959b867c68ec318 Mon Sep 17 00:00:00 2001 From: Phi Date: Thu, 23 Oct 2025 13:55:04 +0200 Subject: [PATCH 1/9] feat(pricing): make pricing constants mutable with owner-controlled updates - Convert STORAGE_PRICE_PER_TIB_PER_MONTH, CACHE_MISS_PRICE_PER_TIB_PER_MONTH, and CDN_PRICE_PER_TIB_PER_MONTH from immutable to storage variables - Add updatePricing() owner-only function to update pricing rates - Add getCurrentPricingRates() getter function - Add PricingUpdated event to track pricing changes --- CHANGELOG.md | 11 +++ .../src/FilecoinWarmStorageService.sol | 86 ++++++++++++++++--- 2 files changed, 84 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 795f87d2..0a6b5ab0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Use `getDataSetStatusDetails()` to check termination status separately from Active/Inactive status - Subgraph schema updated with status enum and history tracking - **Calibnet**: Reduced DEFAULT_CHALLENGE_WINDOW_SIZE from 30 epochs to 20 epochs for faster testing iteration +- Made storage pricing and minimum rate mutable storage variables instead of immutable constants ([#306](https://github.com/FilOzone/filecoin-services/issues/306)) + - `storagePricePerTibPerMonth` (initially 2.5 USDFC, max 10 USDFC) + - `minimumStorageRatePerMonth` (initially 0.06 USDFC, max 0.24 USDFC) +- Added `updatePricing(uint256 newStoragePrice, uint256 newMinimumRate)` function to allow owner to update pricing rates without contract upgrades + - Pass 0 to keep existing value unchanged + - At least one price must be non-zero + - Validates against 4x upper bounds (10 USDFC storage, 0.24 USDFC minimum rate) + - Price updates apply immediately to existing payment rails when recalculated +- Added `getCurrentPricingRates()` function to query current storage price and minimum rate +- Added `PricingUpdated(uint256 storagePrice, uint256 minimumRate)` event to track pricing changes +- Added `AtLeastOnePriceMustBeNonZero` and `PriceExceedsMaximum` errors for pricing validation ## [0.3.0] - 2025-10-08 - M3.1 Calibration Network Deployment diff --git a/service_contracts/src/FilecoinWarmStorageService.sol b/service_contracts/src/FilecoinWarmStorageService.sol index b9154ecd..97eeee4d 100644 --- a/service_contracts/src/FilecoinWarmStorageService.sol +++ b/service_contracts/src/FilecoinWarmStorageService.sol @@ -97,6 +97,8 @@ contract FilecoinWarmStorageService is event ProviderApproved(uint256 indexed providerId); event ProviderUnapproved(uint256 indexed providerId); + event PricingUpdated(uint256 storagePrice, uint256 minimumRate); + // ========================================================================= // Structs @@ -193,16 +195,18 @@ contract FilecoinWarmStorageService is bytes32 private constant WITH_CDN_STRING_STORAGE_REPR = 0x7769746843444e0000000000000000000000000000000000000000000000000e; - // Pricing constants - uint256 private immutable STORAGE_PRICE_PER_TIB_PER_MONTH; // 2.5 USDFC per TiB per month without CDN with correct decimals + // Pricing constants (CDN egress pricing is immutable) uint256 private immutable CDN_EGRESS_PRICE_PER_TIB; // 7 USDFC per TiB of CDN egress uint256 private immutable CACHE_MISS_EGRESS_PRICE_PER_TIB; // 7 USDFC per TiB of cache miss egress - uint256 private immutable MINIMUM_STORAGE_RATE_PER_MONTH; // 0.06 USDFC per month minimum pricing floor // Fixed lockup amounts for CDN rails uint256 private immutable DEFAULT_CDN_LOCKUP_AMOUNT; // 0.7 USDFC uint256 private immutable DEFAULT_CACHE_MISS_LOCKUP_AMOUNT; // 0.3 USDFC + // Maximum pricing bounds (4x initial values) + uint256 private immutable MAX_STORAGE_PRICE_PER_TIB_PER_MONTH; // 10 USDFC (4x 2.5) + uint256 private immutable MAX_MINIMUM_STORAGE_RATE_PER_MONTH; // 0.24 USDFC (4x 0.06) + // Token decimals uint8 private immutable TOKEN_DECIMALS; @@ -272,6 +276,10 @@ contract FilecoinWarmStorageService is PlannedUpgrade private nextUpgrade; + // Pricing rates (mutable for future adjustments) + uint256 private storagePricePerTibPerMonth; + uint256 private minimumStorageRatePerMonth; + event UpgradeAnnounced(PlannedUpgrade plannedUpgrade); // ========================================================================= @@ -328,11 +336,13 @@ contract FilecoinWarmStorageService is // Read token decimals from the USDFC token contract TOKEN_DECIMALS = _usdfc.decimals(); - // Initialize the fee constants based on the actual token decimals - STORAGE_PRICE_PER_TIB_PER_MONTH = (5 * 10 ** TOKEN_DECIMALS) / 2; // 2.5 USDFC + // Initialize the immutable pricing constants based on the actual token decimals CDN_EGRESS_PRICE_PER_TIB = 7 * 10 ** TOKEN_DECIMALS; // 7 USDFC per TiB CACHE_MISS_EGRESS_PRICE_PER_TIB = 7 * 10 ** TOKEN_DECIMALS; // 7 USDFC per TiB - MINIMUM_STORAGE_RATE_PER_MONTH = (6 * 10 ** TOKEN_DECIMALS) / 100; // 0.06 USDFC minimum + + // Initialize maximum pricing bounds (4x initial values) + MAX_STORAGE_PRICE_PER_TIB_PER_MONTH = 10 * 10 ** TOKEN_DECIMALS; // 10 USDFC (4x 2.5) + MAX_MINIMUM_STORAGE_RATE_PER_MONTH = (24 * 10 ** TOKEN_DECIMALS) / 100; // 0.24 USDFC (4x 0.06) // Initialize the lockup constants based on the actual token decimals DEFAULT_CDN_LOCKUP_AMOUNT = (7 * 10 ** TOKEN_DECIMALS) / 10; // 0.7 USDFC @@ -383,6 +393,10 @@ contract FilecoinWarmStorageService is // Set commission rate serviceCommissionBps = 0; // 0% + + // Initialize mutable pricing variables + storagePricePerTibPerMonth = (5 * 10 ** TOKEN_DECIMALS) / 2; // 2.5 USDFC + minimumStorageRatePerMonth = (6 * 10 ** TOKEN_DECIMALS) / 100; // 0.06 USDFC } function announcePlannedUpgrade(PlannedUpgrade calldata plannedUpgrade) external onlyOwner { @@ -467,6 +481,39 @@ contract FilecoinWarmStorageService is serviceCommissionBps = newCommissionBps; } + /** + * @notice Updates the pricing rates for storage services + * @dev Only callable by the contract owner. Pass 0 to keep existing value unchanged. + * Price updates apply immediately to existing payment rails when they're recalculated + * (e.g., during piece additions/deletions or proving period updates). + * Maximum allowed values: 10 USDFC for storage, 0.24 USDFC for minimum rate. + * @param newStoragePrice New storage price per TiB per month (0 = no change, max 10 USDFC) + * @param newMinimumRate New minimum monthly storage rate (0 = no change, max 0.24 USDFC) + */ + function updatePricing(uint256 newStoragePrice, uint256 newMinimumRate) + external + onlyOwner + { + if (newStoragePrice == 0 && newMinimumRate == 0) { + revert Errors.AtLeastOnePriceMustBeNonZero(); + } + + if (newStoragePrice > 0) { + if (newStoragePrice > MAX_STORAGE_PRICE_PER_TIB_PER_MONTH) { + revert Errors.PriceExceedsMaximum("storage", MAX_STORAGE_PRICE_PER_TIB_PER_MONTH, newStoragePrice); + } + storagePricePerTibPerMonth = newStoragePrice; + } + if (newMinimumRate > 0) { + if (newMinimumRate > MAX_MINIMUM_STORAGE_RATE_PER_MONTH) { + revert Errors.PriceExceedsMaximum("minimumRate", MAX_MINIMUM_STORAGE_RATE_PER_MONTH, newMinimumRate); + } + minimumStorageRatePerMonth = newMinimumRate; + } + + emit PricingUpdated(storagePricePerTibPerMonth, minimumStorageRatePerMonth); + } + /** * @notice Adds a provider ID to the approved list * @dev Only callable by the contract owner. Reverts if already approved. @@ -1097,7 +1144,7 @@ contract FilecoinWarmStorageService is /// @param payer The address of the payer function validatePayerOperatorApprovalAndFunds(FilecoinPayV1 payments, address payer) internal view { // Calculate required lockup for minimum pricing - uint256 minimumLockupRequired = (MINIMUM_STORAGE_RATE_PER_MONTH * DEFAULT_LOCKUP_PERIOD) / EPOCHS_PER_MONTH; + uint256 minimumLockupRequired = (minimumStorageRatePerMonth * DEFAULT_LOCKUP_PERIOD) / EPOCHS_PER_MONTH; // Check that payer has sufficient available funds (,, uint256 availableFunds,) = payments.getAccountInfoIfSettled(usdfcTokenAddress, payer); @@ -1120,7 +1167,7 @@ contract FilecoinWarmStorageService is require(isApproved, Errors.OperatorNotApproved(payer, address(this))); // Calculate minimum rate per epoch - uint256 minimumRatePerEpoch = MINIMUM_STORAGE_RATE_PER_MONTH / EPOCHS_PER_MONTH; + uint256 minimumRatePerEpoch = minimumStorageRatePerMonth / EPOCHS_PER_MONTH; // Verify rate allowance is sufficient require( @@ -1241,10 +1288,10 @@ contract FilecoinWarmStorageService is */ function _calculateStorageRate(uint256 totalBytes) internal view returns (uint256) { // Calculate natural size-based rate - uint256 naturalRate = calculateStorageSizeBasedRatePerEpoch(totalBytes, STORAGE_PRICE_PER_TIB_PER_MONTH); + uint256 naturalRate = calculateStorageSizeBasedRatePerEpoch(totalBytes, storagePricePerTibPerMonth); // Calculate minimum rate (floor price converted to per-epoch) - uint256 minimumRate = MINIMUM_STORAGE_RATE_PER_MONTH / EPOCHS_PER_MONTH; + uint256 minimumRate = minimumStorageRatePerMonth / EPOCHS_PER_MONTH; // Return whichever is higher: natural rate or minimum rate return naturalRate > minimumRate ? naturalRate : minimumRate; @@ -1341,22 +1388,35 @@ contract FilecoinWarmStorageService is */ function getServicePrice() external view returns (ServicePricing memory pricing) { pricing = ServicePricing({ - pricePerTiBPerMonthNoCDN: STORAGE_PRICE_PER_TIB_PER_MONTH, + pricePerTiBPerMonthNoCDN: storagePricePerTibPerMonth, pricePerTiBCdnEgress: CDN_EGRESS_PRICE_PER_TIB, pricePerTiBCacheMissEgress: CACHE_MISS_EGRESS_PRICE_PER_TIB, tokenAddress: usdfcTokenAddress, epochsPerMonth: EPOCHS_PER_MONTH, - minimumPricePerMonth: MINIMUM_STORAGE_RATE_PER_MONTH + minimumPricePerMonth: minimumStorageRatePerMonth }); } + /** + * @notice Get the current pricing rates + * @return storagePrice Current storage price per TiB per month + * @return minimumRate Current minimum monthly storage rate + */ + function getCurrentPricingRates() + external + view + returns (uint256 storagePrice, uint256 minimumRate) + { + return (storagePricePerTibPerMonth, minimumStorageRatePerMonth); + } + /** * @notice Get the effective rates after commission for both service types * @return serviceFee Service fee (per TiB per month) * @return spPayment SP payment (per TiB per month) */ function getEffectiveRates() external view returns (uint256 serviceFee, uint256 spPayment) { - uint256 total = STORAGE_PRICE_PER_TIB_PER_MONTH; + uint256 total = storagePricePerTibPerMonth; serviceFee = (total * serviceCommissionBps) / COMMISSION_MAX_BPS; spPayment = total - serviceFee; From 9efacd01e446435c5d78fe387981e8123c820f0a Mon Sep 17 00:00:00 2001 From: Phi Date: Thu, 23 Oct 2025 14:09:38 +0200 Subject: [PATCH 2/9] fix: make update-abi fix: make update-abi --- .../abi/FilecoinWarmStorageService.abi.json | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/service_contracts/abi/FilecoinWarmStorageService.abi.json b/service_contracts/abi/FilecoinWarmStorageService.abi.json index d4a74ce0..e97fe9e5 100644 --- a/service_contracts/abi/FilecoinWarmStorageService.abi.json +++ b/service_contracts/abi/FilecoinWarmStorageService.abi.json @@ -281,6 +281,29 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "getCurrentPricingRates", + "inputs": [], + "outputs": [ + { + "name": "storagePrice", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "cacheMissPrice", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "cdnPrice", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "getEffectiveRates", @@ -793,6 +816,29 @@ "outputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "updatePricing", + "inputs": [ + { + "name": "newStoragePrice", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "newCacheMissPrice", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "newCdnPrice", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, { "type": "function", "name": "updateServiceCommission", @@ -1286,6 +1332,31 @@ ], "anonymous": false }, + { + "type": "event", + "name": "PricingUpdated", + "inputs": [ + { + "name": "storagePrice", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "cacheMissPrice", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "cdnPrice", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, { "type": "event", "name": "ProviderApproved", From 58df7752f16caa2a74513a6345d4331da37f63a9 Mon Sep 17 00:00:00 2001 From: Phi Date: Fri, 24 Oct 2025 08:13:22 +0200 Subject: [PATCH 3/9] chore: make abi-gen chore: make abi-gen --- .../abi/FilecoinWarmStorageService.abi.json | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/service_contracts/abi/FilecoinWarmStorageService.abi.json b/service_contracts/abi/FilecoinWarmStorageService.abi.json index e97fe9e5..95934e06 100644 --- a/service_contracts/abi/FilecoinWarmStorageService.abi.json +++ b/service_contracts/abi/FilecoinWarmStorageService.abi.json @@ -1507,6 +1507,11 @@ } ] }, + { + "type": "error", + "name": "AtLeastOnePriceMustBeNonZero", + "inputs": [] + }, { "type": "error", "name": "CDNPaymentAlreadyTerminated", @@ -2196,6 +2201,27 @@ } ] }, + { + "type": "error", + "name": "PriceExceedsMaximum", + "inputs": [ + { + "name": "priceType", + "type": "string", + "internalType": "string" + }, + { + "name": "maxAllowed", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "actual", + "type": "uint256", + "internalType": "uint256" + } + ] + }, { "type": "error", "name": "ProofAlreadySubmitted", From 79ada5018f43163d9267251a43258426133444b3 Mon Sep 17 00:00:00 2001 From: Phi Date: Fri, 24 Oct 2025 23:08:55 +0200 Subject: [PATCH 4/9] feat: integrate floor pricing with mutable pricing system Rebased on main to incorporate floor pricing changes (commit 6cb08ce) and made MINIMUM_STORAGE_RATE_PER_MONTH mutable to enable runtime updates. Changes: - Converted minimumStorageRatePerMonth from immutable to storage variable - Added MAX_MINIMUM_STORAGE_RATE_PER_MONTH (0.24 USDFC, 4x initial 0.06) - Updated updatePricing() to accept newMinimumRate parameter - Updated PricingUpdated event to include minimumRate - Updated getCurrentPricingRates() to return both storage price and minimum rate - Added AtLeastOnePriceMustBeNonZero and PriceExceedsMaximum errors - Updated all references throughout contract to use camelCase variable names --- service_contracts/abi/Errors.abi.json | 26 +++++++++++++++++++ .../abi/FilecoinWarmStorageService.abi.json | 22 +++------------- service_contracts/src/Errors.sol | 9 +++++++ .../src/FilecoinWarmStorageService.sol | 11 ++------ .../lib/FilecoinWarmStorageServiceLayout.sol | 2 ++ 5 files changed, 42 insertions(+), 28 deletions(-) diff --git a/service_contracts/abi/Errors.abi.json b/service_contracts/abi/Errors.abi.json index b65ad4a8..cbf99eac 100644 --- a/service_contracts/abi/Errors.abi.json +++ b/service_contracts/abi/Errors.abi.json @@ -10,6 +10,11 @@ } ] }, + { + "type": "error", + "name": "AtLeastOnePriceMustBeNonZero", + "inputs": [] + }, { "type": "error", "name": "CDNPaymentAlreadyTerminated", @@ -715,6 +720,27 @@ } ] }, + { + "type": "error", + "name": "PriceExceedsMaximum", + "inputs": [ + { + "name": "priceType", + "type": "string", + "internalType": "string" + }, + { + "name": "maxAllowed", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "actual", + "type": "uint256", + "internalType": "uint256" + } + ] + }, { "type": "error", "name": "ProofAlreadySubmitted", diff --git a/service_contracts/abi/FilecoinWarmStorageService.abi.json b/service_contracts/abi/FilecoinWarmStorageService.abi.json index 95934e06..f9b990cb 100644 --- a/service_contracts/abi/FilecoinWarmStorageService.abi.json +++ b/service_contracts/abi/FilecoinWarmStorageService.abi.json @@ -292,12 +292,7 @@ "internalType": "uint256" }, { - "name": "cacheMissPrice", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "cdnPrice", + "name": "minimumRate", "type": "uint256", "internalType": "uint256" } @@ -826,12 +821,7 @@ "internalType": "uint256" }, { - "name": "newCacheMissPrice", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "newCdnPrice", + "name": "newMinimumRate", "type": "uint256", "internalType": "uint256" } @@ -1343,13 +1333,7 @@ "internalType": "uint256" }, { - "name": "cacheMissPrice", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - }, - { - "name": "cdnPrice", + "name": "minimumRate", "type": "uint256", "indexed": false, "internalType": "uint256" diff --git a/service_contracts/src/Errors.sol b/service_contracts/src/Errors.sol index 62c65599..a60a51cd 100644 --- a/service_contracts/src/Errors.sol +++ b/service_contracts/src/Errors.sol @@ -326,4 +326,13 @@ library Errors { error InsufficientMaxLockupPeriod( address payer, address operator, uint256 maxLockupPeriod, uint256 requiredLockupPeriod ); + + /// @notice At least one price parameter must be non-zero when updating pricing + error AtLeastOnePriceMustBeNonZero(); + + /// @notice Price update exceeds the maximum allowed value + /// @param priceType The type of price being updated (e.g., "storage", "minimumRate") + /// @param maxAllowed The maximum allowed value for this price type + /// @param actual The attempted value that exceeds the maximum + error PriceExceedsMaximum(string priceType, uint256 maxAllowed, uint256 actual); } diff --git a/service_contracts/src/FilecoinWarmStorageService.sol b/service_contracts/src/FilecoinWarmStorageService.sol index 97eeee4d..a27fab0f 100644 --- a/service_contracts/src/FilecoinWarmStorageService.sol +++ b/service_contracts/src/FilecoinWarmStorageService.sol @@ -490,10 +490,7 @@ contract FilecoinWarmStorageService is * @param newStoragePrice New storage price per TiB per month (0 = no change, max 10 USDFC) * @param newMinimumRate New minimum monthly storage rate (0 = no change, max 0.24 USDFC) */ - function updatePricing(uint256 newStoragePrice, uint256 newMinimumRate) - external - onlyOwner - { + function updatePricing(uint256 newStoragePrice, uint256 newMinimumRate) external onlyOwner { if (newStoragePrice == 0 && newMinimumRate == 0) { revert Errors.AtLeastOnePriceMustBeNonZero(); } @@ -1402,11 +1399,7 @@ contract FilecoinWarmStorageService is * @return storagePrice Current storage price per TiB per month * @return minimumRate Current minimum monthly storage rate */ - function getCurrentPricingRates() - external - view - returns (uint256 storagePrice, uint256 minimumRate) - { + function getCurrentPricingRates() external view returns (uint256 storagePrice, uint256 minimumRate) { return (storagePricePerTibPerMonth, minimumStorageRatePerMonth); } diff --git a/service_contracts/src/lib/FilecoinWarmStorageServiceLayout.sol b/service_contracts/src/lib/FilecoinWarmStorageServiceLayout.sol index df5617a7..fb8182af 100644 --- a/service_contracts/src/lib/FilecoinWarmStorageServiceLayout.sol +++ b/service_contracts/src/lib/FilecoinWarmStorageServiceLayout.sol @@ -25,3 +25,5 @@ bytes32 constant APPROVED_PROVIDER_IDS_SLOT = bytes32(uint256(16)); bytes32 constant VIEW_CONTRACT_ADDRESS_SLOT = bytes32(uint256(17)); bytes32 constant FIL_BEAM_CONTROLLER_ADDRESS_SLOT = bytes32(uint256(18)); bytes32 constant NEXT_UPGRADE_SLOT = bytes32(uint256(19)); +bytes32 constant STORAGE_PRICE_PER_TIB_PER_MONTH_SLOT = bytes32(uint256(20)); +bytes32 constant MINIMUM_STORAGE_RATE_PER_MONTH_SLOT = bytes32(uint256(21)); From f4b0e120cf7391271bcaf4b35f4a4bbb45cc1b0a Mon Sep 17 00:00:00 2001 From: Phi-rjan Date: Sun, 26 Oct 2025 20:15:58 +0100 Subject: [PATCH 5/9] Update service_contracts/src/FilecoinWarmStorageService.sol Co-authored-by: William Morriss --- service_contracts/src/FilecoinWarmStorageService.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/service_contracts/src/FilecoinWarmStorageService.sol b/service_contracts/src/FilecoinWarmStorageService.sol index a27fab0f..2e73ad9e 100644 --- a/service_contracts/src/FilecoinWarmStorageService.sol +++ b/service_contracts/src/FilecoinWarmStorageService.sol @@ -496,8 +496,7 @@ contract FilecoinWarmStorageService is } if (newStoragePrice > 0) { - if (newStoragePrice > MAX_STORAGE_PRICE_PER_TIB_PER_MONTH) { - revert Errors.PriceExceedsMaximum("storage", MAX_STORAGE_PRICE_PER_TIB_PER_MONTH, newStoragePrice); + require(newStoragePrice <= MAX_STORAGE_PRICE_PER_TIB_PER_MONTH, Errors.PriceExceedsMaximum("storage", MAX_STORAGE_PRICE_PER_TIB_PER_MONTH, newStoragePrice)); } storagePricePerTibPerMonth = newStoragePrice; } From 6fae6e17554f9d3da92b8d027ec8acd90e978a04 Mon Sep 17 00:00:00 2001 From: Phi-rjan Date: Sun, 26 Oct 2025 20:16:17 +0100 Subject: [PATCH 6/9] Update service_contracts/src/FilecoinWarmStorageService.sol Co-authored-by: William Morriss --- service_contracts/src/FilecoinWarmStorageService.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/service_contracts/src/FilecoinWarmStorageService.sol b/service_contracts/src/FilecoinWarmStorageService.sol index 2e73ad9e..cdcfdc45 100644 --- a/service_contracts/src/FilecoinWarmStorageService.sol +++ b/service_contracts/src/FilecoinWarmStorageService.sol @@ -501,8 +501,7 @@ contract FilecoinWarmStorageService is storagePricePerTibPerMonth = newStoragePrice; } if (newMinimumRate > 0) { - if (newMinimumRate > MAX_MINIMUM_STORAGE_RATE_PER_MONTH) { - revert Errors.PriceExceedsMaximum("minimumRate", MAX_MINIMUM_STORAGE_RATE_PER_MONTH, newMinimumRate); + require(newMinimumRate <= MAX_MINIMUM_STORAGE_RATE_PER_MONTH, Errors.PriceExceedsMaximum("minimumRate", MAX_MINIMUM_STORAGE_RATE_PER_MONTH, newMinimumRate)); } minimumStorageRatePerMonth = newMinimumRate; } From b0667c756b93e2c8430937ae04ddedaeb55279bc Mon Sep 17 00:00:00 2001 From: Phi-rjan Date: Sun, 26 Oct 2025 20:16:29 +0100 Subject: [PATCH 7/9] Update service_contracts/src/FilecoinWarmStorageService.sol Co-authored-by: William Morriss --- service_contracts/src/FilecoinWarmStorageService.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/service_contracts/src/FilecoinWarmStorageService.sol b/service_contracts/src/FilecoinWarmStorageService.sol index cdcfdc45..09659a14 100644 --- a/service_contracts/src/FilecoinWarmStorageService.sol +++ b/service_contracts/src/FilecoinWarmStorageService.sol @@ -491,8 +491,7 @@ contract FilecoinWarmStorageService is * @param newMinimumRate New minimum monthly storage rate (0 = no change, max 0.24 USDFC) */ function updatePricing(uint256 newStoragePrice, uint256 newMinimumRate) external onlyOwner { - if (newStoragePrice == 0 && newMinimumRate == 0) { - revert Errors.AtLeastOnePriceMustBeNonZero(); + require(newStoragePrice > 0 || newMinimumRate > 0, Errors.AtLeastOnePriceMustBeNonZero()); } if (newStoragePrice > 0) { From 3d6388bb96a403dab5f97a74e52cb82880abdbca Mon Sep 17 00:00:00 2001 From: Phi Date: Sun, 26 Oct 2025 20:58:33 +0100 Subject: [PATCH 8/9] refactor: replace string with enum in PriceExceedsMaximum error Address PR feedback to reduce code size by using enum instead of string literals in error definitions. Changes: - Add PriceType enum (Storage, MinimumRate) to Errors.sol - Update PriceExceedsMaximum error to use PriceType enum instead of string - Update error calls in updatePricing() to use Errors.PriceType.Storage/MinimumRate - Fix formatting issues in updatePricing() function --- service_contracts/abi/Errors.abi.json | 4 ++-- .../abi/FilecoinWarmStorageService.abi.json | 4 ++-- service_contracts/src/Errors.sol | 11 +++++++++-- .../src/FilecoinWarmStorageService.sol | 17 ++++++++++++----- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/service_contracts/abi/Errors.abi.json b/service_contracts/abi/Errors.abi.json index cbf99eac..988c5fa1 100644 --- a/service_contracts/abi/Errors.abi.json +++ b/service_contracts/abi/Errors.abi.json @@ -726,8 +726,8 @@ "inputs": [ { "name": "priceType", - "type": "string", - "internalType": "string" + "type": "uint8", + "internalType": "enum Errors.PriceType" }, { "name": "maxAllowed", diff --git a/service_contracts/abi/FilecoinWarmStorageService.abi.json b/service_contracts/abi/FilecoinWarmStorageService.abi.json index f9b990cb..12d665a6 100644 --- a/service_contracts/abi/FilecoinWarmStorageService.abi.json +++ b/service_contracts/abi/FilecoinWarmStorageService.abi.json @@ -2191,8 +2191,8 @@ "inputs": [ { "name": "priceType", - "type": "string", - "internalType": "string" + "type": "uint8", + "internalType": "enum Errors.PriceType" }, { "name": "maxAllowed", diff --git a/service_contracts/src/Errors.sol b/service_contracts/src/Errors.sol index a60a51cd..09caf1d4 100644 --- a/service_contracts/src/Errors.sol +++ b/service_contracts/src/Errors.sol @@ -36,6 +36,13 @@ library Errors { Service } + enum PriceType { + /// Storage price per TiB per month + Storage, + /// Minimum monthly storage rate (floor price) + MinimumRate + } + /// @notice An expected contract or participant address was the zero address /// @dev Used for parameter validation when a non-zero address is required /// @param field The specific address field that was zero (see enum {AddressField}) @@ -331,8 +338,8 @@ library Errors { error AtLeastOnePriceMustBeNonZero(); /// @notice Price update exceeds the maximum allowed value - /// @param priceType The type of price being updated (e.g., "storage", "minimumRate") + /// @param priceType The type of price being updated (see enum {PriceType}) /// @param maxAllowed The maximum allowed value for this price type /// @param actual The attempted value that exceeds the maximum - error PriceExceedsMaximum(string priceType, uint256 maxAllowed, uint256 actual); + error PriceExceedsMaximum(PriceType priceType, uint256 maxAllowed, uint256 actual); } diff --git a/service_contracts/src/FilecoinWarmStorageService.sol b/service_contracts/src/FilecoinWarmStorageService.sol index 09659a14..e047c4b7 100644 --- a/service_contracts/src/FilecoinWarmStorageService.sol +++ b/service_contracts/src/FilecoinWarmStorageService.sol @@ -492,16 +492,23 @@ contract FilecoinWarmStorageService is */ function updatePricing(uint256 newStoragePrice, uint256 newMinimumRate) external onlyOwner { require(newStoragePrice > 0 || newMinimumRate > 0, Errors.AtLeastOnePriceMustBeNonZero()); - } if (newStoragePrice > 0) { - require(newStoragePrice <= MAX_STORAGE_PRICE_PER_TIB_PER_MONTH, Errors.PriceExceedsMaximum("storage", MAX_STORAGE_PRICE_PER_TIB_PER_MONTH, newStoragePrice)); - } + require( + newStoragePrice <= MAX_STORAGE_PRICE_PER_TIB_PER_MONTH, + Errors.PriceExceedsMaximum( + Errors.PriceType.Storage, MAX_STORAGE_PRICE_PER_TIB_PER_MONTH, newStoragePrice + ) + ); storagePricePerTibPerMonth = newStoragePrice; } if (newMinimumRate > 0) { - require(newMinimumRate <= MAX_MINIMUM_STORAGE_RATE_PER_MONTH, Errors.PriceExceedsMaximum("minimumRate", MAX_MINIMUM_STORAGE_RATE_PER_MONTH, newMinimumRate)); - } + require( + newMinimumRate <= MAX_MINIMUM_STORAGE_RATE_PER_MONTH, + Errors.PriceExceedsMaximum( + Errors.PriceType.MinimumRate, MAX_MINIMUM_STORAGE_RATE_PER_MONTH, newMinimumRate + ) + ); minimumStorageRatePerMonth = newMinimumRate; } From 09afd068b7fac57ebab173b21b5f814b06123033 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Mon, 27 Oct 2025 01:36:26 +0000 Subject: [PATCH 9/9] mv getCurrentPricingRates to View (#330) Reviewer @rjan90 We want view methods to use `extsload` because this reduces codesize. Codesize is a minor component of gas costs in the Filecoin EVM. #### Changes * mv getCurrentPricingRates to the View contract --- .../abi/FilecoinWarmStorageService.abi.json | 18 -------------- ...oinWarmStorageServiceStateLibrary.abi.json | 24 +++++++++++++++++++ ...lecoinWarmStorageServiceStateView.abi.json | 18 ++++++++++++++ .../src/FilecoinWarmStorageService.sol | 9 ------- .../FilecoinWarmStorageServiceStateView.sol | 4 ++++ ...WarmStorageServiceStateInternalLibrary.sol | 16 +++++++++++++ ...FilecoinWarmStorageServiceStateLibrary.sol | 16 +++++++++++++ 7 files changed, 78 insertions(+), 27 deletions(-) diff --git a/service_contracts/abi/FilecoinWarmStorageService.abi.json b/service_contracts/abi/FilecoinWarmStorageService.abi.json index 12d665a6..d2e9ca0e 100644 --- a/service_contracts/abi/FilecoinWarmStorageService.abi.json +++ b/service_contracts/abi/FilecoinWarmStorageService.abi.json @@ -281,24 +281,6 @@ ], "stateMutability": "view" }, - { - "type": "function", - "name": "getCurrentPricingRates", - "inputs": [], - "outputs": [ - { - "name": "storagePrice", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "minimumRate", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, { "type": "function", "name": "getEffectiveRates", diff --git a/service_contracts/abi/FilecoinWarmStorageServiceStateLibrary.abi.json b/service_contracts/abi/FilecoinWarmStorageServiceStateLibrary.abi.json index 2cb33fa8..02816c8a 100644 --- a/service_contracts/abi/FilecoinWarmStorageServiceStateLibrary.abi.json +++ b/service_contracts/abi/FilecoinWarmStorageServiceStateLibrary.abi.json @@ -295,6 +295,30 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "getCurrentPricingRates", + "inputs": [ + { + "name": "service", + "type": "FilecoinWarmStorageService", + "internalType": "contract FilecoinWarmStorageService" + } + ], + "outputs": [ + { + "name": "storagePrice", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "minimumRate", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "getDataSet", diff --git a/service_contracts/abi/FilecoinWarmStorageServiceStateView.abi.json b/service_contracts/abi/FilecoinWarmStorageServiceStateView.abi.json index 8bdf9744..deeabdc0 100644 --- a/service_contracts/abi/FilecoinWarmStorageServiceStateView.abi.json +++ b/service_contracts/abi/FilecoinWarmStorageServiceStateView.abi.json @@ -258,6 +258,24 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "getCurrentPricingRates", + "inputs": [], + "outputs": [ + { + "name": "storagePrice", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "minimumRate", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "getDataSet", diff --git a/service_contracts/src/FilecoinWarmStorageService.sol b/service_contracts/src/FilecoinWarmStorageService.sol index e047c4b7..d2e85b4b 100644 --- a/service_contracts/src/FilecoinWarmStorageService.sol +++ b/service_contracts/src/FilecoinWarmStorageService.sol @@ -1398,15 +1398,6 @@ contract FilecoinWarmStorageService is }); } - /** - * @notice Get the current pricing rates - * @return storagePrice Current storage price per TiB per month - * @return minimumRate Current minimum monthly storage rate - */ - function getCurrentPricingRates() external view returns (uint256 storagePrice, uint256 minimumRate) { - return (storagePricePerTibPerMonth, minimumStorageRatePerMonth); - } - /** * @notice Get the effective rates after commission for both service types * @return serviceFee Service fee (per TiB per month) diff --git a/service_contracts/src/FilecoinWarmStorageServiceStateView.sol b/service_contracts/src/FilecoinWarmStorageServiceStateView.sol index 4e5068b9..211917b9 100644 --- a/service_contracts/src/FilecoinWarmStorageServiceStateView.sol +++ b/service_contracts/src/FilecoinWarmStorageServiceStateView.sol @@ -70,6 +70,10 @@ contract FilecoinWarmStorageServiceStateView is IPDPProvingSchedule { return service.getClientDataSets(client); } + function getCurrentPricingRates() external view returns (uint256 storagePrice, uint256 minimumRate) { + return service.getCurrentPricingRates(); + } + function getDataSet(uint256 dataSetId) external view diff --git a/service_contracts/src/lib/FilecoinWarmStorageServiceStateInternalLibrary.sol b/service_contracts/src/lib/FilecoinWarmStorageServiceStateInternalLibrary.sol index 117708eb..8df5e514 100644 --- a/service_contracts/src/lib/FilecoinWarmStorageServiceStateInternalLibrary.sol +++ b/service_contracts/src/lib/FilecoinWarmStorageServiceStateInternalLibrary.sol @@ -533,4 +533,20 @@ library FilecoinWarmStorageServiceStateInternalLibrary { nextImplementation = address(uint160(uint256(upgradeInfo))); afterEpoch = uint96(uint256(upgradeInfo) >> 160); } + + /** + * @notice Get the current pricing rates + * @return storagePrice Current storage price per TiB per month + * @return minimumRate Current minimum monthly storage rate + */ + function getCurrentPricingRates(FilecoinWarmStorageService service) + internal + view + returns (uint256 storagePrice, uint256 minimumRate) + { + return ( + uint256(service.extsload(StorageLayout.STORAGE_PRICE_PER_TIB_PER_MONTH_SLOT)), + uint256(service.extsload(StorageLayout.MINIMUM_STORAGE_RATE_PER_MONTH_SLOT)) + ); + } } diff --git a/service_contracts/src/lib/FilecoinWarmStorageServiceStateLibrary.sol b/service_contracts/src/lib/FilecoinWarmStorageServiceStateLibrary.sol index 9e5e0344..edd6a7d7 100644 --- a/service_contracts/src/lib/FilecoinWarmStorageServiceStateLibrary.sol +++ b/service_contracts/src/lib/FilecoinWarmStorageServiceStateLibrary.sol @@ -529,4 +529,20 @@ library FilecoinWarmStorageServiceStateLibrary { nextImplementation = address(uint160(uint256(upgradeInfo))); afterEpoch = uint96(uint256(upgradeInfo) >> 160); } + + /** + * @notice Get the current pricing rates + * @return storagePrice Current storage price per TiB per month + * @return minimumRate Current minimum monthly storage rate + */ + function getCurrentPricingRates(FilecoinWarmStorageService service) + public + view + returns (uint256 storagePrice, uint256 minimumRate) + { + return ( + uint256(service.extsload(StorageLayout.STORAGE_PRICE_PER_TIB_PER_MONTH_SLOT)), + uint256(service.extsload(StorageLayout.MINIMUM_STORAGE_RATE_PER_MONTH_SLOT)) + ); + } }