feat: axlUSDC payment rails + network value-accrual fee (FWSS v1.4.0) - #525
feat: axlUSDC payment rails + network value-accrual fee (FWSS v1.4.0)#525hannahhoward wants to merge 6 commits into
Conversation
dae850d to
839221d
Compare
There was a problem hiding this comment.
The ValueAccrualRouter is entirely unnecessary. FilecoinPay does this automatically. If you want to burn even more than the default 0.5%, you can set the serviceFeeRecipient to address(FilecoinPay), and it will automatically be entered into the rolling double-dutch auction.
There was a problem hiding this comment.
Good call — removed in 91fe067. USDC rails now set serviceFeeRecipient to the FilecoinPay contract itself, so the commission lands in the same account burnForFees already auctions and burns. The router, its deploy steps, and its tests are gone (net −327 lines).
| /// instance, the USDFC list otherwise. Lives here (external library) to keep the per-token | ||
| /// price constants out of the main contract's code size. `pl.token` is populated with | ||
| /// `token`. | ||
| function priceListFor(IERC20 token, IERC20 usdc) public pure returns (PriceList memory pl) { |
There was a problem hiding this comment.
usdc should not be a parameter to this function
There was a problem hiding this comment.
Fixed in 91fe067 — the main contract resolves the token choice with an immutable compare and passes a bool; priceListFor(bool) is internal to the library now, and the USDC address is no longer threaded through the Rails API.
| keyBytes.length == METADATA_KEY_PAYMENT_TOKEN_SIZE | ||
| && keccak256(keyBytes) == METADATA_KEY_PAYMENT_TOKEN_HASH | ||
| ) { | ||
| bytes32 valueHash = keccak256(bytes(metadataValues[i])); |
There was a problem hiding this comment.
this is considerably more expensive than checking if the value is equal to your length-4 string
There was a problem hiding this comment.
Fixed in 91fe067 — length check plus direct bytes4/bytes5/bytes12 comparisons; the hash constants are gone.
| if (valueHash == PAYMENT_TOKEN_VALUE_USDC_HASH) { | ||
| if (address(usdc) == address(0)) { | ||
| revert Errors.UnsupportedPaymentToken(metadataValues[i]); | ||
| } |
There was a problem hiding this comment.
We have also thought about supporting USDC. My preference is for having separate contracts per token, with the only difference being the immutable token and the price list. That would be a much smaller change than your PR, and would have a smaller storage footprint per data set.
There's not a good reason for USDC and USDFC data sets to share an account.
There was a problem hiding this comment.
With the router gone (91fe067) the delta here is a lot smaller than what you reviewed — the branch has also since been rebased onto current main. Separate per-token contracts would drop the dispatch and the per-data-set token slot, but each deployment duplicates the proxy/view/provider-approval/FilBeam wiring, SDKs have to target a second service address, and since the EIP-712 domain binds signatures to the verifying contract we'd lose the single-signature-format property — token choice would move from a signed metadata key to a contract-address choice. Keeping the shared contract for now; happy to dig into the split if you still think it pays.
Address wjmelements's review on FilOzone#525: - Drop the ValueAccrualRouter. FilecoinPay already auctions and burns whatever accrues to its own account: setting a rail's serviceFeeRecipient to the FilecoinPay contract routes the operator commission into accounts[token][payments], the same pot burnForFees sells through the rolling Dutch auction. USDC rails now set serviceFeeRecipient = FilecoinPay; the router contract, its deploy-script steps, its errors, and the constructor wiring are gone. - Stop threading the USDC token address through the Rails library. The main contract resolves token -> price list once (an immutable compare) and passes a bool; priceListFor(bool) is now internal to the library. - Compare metadata strings directly instead of hashing. The paymentToken key and its USDC/USDFC values are length-checked and compared as fixed-size words, replacing the keccak256 comparisons. 819 tests pass (822 before: 5 router-specific tests replaced by 2 covering commission accrual to FilecoinPay and burnForFees buyout). Storage layout unchanged (25 slots); FWSS runtime size 23,809 bytes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds bridged USDC (axlUSDC, 6 decimals) as a second payment token for FilecoinWarmStorageService data sets, and introduces a network value-accrual fee (NVAF) on USDC rails: a 200 bps operator commission, locked into each USDC data set's rails at creation, routed to a new ValueAccrualRouter contract, sold for native FIL through a recurring Dutch auction (the same mechanism FilecoinPay uses for its network fee), and burned to the burn actor (f099). USDFC data sets are unchanged and remain commission-free. - Token selection rides the payer-signed `paymentToken` metadata key, so the existing CreateDataSet EIP-712 signature covers the choice; no signature-format changes. - PriceListUSDC grosses SP-bound amounts up by 1/(1 - 2%) so the SP nets the USDFC-equivalent after the commission; the per-dataset fee sits at the 6-decimal per-epoch quantization floor ($0.0864/month). - The commission cap equals the gross-up (200 bps) so the SP-parity guarantee holds for every permitted owner setting. - Storage layout is append-only (two new slots); pre-upgrade data sets resolve to USDFC in all fee paths, and a stored token matching neither token immutable reverts loudly (UnknownRailToken). - Deploy scripts deploy the router and pass the new constructor args; mainnet defaults USDC to axlUSDC, calibration defaults to disabled. Removes calibnet's pinned RAILS_LIB_ADDRESS (Rails ABI changed) and adds a linked-library redeploy step to the upgrade checklist. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Providers net USD 5 per TiB-month on USDC rails (double the USDFC list's USD 2.50 base); the posted price carries the same 1/(1 - 2%) NVAF gross-up, giving 5.102041 USDC/TiB/month. All other USDC amounts keep the USDFC-equivalent base. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The v1.3.0 mainnet deploy (FilOzone#523) pinned a Rails library built before this change. Rails' ABI changes here (createRails signature, new public functions), so reusing the pinned address would link the new implementation against selectors the deployed library does not have. Removing the pin makes the next deploy relink against a fresh Rails, matching the calibnet unpin and the new upgrade-checklist step. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Reformat with forge 1.3.5 (CI pin); newer local forge formatted a few constructs differently and would fail the CI fmt check. - Drop unused re-exported constants from the PriceListUSDFC import (EPOCHS_PER_DAY moves consumers to PriceList.sol, GIB/MIB unused). - Use checked SafeERC20 transfers for the 6-decimal mock in tests. forge fmt --check and forge lint are clean under v1.3.5; 823 tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Address wjmelements's review on FilOzone#525: - Drop the ValueAccrualRouter. FilecoinPay already auctions and burns whatever accrues to its own account: setting a rail's serviceFeeRecipient to the FilecoinPay contract routes the operator commission into accounts[token][payments], the same pot burnForFees sells through the rolling Dutch auction. USDC rails now set serviceFeeRecipient = FilecoinPay; the router contract, its deploy-script steps, its errors, and the constructor wiring are gone. - Stop threading the USDC token address through the Rails library. The main contract resolves token -> price list once (an immutable compare) and passes a bool; priceListFor(bool) is now internal to the library. - Compare metadata strings directly instead of hashing. The paymentToken key and its USDC/USDFC values are length-checked and compared as fixed-size words, replacing the keccak256 comparisons. 819 tests pass (822 before: 5 router-specific tests replaced by 2 covering commission accrual to FilecoinPay and burnForFees buyout). Storage layout unchanged (25 slots); FWSS runtime size 23,809 bytes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two build fixes from rebasing onto main: abandonRails (underfunded-payer handling, FilOzone#520) uses DEFAULT_LOCKUP_PERIOD, which this branch moved from PriceListUSDFC to PriceList — import it from its new home; and the new announce-upgrade-only-owner test constructs FWSS, which now takes the optional USDC token parameter. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
95e259b to
dc02b43
Compare
|
@wjmelements @BigLep sorry.... I never saw this got reviewed. I've responded to changes and rebased on latest. At the same time, I never meant to be primary dev on this especially since a lot of it was AI generated. I can keep working if you like -- let me know -- otherwise I'll tree this as a WIP contribution that you guys are picking up. I also believe @rvagg and @jennijuju had some significant design changes they wanted to make. |
Yeah, understood - all good. My name showing up in the audit log was just an agent I run locally using my credentials for organizing things. I'm not aware of the plan around this specific area or timeline. I don't know enough to have expectations that you'd driving this or doing development work here. I assume here would be more discussion and agreement before this get actively engaged in. |
Summary
Adds bridged USDC (axlUSDC, 6 decimals) as a second payment token for FilecoinWarmStorageService data sets, and introduces a network value-accrual fee (NVAF) on USDC rails: a 200 bps operator commission, locked into each USDC data set's rails at creation, accrued to the FilecoinPay contract itself, sold for native FIL through FilecoinPay's existing recurring Dutch fee auction, and burned to the burn actor (f099). USDFC data sets are unchanged and remain commission-free, preserving a real posted-price discount for the web3-native token while the network captures value when customers choose USDC.
Design
Token selection. A data set's rail token is chosen by the payer-signed
paymentTokenmetadata key ("USDC","USDFC", or absent = USDFC) — the same extension pattern aswithCDN. The choice is covered by the existing CreateDataSet EIP-712 signature, so there are no signature-format changes; SDKs only need to pass one more metadata entry. Per-token pricing resolution lives in theRailsexternal library to stay inside the 24 KiB limit: the main contract resolves the token choice with an immutable compare and the library keys the price list off that.Pricing (
PriceListUSDC). Storage is priced from a $5.00/TiB/month base (double the USDFC list's $2.50) so providers net $5 per TiB-month on USDC rails; every other amount keeps the USDFC-equivalent base. All SP-bound amounts are grossed up by 1/(1 − 2%) — posted storage price 5.102041 USDC/TiB/month — so the SP nets the base after the commission and the customer bears the NVAF. Because the 0.5% FilecoinPay network fee applies identically to both tokens, it cancels out of the gross-up. The per-dataset fee is set at the 6-decimal per-epoch quantization floor (1 unit/epoch = $0.0864/month), addressing the truncation-to-zero behavior noted in #468; size-proportional rates for very small data sets still truncate toward zero, which the floor backstops.Commission. Carried as the FilecoinPay per-rail
commissionRateBpswithserviceFeeRecipientset to the FilecoinPay contract itself, on all three rails (PDP, cache-miss, CDN) of a USDC data set. FilecoinPay fixes a rail's commission at creation, so changes only affect new data sets. The owner can stage the rate viasetUSDCCommissionBps(evented); the cap deliberately equals the gross-up (200 bps) so the SP nets at least the list's base amounts for every permitted setting — raising the NVAF beyond 2% requires an upgrade that also revises the posted prices.Burn. Commission accrued to FilecoinPay's own account lands in the same pot as its 0.5% network fee and sells through the contract's live
burnForFeesrolling Dutch auction (price decays 3/4 per week, each purchase resets to 4× the clearing price), with the buyer's FIL destroyed via the burn actor. No new contract, no owner, no parameters — the burn path is the one already deployed and running in FilecoinPay. A DEX TWAP swap was considered and rejected: the only live axlUSDC/WFIL pool holds ~$280k with observation cardinality 1, and no Sushi V3 SwapRouter is deployed on Filecoin — the auction needs no on-chain liquidity or oracle.Upgrade safety
dataSetPaymentToken,usdcCommissionBps);make check-layoutpasses.UnknownRailToken) instead of silently pricing a 6-decimal token with the 18-decimal list — guards against a future upgrade changingusdcTokenAddresswhile old-token data sets exist.migrate()backfills the default NVAF only from the uninitialized (zero) state and emitsUSDCCommissionBpsUpdated.createRailssignature, new public functions). This PR removes the pinnedRAILS_LIB_ADDRESSfromdeployments.jsonfor both networks (the v1.3.0 deploys pinned a pre-change Rails) so the next deploy relinks against a fresh Rails, and adds a linked-library redeploy step to the upgrade checklist — reusing the old address would brick every core flow after upgrade.0xEB466342C4d449BC9f53A865D5Cb90586f405215.Testing
MultiTokenValueAccrual.t.sol; no behavioral changes for USDFC data sets.vm.store+announcePlannedUpgrade/upgradeToAndCall), fee-auction arming on accrual and aburnForFeesbuyout with the FIL burned to f099 via the FVM precompile mocks, and funding-requirement reverts.FilecoinWarmStorageServicedeployed size: 24,193 bytes (383 under the EIP-170 limit).forge fmtclean; ABIs and generated state-view/layout files regenerated.Known trade-offs
settleRailcalls on a floor-rate stream can divert it entirely to the (ceil'd) network fee — grief-only, capped at $0.0864/month per data set, costs the caller gas per epoch.burnForFees(lot pricing independent ofrequested, overpayment burned, no refunds). Both streams end burned either way; per-rail commission amounts remain observable in settlement events.Test plan
USDC_TOKEN_ADDRESS)🤖 Generated with Claude Code