feat: billing contract v1 (WIP-107)#830
Conversation
| // Members // | ||
| //////////////////////////////////////////////////////////// | ||
|
|
||
| // DO NOT REORDER! To ensure compatibility between upgrades, it is exceedingly important |
There was a problem hiding this comment.
question: should we leave a gap variable for future updates of this contract?
There was a problem hiding this comment.
doesn't this only make sense if another contracts inherits from BillingContract?
| * `maxAmount` guard. Pulls WLD from msg.sender to the fee recipient. | ||
| * @param payments The per-RP payment instructions. | ||
| */ | ||
| function pay(RpPayment[] calldata payments) external; |
There was a problem hiding this comment.
nit. i feel like collectDebt would be better naming considering all the accounting logic happening on this call?
There was a problem hiding this comment.
Why? pay seems to be the correct name here.
| // Members // | ||
| //////////////////////////////////////////////////////////// | ||
|
|
||
| // DO NOT REORDER! To ensure compatibility between upgrades, it is exceedingly important |
There was a problem hiding this comment.
doesn't this only make sense if another contracts inherits from BillingContract?
| /// that ever existed keeps its true boundaries and windows: epoch `e` is governed by the | ||
| /// newest era with `startEpoch <= e`, so timing changes never affect existing epochs. | ||
| /// Grows by one entry per {setTiming} call (rare, owner-only), never per epoch. | ||
| Era[] internal _eras; |
There was a problem hiding this comment.
worth discussing to change to something like the following, then we have future compatibility to change the Era struct.
mapping(uint32 => Era) internal _eras;
uint32 internal _eraCount;
There was a problem hiding this comment.
I feel like if we need to update this, we can just deploy a new contract version. Not sure if taking on extra complexity here is worth it.
3a018b7 to
88e7b55
Compare
88e7b55 to
7336788
Compare
Co-authored-by: kempy <33121795+Kemperino@users.noreply.github.com>
…-id-protocol into feat/billing-contract-v1
Add permissionless view getters so off-chain keepers can decide whether finalization is pending without blind-firing transactions: - latestClosedEpoch(): the latest epoch whose voting window has closed. - latestFinalizedEpoch(): the latest fully-finalized epoch. Both are inclusive high-water marks returning (bool exists, uint32 epoch), so the "nothing yet" genesis case is unambiguous and the pending range is a clean [latestFinalized+1, latestClosed]. Co-authored-by: Cursor <cursoragent@cursor.com>
Add votingWindowEnd(epoch) and paymentWindowEnd(epoch) alongside epochEnd, completing the per-epoch timing boundaries. Both derive from the era that governs the window (the era of epoch+1), so they stay exact across setTiming changes — unlike a client-side epochEnd + current-era-votingWindow estimate. Off-chain keepers can use these directly: the finalizer for the voting-window close, and the (upcoming) payer for the payment deadline. Co-authored-by: Cursor <cursoragent@cursor.com>
| * equal to type(uint256).max. | ||
| * @param tiers The new tier schedule. | ||
| */ | ||
| function setTierSchedule(Tier[] calldata tiers) external; |
There was a problem hiding this comment.
I'm wondering if this shouldn't be in something like ITieredRebaedBillingContract.sol
There was a problem hiding this comment.
same for setRebatePeriodEpochs
There was a problem hiding this comment.
my point here is that we should leave in IBillingContract only the parts relevant to the core billing protocol - i.e. request counting, voting, etc.
how exactly fees are calculated is an "implementation detail" that's not relevant to e.g. oprf nodes
| * @param votingWindow The voting window in seconds. | ||
| * @param paymentWindow The payment window in seconds. | ||
| */ | ||
| function setTiming(uint64 epochLength, uint64 votingWindow, uint64 paymentWindow) external; |
There was a problem hiding this comment.
imo setter methods in general don't belong in this interface
Replace latestFinalizedEpoch and latestClosedEpoch with epochWatermarks so keepers read both watermarks from the same block and avoid read skew between two separate calls. Co-authored-by: Cursor <cursoragent@cursor.com>
Summary
First version of the on-chain Billing Contract (WIP-107): OPRF nodes count unique RP requests per ~epoch, submit EIP-712-signed votes, and the contract finalizes the lower-median count (gated on quorum), prices it through a tiered WLD fee schedule, accrues per-RP debt, and surfaces
is_blockedfor RPs past their payment window.Scope of this PR: contract +
IBillingContractinterface + Foundry tests only. No deploy script and no OPRF node-side Rust (separate follow-up entries).Deviations from initial spec