You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
builder-stake: size the deposit against the committed rate (#4285)
Implements [B2](malbeclabs/edge-builder#9).
Stacked on #4284; review that first, this diff is the six files above
the base.
## Summary
- `ProgramConfig` gains a tier table: three 2Z amounts, one per RFC-28
rate tier, set by the admin through `ConfigureProgram`.
- `InitializeBuilderStake` looks up the amount for the rate the builder
commits to and pins it on the stake as `required_2z_amount`.
- `BuilderStake::is_funded()` answers whether the stake holds what it
owes.
- A malformed table is refused: a zero amount, or a higher tier that
costs less than a lower one.
## Decisions worth arguing with
**A table, not a price feed.** RFC-28 quotes about \$100k / \$200k /
\$500k but says the deposit is "denominated in 2Z, fixed at the price
prevailing when the tier is set". So an admin writes three numbers and
no oracle is involved.
**The rate ceilings are compiled in, not configured.** 1 Gbps and 5 Gbps
have to agree with `StakeTier::max_rate_bits_per_sec` in the
serviceability program on the DZ ledger. If those two drift, a builder
funds one tier here and its feed is checked against a different one
there. Two configurable copies is exactly how that drift happens.
**The requirement is pinned per stake, not read live.** Repricing a tier
does not move what an existing stake owes, so a builder who fully funded
a stake cannot wake up under-funded because the admin changed a number.
This is the stronger reading of "fixed at the price prevailing when the
tier is set", and it is one field. There is a test for it.
**`Deposit` still accepts any positive amount, which is a departure from
how issue #9 was written.** Requiring the full amount in one transfer
would make "a stake exists implies it is funded" true, which is
tempting. Against it: a builder may reasonably fund from more than one
source or split a large transfer, and nothing bad happens with a short
stake, because only a funded stake gets mirrored to the DZ ledger and so
a short one backs no feed. The guard that actually matters is `Withdraw`
in [B3](malbeclabs/edge-builder#10), which
must not drop a stake below its requirement regardless. Over-funding is
allowed too, since RFC-28 lets a builder withdraw the excess after the
hold.
**An unset table sizes nothing rather than sizing everything at zero.**
`required_2z_amount` returns `None` for a zero entry, so a program
unpaused before it was configured takes no stake instead of handing out
free ones.
## Struct change
`BuilderStake` grew `required_2z_amount`, so its size assertion moves
from 136 to 144. Nothing is deployed, so there is no migration. I also
dropped the `ProgramConfig` size assertion: it is allocated at 10kb, so
a new setting grows into slack, which is the same reasoning
`revenue-distribution` gives.
## Testing Verification
`make test-sbf`, 16 tests (3 unit, 12 integration, plus the id check):
- Every tier boundary, at and either side of each ceiling: 1 Gbps and
one unit over, 5 Gbps and one unit over, and `u64::MAX`.
- A stake starts unfunded, two part-deposits reach the requirement with
the last unit tipping it over, and over-funding is accepted.
- An unpaused program with no tier table refuses to take a stake at all.
- Three malformed tables are refused (a hole at the first tier, 5 Gbps
cheaper than 1 Gbps, unmetered cheaper than 5 Gbps), and a well-formed
one lands and reads back.
- Raising every tier tenfold leaves an existing funded stake funded and
its requirement unchanged, while the next stake posted pays the new
price.
- The seven B1 tests still pass unchanged.
## Size
About 90 non-test lines.
Copy file name to clipboardExpand all lines: CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -19,6 +19,7 @@ All notable changes to this project will be documented in this file.
19
19
- The TypeScript and Python `Feed` deserializers read the RFC-28 tail and synthesize `Active` for an account that carries no status byte, matching the Rust program. New `feed_legacy` fixture covers that path alongside the updated `feed` fixture.
20
20
- Solana programs (`solana/`)
21
21
- New `builder-stake` program at `dzbschFChpPoWihZFdnYjyzHJicZwPHb6QTntHjhLki`, holding the 2Z bond a builder posts before deploying a feed under RFC-28. A `BuilderStake` PDA, a 2Z token account owned by it, and `InitializeProgram`, `SetAdmin`, `ConfigureProgram`, `InitializeBuilderStake` and `PostBond`. A bond rather than a deposit: it is returnable after the hold and forfeitable by slashing, and `deposit` carries neither. The address is keyed on `(builder, stake_index)` rather than the builder alone, because RFC-28 collateralizes each feed on its own bond and a builder-only address would cap a builder at one stake for life. The program starts paused, so a deployment with no admin and no tier table holds nothing. No slash instruction yet: the burn authority is what makes this its own deployable, and writing it before the verdict signer is settled means writing it twice. Bond sizing and the six-month hold are not here either.
22
+
-`builder-stake` sizes a bond against the rate its feed commits to. An admin sets three 2Z amounts, one per RFC-28 rate tier; RFC-28 quotes the tiers in dollars but fixes the bond in 2Z at the price prevailing when the tier is set, so this is a table rather than a price feed. The rate ceilings are compiled in, because they have to agree with `StakeTier` in the serviceability program. A stake's requirement follows the tier table while the stake is short and stops moving once it is funded, so repricing cannot under-fund a builder who already paid in full, and cannot be dodged by pre-creating stakes for the cost of rent and funding them after a rise. A table with a hole, or one where more rate costs less, is refused.
0 commit comments