feat: operational pricing#488
Conversation
Assisted-by: Claude:claude-sonnet-4-6
Assisted-by: Claude:claude-sonnet-4-6
Assisted-by: Claude:claude-sonnet-4-6
Assisted-by: Claude:claude-sonnet-4-6
Assisted-by: Claude:claude-sonnet-4-6
Assisted-by: Claude:claude-sonnet-4-6
There was a problem hiding this comment.
Pull request overview
Implements per-operation pricing for FilecoinWarmStorageService by introducing one-time operation fees paid from a fixed “lifecycle reserve” on the PDP rail, while also removing the previously-charged sybil fee burn-rail flow.
Changes:
- Add USDFC operation-fee constants (create dataset / add pieces / schedule removals / consent termination) plus lifecycle reserve target + replenishment threshold.
- Track and batch-flush pending one-time fees via
updateStorageRates, including reserve replenishment viamodifyRailLockup. - Add Foundry tests covering fee accumulation/flush and replenishment behavior; remove sybil-fee tests and docs.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| SPEC.md | Documents operation fees and lifecycle reserve batching/replenishment behavior. |
| README.md | Updates pricing overview to include one-time operation fees and lifecycle reserve concept. |
| CHANGELOG.md | Removes sybil-fee related release notes. |
| service_contracts/src/lib/PriceListUSDFC.sol | Adds operation-fee constants and lifecycle reserve parameters; removes SYBIL_FEE. |
| service_contracts/src/lib/Rails.sol | Seeds lifecycle reserve on rail creation; updates updateStorageRates to flush one-time fees and replenish lockup. |
| service_contracts/src/FilecoinWarmStorageService.sol | Adds pending-fee + reserve accounting, operation-fee accrual points, topUpLifecycleReserve, and updated rate/fee flush flow. |
| service_contracts/src/lib/FilecoinWarmStorageServiceStateLibrary.sol | Extends dataset view struct decoding to include pending fees + reserve balance. |
| service_contracts/src/lib/FilecoinWarmStorageServiceStateInternalLibrary.sol | Same as above for the internal (generated) view library. |
| service_contracts/src/lib/FilecoinWarmStorageServiceLayout.json | Updates storage layout to include the new packed slot for fee/reserve fields. |
| service_contracts/abi/FilecoinWarmStorageService.abi.json | Adds ABI entry for topUpLifecycleReserve. |
| service_contracts/abi/FilecoinWarmStorageServiceStateView.abi.json | Extends DataSetInfo view structs with pending fees + reserve balance fields. |
| service_contracts/abi/FilecoinWarmStorageServiceStateLibrary.abi.json | Same ABI struct extension for the library ABI. |
| service_contracts/test/FilecoinWarmStorageService.t.sol | Updates expectations for removed burn rail and new lifecycle reserve lockup assumptions; removes sybil-fee tests. |
| service_contracts/test/OpFees.t.sol | Adds new tests validating fee accrual/flush and lifecycle reserve replenishment behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Some good comments from copilot in there. I have two main thoughts: First: The 0.1 sybil was still serving a purpose, as per the comment in there and as described in https://www.notion.so/filecoindev/Minimising-Client-Griefing-Potential-via-SP-Deposit-36bdc41950c180a283b3c4caf39c0895 - with only a data set creation fee it's cheap to cause an SP to lock up considerable FIL for 30+ days. Roughly 1:40: ~$100 of create fees locks up ~4000 FIL of SP capital. Luca suggested a matched client-side lockup (0.1 USDFC, returned 30 days after termination) and the lifecycle reserve is essentially that, locked at creation. My hesitation as mentioned in response to Luca in the doc is that the costs are still different on both sides: lockup costs the attacker basically nothing (they just wait and get it back), while the attacked SP has a drained wallet and is denying service to all current and future clients (including proving) until they top-up or wait 30 days. So the question for you and Luca: is a matched lockup a clear enough disincentive on its own, or do we want a burned component on top, also what do we suggest to SPs on how much FIL to hold now? No strong opinion on mechanism as long as we don't have such an obvious DoS vector. Second: Batching the rail changes is clever, but the deferral brings us back to the Ideally every client-incurred fee is secured from the client at the moment they incur it. The revert belongs on the client's transaction, not the SP's, and the SP is never out of pocket because a client found a loophole.
|
I see your point, which basically says: "even if client locks, then client does not have to operate, while SP does", which is a fair point. I think that adding a burnt part on the client side could make sense in the end. Curious to know what others think specifically on this. The high level reasoning on my side is that having some burn cost other that capital cost on the client side could be a disincentive in DoSsing the SP. See this as a reference of old formulation |
Assisted-by: Claude:claude-sonnet-4-6
Assisted-by: Claude:claude-sonnet-4-6
Assisted-by: Claude:claude-sonnet-4-6
|
After further conversation with @rvagg, I have a more complete picture. My opinion is the following:
My personal intuition is that, at least at the beginning, it is not out of mind to assume that clients wont be that malicious/SP and client somehow trust each other. But it can be i'm not viewing the full picture. If we agree on the above, we can stay as we are, avoiding complexity, flag the potential abuse and keep the sybil burn as a potential patch in the future (if needed). |
…istent and terminated datasets Assisted-by: Claude:claude-sonnet-4-6
I agree that we should ensure
|
…s and add depletion test Assisted-by: Claude:claude-sonnet-4-6
…flush Assisted-by: Claude:claude-sonnet-4-6
…ay settle period Assisted-by: Claude:claude-sonnet-4-6
|
I did some measurement in devnet of the cost of the burn rail last night. At fresh devnet gas using the stand-alone createDataSet we go from 228M with the burn rail to 130M without it, so it's consuming ~42% of the gas, at least in devnet (I'm unsure how the storage costs would scale that on mainnet, would FP be more expensive to operate or less than PDP+FWSS?). So yeah, that biases me toward removing it too. Let's proceed without it for now and talk about possibly bumping the create fee to the SP; not so high that it's a client-farming incentive and we can frame it as both an incentive, reimbursement for the createDataSet gas and maybe the administrative hassle of setting up a new data set to manage. |
Reviewer @rvagg
Implements #469
One difficulty of this task is that one-time payments have to come from the fixed lockup, and, after termination, the lockup cannot be increased further.
One design goal is to avoid reading
getRail, which reads many fields.Another goal is to limit the additional calls to
modifyRailPaymentandmodifyRailLockup.The main approach is to aggregate the fees into batches.
Fixed lockup is replenished when the lockup (lifecycle reserve) gets low.
It can also be prefunded by
topUpLifecycleReserveI also remove the sybil fee (briefly discussed here) in a separate commit (allowing an easy revert if necessary).
Changes
topUpLifecycleReserve