Skip to content

SWIP-067: Custody separation - #108

Open
0xCardiE wants to merge 22 commits into
masterfrom
swip-67-custody-separation-and-forks
Open

SWIP-067: Custody separation#108
0xCardiE wants to merge 22 commits into
masterfrom
swip-67-custody-separation-and-forks

Conversation

@0xCardiE

@0xCardiE 0xCardiE commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Read SWIP-67

Change the storage-incentive rules without moving user BZZ, and without leaving an admin path that can steal it.

What this gets us

  • Frozen cores hold deposits (PostageAccounting, StakingCore). No proxy, no withdraw(address), no copyBatch.
  • Replaceable policies hold the rules and cannot name a payout address. Pointers flip after a core-enforced timelock; the redistributor only at a round start.
  • Redistribution and PriceOracle stay unsplit — they hold no deposits and are redeployed as-is.
  • Staking lifecycle is storage-incentives#309 (queued withdraw / exit). This SWIP splits that contract; it does not invent a second unbonding design.
  • One stake migration and one treasury-matched postage genesis. After that, upgrades are not fund movements.
  • Bee still compiles contract addresses into the binary.

Sequencing and parameters are in the SWIP.

Splits each fund-holding storage-incentive contract into a frozen custody
core and a replaceable policy contract, and specifies the fork-migration
protocol that policy replacement runs under.

Consolidates the security thread from storage-incentives#310 and the
migration thread from Andrew Macpherson's "Forking Swarm" into one
proposal, on the basis that both stem from state and logic sharing a
contract.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@0xCardiE 0xCardiE added draft protocol describes a process every swarm node must implement and adhere to improvement enhancement of an existing protocol/strategy/convention labels Sep 7, 2026
@0xCardiE 0xCardiE self-assigned this Sep 7, 2026
0xCardiE and others added 7 commits September 7, 2026 11:55
storage-incentives#310 is an upgradeability proposal, not a security one;
describe it that way rather than as "a security thread".

Remove the claim that full redeployment cost the network ten days of
downtime and half its target replication. The 2025 gap was a consequence
of the old stake registry's pause being scheduled well after the client
release, not a lower bound on migration cost. The v0.9.3/v0.9.4 evidence
is kept, but recast as two scheduling failures that F3/F4 fix by
construction, and the argument for Part 1 no longer leans on it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… fixes

Correctness fix. The first draft put price ingestion in PostagePolicy. A
batch's normalisedBalance is denominated in the issuing contract's
outpayment accumulator, so a policy-side accumulator would rebase every
batch on every policy replacement — wrong expiry and premature reserve
eviction, once per upgrade instead of once per migration. The accumulator
and normalised-balance arithmetic move into PostageAccounting, and the
frozen-outpayment-model consequence is stated (C2.7, C4).

Follow-on: `accrue` is gone. The core derives remaining balances itself,
and `expire` is permissionless and self-verifying, so policy has no
pot-accrual authority at all.

C3.1 eligibility clock: StakingPolicy counts eligibility from
min(depositBlock, preRegistrationBlock), so a mass re-staking event does
not open a rolling participation trough. Staggering to avoid a gas spike
lengthens the trough rather than fixing it.

C3.2 accounts and nodes: deposits are per account, overlay mapping is
policy-side. Fleet operations scale with accounts, withdrawal authority
separates from the node signer, and the shared-account slashing question
is recorded as open.

F7 cutover typology: Type A (wire-breaking, single game ABI, no
dual-mode) vs Type B (contract-only, dual bindings required). F7.1
requires any cutover needing a branch in consensus-critical computation
to be Type A, since a dual-mode sampler is itself a dissent source.
Old F7 renumbered to F8.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds a Contents block and a "Normative requirements at a glance" table so
an 880-line spec can be navigated and reviewed rule by rule.

Fixes two defects introduced by the previous edit: a list item in C2.7
was orphaned below the accumulator discussion, breaking the list, and F8
still described a stake migration as "the ten-day outage" after that
attribution was removed everywhere else.

Prose: breaks the Abstract's Part 1 sentence, removes first-person
hedging in C2.5, C5, Motivation and Rationale, and normalises
cross-references to bare rule ids.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Factual corrections, checked against the deployed sources:

- StakeRegistry declares no PAUSER_ROLE; pause() checks DEFAULT_ADMIN_ROLE
  and the OnlyPauser() error name is misleading. Corrected in Motivation
  and in the C5 residual-trust table.
- remainingBalance clamps at zero; the quoted formula now says so.
- Overlay derivation is network-scoped, not bound to the wire protocol —
  NetworkId is admin-mutable via changeNetworkId.
- The round-306865 freeze multiple is not derivable from the freeze
  formula alone, so the unquantified claim replaces it.

Contradictions:

- Simple Summary and the C1 table called cores "no admin", which C2.5
  explicitly forbids. Both now say the core has no admin power over the
  money it holds.
- Roadmap stage 6 proposed extending POLICY_TIMELOCK, which C2.5 declares
  immutable and C2.7 gives no upgrade path to change.
- C2.2 said policy "may assert an accrual" while C4 says accrual is not a
  policy primitive at all. The clause is gone.
- F5 claimed a "deliberate exception" to F4 and then argued no second
  authorisation exists. It is a payment overlap, not an authority overlap,
  and the funding assertion moves back to open question 4.
- F7 Type B required un-restarted operators to keep earning, which F2.1
  makes impossible; it now says operators running a release that carries
  both bindings.
- Backwards compatibility put an admin-gated migration function inside the
  core; the assisted path now goes through the ordinary fund() call.
- The StakingCore invariant test subtracted totalSlashed from a balance
  that still holds it, since slashing burns in place.

Unenforceable or dead requirements:

- F3 required the outgoing redistributor to stop accepting commits a round
  early. Commit, reveal and claim all fall inside one ROUND_LENGTH, so a
  boundary-aligned cutover already orphans nobody and the bullet only
  created a dead round. The matching cutover test went with it.
- C2.4's per-node slash cap is unenforceable once C3.2 makes the core
  account-scoped, and one constant cannot be both the per-node and the
  aggregate cap. Reduced to a single aggregate bound.
- C2.4 windows were tied to ROUND_LENGTH, which is per-branch and
  replaceable; they are now core-owned block counts.
- C2.6 guarded against policy state the core cannot hold; it now names the
  lock, which is the real mechanism.
- C2.1 wrote a prohibition with RFC 2119 MAY.

Consistency: slash/lock/depositOf take an account, not a node (C3.2);
claimPot(amount) and withdraw(amount) match the interface; C2.5 is retitled
for the pointers it actually governs; exit() is the two-step requestExit
form everywhere.

Cuts: the v0.9.3 incident was told twice in Motivation; the Rationale
proxies entry duplicated Motivation; the accumulator and copyBatch
explanations were doubled. Assorted self-congratulation and announcement
sentences removed throughout. 920 lines to 870.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…d migration, windowed cutover

Design changes, each forced by checking the spec against the deployed
contracts:

- Batch migration: PostageStamp has no path that releases an unexpired
  deposit (withdraw moves only the pot), so "assisted migration through
  ordinary fund()" was unimplementable. Replaced with a treasury-matched
  genesis: batch state seeded at deployment, matching BZZ transferred in,
  genesis sealed in the same ceremony, treasury reimbursed from the old
  pot as seeded batches expire — the final, announced use of
  withdraw(beneficiary). No unbacked creation path ever exists.

- Core accounting: the C4 interface had no batch size, so the core could
  not compute normalised balances, conservation, or pot accrual. fund()
  now records depth, resize() replaces increaseDepth, and
  validChunkCount / lastExpiryBalance are core aggregates with the
  accrual identity from expireLimited written out normatively.
  Consequence: the expiry ordering moves back into the core — live-chunk
  accrual is only sound when the core can prove no expired batch is
  still counted, which requires knowing the minimum normalised balance.
  C2.7 names this the second-largest frozen-core risk instead of calling
  the tree a rebuildable policy-side index.

- fund() call topology specified: creation is policy-gated
  (admissibility), exits and expiry are direct on the core; new ids bind
  to (originator, nonce) so announced ids cannot be front-run; seeded
  ids exist only before genesis seal.

- StakingCore records firstDepositBlock, making the C3.1 eligibility
  clock computable. C3.2 resolved with a coverage requirement instead of
  an open question.

- F3: exact-block multisig execution replaced by an execution window
  [activationBlock, activationBlock + EXECUTION_WINDOW), aligned to the
  outgoing game's round length; ROUND_LENGTH changes are Type A. C2.5
  pointers are cancellable. F2.3 added: Cutover governance is
  liveness-only.

- F5: wind-down must be pre-funded before cutover; a retired
  Redistribution never regains pot access. Resolves the F4 contradiction
  and the stranded-pot question.

- Honesty fixes: C1 table no longer says "no admin"; Redistribution
  acknowledged as transient pot custodian on the claimPot path; the C2.4
  pot bound described as capping acceleration, not stopping outflow
  below the honest rate; exit() named as withdrawable stake with
  EXIT_DELAY >= the freeze horizon; refundBatch named as a change to the
  storage promise whose introduction is a Type A cutover.

Open questions cut from eight to five; resolved ones (wind-down funding,
migration tail, shared-account slashing, stranded pot) folded into the
spec. Concision pass throughout.
@0xCardiE 0xCardiE changed the title SWIP-067: Custody separation and fork migration SWIP-067: Custody separation and cutover Sep 8, 2026
@0xCardiE 0xCardiE changed the title SWIP-067: Custody separation and cutover SWIP-067: Custody separation Sep 8, 2026
Use the queued withdraw/exit workflow instead of a parallel requestExit design, and keep the custody split of that contract.
Policy execute is not round-aligned; Redistribution freeze can delay a staking payout, admin pause cannot.
Refund forfeits a slice to the pot; postage genesis is a self-top-up plus withdraw from the stopped contract; a later pricing model is a new core.
Rejected alternatives belong with the problem statement, before the specification.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

draft improvement enhancement of an existing protocol/strategy/convention protocol describes a process every swarm node must implement and adhere to

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants