Skip to content

feat: New Staking workflow - #309

Open
0xCardiE wants to merge 65 commits into
masterfrom
feat/new_staking
Open

feat: New Staking workflow#309
0xCardiE wants to merge 65 commits into
masterfrom
feat/new_staking

Conversation

@0xCardiE

@0xCardiE 0xCardiE commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Summary

  • replace immediate staking mutations with queued stake updates for deposits, top-ups, height changes, overlay changes, withdrawals, and exits
  • simplify effective stake so an unfrozen node's effective stake is just its current balance, and a frozen node's effective stake is 0
  • add forward-looking staking preview getters with explicit lookahead semantics instead of AtRound naming
  • reconcile queued withdrawals after slashing so queued payouts cannot exceed the remaining stake
  • make redistribution claim payout atomic with the postage withdrawal
  • update deployment config and tests to cover the new staking lifecycle, withdrawal/exit behavior, payout retry behavior, winner selection stability, and Bee-facing preview semantics
  • applyUpdates reverts on frozen withdrawals/exits, post-exit operations revert QueueClosed instead of NotStaked, constructor validates wait-parameter ordering
  • fix _applyReadyUpdates so privileged callers (freezeDeposit, slashDeposit, migrateStake) can never be blocked by a frozen withdrawal — they skip frozen entries instead of reverting
  • store protocol freeze per account in freezeUntilBlock; the penalty persists across exit and stake deletion on the same registry (unstaking does not clear it)
  • freezeDeposit is monotonic — a shorter freeze never reduces an existing deadline
  • consolidate per-address state into an internal Account struct (Stake, freezeUntilBlock, UpdateQueue) with partial clears (_clearStake / _clearQueue) so freezeUntilBlock is never wiped when stake or queue is cleared

Bee Node Changes

  • Bee must no longer assume staking changes become effective immediately at transaction time. createDeposit, addTokens, increaseHeight, changeOverlay, withdraw, and exit enqueue updates with an effectiveFromRound returned by the call (also emitted in events). Until that round is reached, the queued change is not yet effective.
  • Bee should read the staking contract wait parameters instead of hardcoding timings:
    • WAIT_BASE for deposit, top-up, and height increase
    • WAIT_OVERLAY_CHANGE for overlay changes
    • WAIT_WITHDRAWAL for withdraw and exit
  • On deployed networks, WAIT_WITHDRAWAL is intended to represent roughly a 28-day delay in rounds. Local development configs may still use short values.
  • Effective state vs persisted storage: once currentRound() >= effectiveFromRound, the contract treats a queued update as effective for all view getters and for Redistribution commit/reveal — even if applyUpdates(owner) has not been called yet. nodeEffectiveStake(owner), overlayOfAddress(owner), heightOfAddress(owner), and stakes(owner) all simulate the ready prefix of the queue via _previewStake (same logic Redistribution uses). Bee must mirror this: switch overlay, height, and balance for commit/reveal as soon as the effective round is reached, not when applyUpdates succeeds.
  • applyUpdates is still required, but for different reasons: it writes matured updates to storage and executes BZZ transfers for withdrawals and exits. Token payout only happens inside applyUpdates; persisted account.stake may lag behind the effective preview until it runs. Bee should call it after the wait period (and retry on schedule) so on-chain storage and balances stay in sync, but commit/reveal must use the previewed effective state immediately once the round threshold is met.
  • If a queued withdrawal or exit has reached its effective round but the node is still frozen, applyUpdates(owner) reverts with FrozenWithdrawal(). Bee should catch this revert and retry after the freeze expires. The frozen update stays at the queue head and will execute on a subsequent applyUpdates call once the node is unfrozen.
  • Bee should continue to use Redistribution.isParticipatingInUpcomingRound(owner, depth) as the source of truth for eligibility. Bee should not try to reconstruct that decision from plain StakeRegistry getters alone, because redistribution combines staking state with round/anchor logic.
  • For future (not yet effective) queued items, the standard getters do not include them — use the lookahead getters or the effectiveFromRound from the enqueue event/return value to know when they will activate:
    • nodeEffectiveStakeLookahead(owner, lookahead)
    • overlayOfAddressLookahead(owner, lookahead)
    • heightOfAddressLookahead(owner, lookahead)
  • lookahead = 0 means "effective state for the current round context", and lookahead = 1 means "effective state one round ahead". This replaces the previous absolute-round preview naming.
  • Bee should treat nodeEffectiveStake(owner) as the live effective stake value. The previous committed/potential split is gone.
  • Bee may queue staking updates while frozen, but frozen nodes still cannot participate (nodeEffectiveStake returns 0), and queued withdrawals or exits will not execute until the freeze no longer blocks them.
  • Freeze is account-level, not stake-level: after exit or restake on the same registry, nodeEffectiveStake(owner) stays 0 until block.number > freezeUntilBlock(owner). Deploying a new registry starts with a clean slate; there is no on-chain freeze import between contracts.
  • After exit() is queued, the owner's staking queue is closed. Any subsequent call to createDeposit, addTokens, changeOverlay, increaseHeight, withdraw, or exit reverts with QueueClosed(). No further staking updates can be enqueued for that owner until the exit is applied and the queue is cleared. Bee should treat a queued exit as a terminal pending action for that stake position. Note: once the exit's effectiveFromRound is reached, nodeEffectiveStake becomes 0 and commit/reveal will revert with NotStaked() even before applyUpdates runs.
  • Bee can read UPDATE_QUEUE_MAX_LENGTH on-chain to check queue capacity before enqueuing. The queue reverts with UpdateQueueFull() when the limit is reached.
  • Bee operators migrating existing integration logic should update any workflow that previously relied on immediate post-transaction staking state. The safe sequence is:
    1. submit staking change (note the returned/emitted effectiveFromRound)
    2. wait until currentRound() >= effectiveFromRound
    3. use the new overlay/height/balance from the view getters for commit/reveal immediately
    4. call applyUpdates(owner) to persist state and settle token transfers (especially for withdrawals/exits)

Deployment Notes

  • The constructor now enforces WAIT_OVERLAY_CHANGE >= WAIT_BASE and WAIT_WITHDRAWAL >= WAIT_BASE. Deployment scripts that pass wait parameters violating this invariant will fail with InvalidWaitConfiguration().
  • Contract upgrade workflow: pause the old registry, nodes call migrateStake() to withdraw, deploy the successor registry, nodes restake there. Freeze penalties are not carried over automatically.

Redistribution Notes

  • claim() now keeps payout and round finalization atomic. If the postage payout fails, the whole claim reverts and can be retried later once the underlying issue is resolved.
  • This avoids a state where a round is marked as claimed but the winner was not paid.
  • Upcoming-round eligibility now uses the staking lookahead preview API rather than target-round naming, so the staking/redistribution interface more clearly expresses forward preview semantics.

Reference

Implement the SWIP-40 and SWIP-41 staking flow with delayed queue-based stake updates, withdrawals, and exits while keeping redistribution aligned with effective active stake.
Prevent queued stake withdrawals and exits from executing while a node is frozen or actively participating in the current redistribution round, and wire staking to the redistribution contract for the runtime check.
Prevent claims from finalizing when postage payout fails, and initialize staking with the expected redistribution contract so deployment catches linkage mismatches early.
Add direct effective stake coverage and make the two-reveal winner assertion resilient to deterministic state changes, while cleaning related test typing and lint issues.
@0xCardiE 0xCardiE changed the title feat: queue stake updates and harden redistribution payouts feat: queue stake updates Apr 13, 2026
@0xCardiE 0xCardiE self-assigned this Apr 13, 2026
0xCardiE added 11 commits April 14, 2026 00:56
Reconcile queued withdrawals after slashing and preview stake state at a specific round so upcoming-round eligibility uses the same round context as the anchor.
Prevent new stake updates from being enqueued after an exit is scheduled, and align withdrawal waits on real networks with the intended 28-day round window while keeping local settings fast.
Use overlay presence as the stake initialization check and remove the dead lastUpdatedBlockNumber field and related test assertions.
…king

Allow effective withdrawals and exits to execute without current-round participation blocking them, and remove the admin-controlled redistribution hook from staking and deployment wiring.
Clarify that queued stake preview getters are forward-looking rather than historical by switching the staking and redistribution APIs from target-round naming to explicit round lookahead semantics.
- Revert FrozenWithdrawal on frozen withdrawal/exit in applyUpdates
  instead of silently skipping
- Check _queueClosed before _previewStake so terminating queues
  revert QueueClosed instead of NotStaked
- Enforce WAIT_OVERLAY_CHANGE >= WAIT_BASE and
  WAIT_WITHDRAWAL >= WAIT_BASE in constructor
- Make UPDATE_QUEUE_MAX_LENGTH public
- Remove dead `Frozen` error (unused after FrozenWithdrawal was added)
- Merge identical `StakeState` into `Stake`, remove `_toStakeView`
- Add `_revertOnFrozen` param to `_applyReadyUpdates` so privileged
  callers (freezeDeposit, slashDeposit, migrateStake) break instead
  of reverting when a frozen withdrawal is encountered
- Move `_queueClosed` check from `_enqueueUpdate` to all six public
  callers including `createDeposit` which previously lacked it
- Update tests to expect FrozenWithdrawal revert on applyUpdates
  while frozen
0xCardiE added 10 commits April 18, 2026 20:32
Move FrozenWithdrawal revert into applyUpdates as a post-call
check instead of threading a bool through _applyReadyUpdates.
The internal function now always breaks on frozen entries.
Swap freeze and apply order in freezeDeposit so mature withdrawals
settle while the node is still unfrozen, then the freeze takes
effect for future rounds.
- Revert FrozenWithdrawal only when queue head is due and blocked
- Transfer min(scheduled amount, balance) on withdrawal apply
- Pause/unpause revert Unauthorized; zero deposit uses InvalidAmount
- InvalidWithdrawalAmount(WithdrawalAmountIssue) for withdraw rejects

BREAKING CHANGE: pause/unpause ABI error is Unauthorized not OnlyPauser;
InvalidWithdrawalAmount now takes uint8 reason.
- Document all custom errors with @notice
- BelowMinimumStake(have,need), InvalidWaitConfiguration(...),
  UpdateQueueFull(count,limit)

BREAKING CHANGE: StakeRegistry error signatures changed.
- Expose ROUND_LENGTH; rename NetworkId to networkId
- Internal _addressNotFrozen; lookahead delegates when lookahead is zero
- Reconcile queue via _applyPreviewUpdate plus WithdrawTokens storage cap
- Clear last-scheduled-round when queue empty; reconcile only after slash paths that keep stake
- Emit StakeMigrated before payout transfer from migrateStake

Tests derive round length from contract; trim redundant constants; add cases for invalid waits,
queue full, frozen apply with mismatched overlay delay, migrate event.

BREAKING CHANGE: networkId() replaces NetworkId(); ROUND_LENGTH on ABI.

@nugaon nugaon left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bee should continue participating under the old effective balance and metadata while updates are still waiting in the queue. A newly requested overlay or height must not be used for commit or reveal until the delay has passed and applyUpdates(owner) has succeeded.

The contract doesn't wait for applyUpdates to be called, the new overlay/height is the effective state, even if applyUpdates was never called <-> Bee node is still sampling under the old overlay (following the doc's advice). The commit would fail or produce an incorrect proof. Bee must handle the the changes in the queue the same way as the contract: activating the property from the return block number.

Slashing allows playing the redis game with dust. If it is considered as dead code -> remove it please.

Queue must applying actions without hook functions because head is advanced after updating the items (current BZZ ERC20 contract does not have hooks so no reentrance).

Comment thread src/Staking.sol Outdated
Comment thread src/Staking.sol Outdated
Comment thread src/Staking.sol Outdated
Comment thread src/Staking.sol
Comment thread src/Staking.sol
Comment thread src/Staking.sol Outdated
Comment thread src/Staking.sol Outdated
Comment thread src/Staking.sol Outdated
Comment thread src/Staking.sol Outdated
Comment thread src/Staking.sol Outdated
@0xCardiE

0xCardiE commented Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

Bee should continue participating under the old effective balance and metadata while updates are still waiting in the queue. A newly requested overlay or height must not be used for commit or reveal until the delay has passed and applyUpdates(owner) has succeeded.

The contract doesn't wait for applyUpdates to be called, the new overlay/height is the effective state, even if applyUpdates was never called <-> Bee node is still sampling under the old overlay (following the doc's advice). The commit would fail or produce an incorrect proof. Bee must handle the the changes in the queue the same way as the contract: activating the property from the return block number.

Slashing allows playing the redis game with dust. If it is considered as dead code -> remove it please.

Queue must applying actions without hook functions because head is advanced after updating the items (current BZZ ERC20 contract does not have hooks so no reentrance).

Corrected this in PR.

Slashing was respected historically where we have dead code in place for slashing but never used it. We removed slashing from code as we agreed on so there is solely focus on freezing now.

@nugaon nugaon left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

Comment thread deploy/main/010_deploy_verify.ts
@0xCardiE

0xCardiE commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Some observations, some of them might be considered features, but still worth identifying.

Slashing associated issues

Current Redistribution uses freezing only. There are some possible issues that could become problematic only if slashDeposit() would be called by redistribution.

Failure path Cheat implication Reachability

1 Queue increaseHeight(h+1) while sufficiently funded.
Then slash balance below the required stake before the update matures.
applyUpdates() applies the queued height without rechecking balance. Node gets a height it no longer paid for.
Redistribution can treat it as higher-height / less-responsible than its stake justifies. Slash-enabled only
2 High-height node has a queued top-up.
Full slash happens while queue exists.
Balance becomes 0, but old overlay/height stay.
Queued top-up later applies. Fully slashed node can reactivate at the old high height with too little stake. Slash-enabled only
3 Height-0 node is slashed from MIN_STAKE to a tiny positive balance.
Height cannot be reduced below 0.
Overlay remains. Node can remain eligible with less than the minimum stake.
Even dust can pass the stake != 0 check. Slash-enabled only

Other possible issues

Failure path Practical implication Reachability

4 Overlay/height change matures.
Owner does not call applyUpdates().
Staking getters still preview the matured queued state.
Redistribution reads the new virtual overlay/height. Changes can affect commit/reveal before being materialized. Current
5 Bee follows the PR text and keeps using the old overlay until applyUpdates() succeeds.
Contract previews the matured new overlay during commit/reveal. Bee can build commit data with the old overlay while the contract verifies with the new overlay.
Reveal can fail. Current
6 Deployment uses 5 StakeRegistry constructor args.
Verification script supplies 6 args by inserting redistribution.address. Deployment works, but contract verification fails because constructor args do not match deployed bytecode. Current
7 Admin calls changeNetworkId() after deployment.
Future overlays use the new network ID.
Existing overlays keep the old derivation. Registry can contain mixed overlay derivation domains.
Admin retains a protocol-identity mutation power. Current, admin-only
8 Account has only queued stake.
freezeDeposit() extends freezeUntilBlock.
No freeze event emits because committed stake is absent. Indexers or Bee tooling can miss a real freeze. Current
9 Account is already frozen longer.
freezeDeposit() is called with a shorter duration.
Deadline is unchanged, but StakeFrozen can still emit. Logs can suggest a freeze changed even when state did not change. Current
10 Account has balance 10.
slashDeposit(owner, 100) is called.
Only 10 can be removed.
Event emits 100. Slashing analytics/accounting can overstate the actual slashed value. Slash-enabled only
11 Withdrawal is queued.
Stake is later slashed.
Withdrawal is capped to remaining balance.
Withdrawal applies to 0, but overlay/height remain. Account can show nonzero staking metadata with zero effective stake.
Integrations may misclassify it. Slash-enabled only
12 Contract is paused.
Matured queued update exists.
Anyone calls applyUpdates(owner) because it lacks whenNotPaused. Pause does not stop queued state changes or withdrawal/exit transfers. Current

Not addressing any of the highlighted issues directly, but just to provide context I'll point out that the SWIP makes no attempt to describe how slashing would work with the update schedule because slashing is currently dead code.

All slashing parts were removed as its not used and just introduces confusion and dead code.
So until we aim to implement its best we skip the discussions about it to stay focused on current agenda.

@bcsorvasi

Copy link
Copy Markdown

@acud This has been reviewed and approved by the Research team. Could you please go through the details of this PR and assess whether it is ready for implementation?

@bcsorvasi bcsorvasi added this to the 2026 milestone Jun 18, 2026
@acud

acud commented Jun 18, 2026

Copy link
Copy Markdown

@0xCardiE i went through the PR description (that's about as far as i managed to get so far) but i must say i can't really understand the functional changes required in bee.
can you please transcode this into actionable items that are changing or added?
it would be easier to reason about this in terms of how the node should behave rather than branching out from technical terms into behavioral changes (the other way around). it would also make it easier to onboard devs to the tasks needed.

@0xCardiE

0xCardiE commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

@0xCardiE i went through the PR description (that's about as far as i managed to get so far) but i must say i can't really understand the functional changes required in bee. can you please transcode this into actionable items that are changing or added? it would be easier to reason about this in terms of how the node should behave rather than branching out from technical terms into behavioral changes (the other way around). it would also make it easier to onboard devs to the tasks needed.

Would be useful to go through referred SWIPS 40 and 41 as this is source of info on what is changing. Basicaly the whole staking logic is different. Here is overview of what is changed.


1. Staking changes are delayed, not instant

Before

After a staking transaction, Bee could treat overlay, height, and balance as updated immediately.

Now

After createDeposit, addTokens, increaseHeight, changeOverlay, withdraw, or exit, the node must treat the change as pending until a future round.

Bee should

  • Record the effectiveFromRound returned by the call (or from the event).
  • Read wait durations from the contract (WAIT_BASE, WAIT_OVERLAY_CHANGE, WAIT_WITHDRAWAL) instead of hardcoding them.
  • On mainnet/testnet, expect WAIT_WITHDRAWAL to be ~28 days in rounds; local dev may use short values.

2. Commit/reveal must use effective state, not applied state

Before

Bee may have waited for applyUpdates before switching overlay/height/stake for the redistribution game.

Now

Once currentRound() >= effectiveFromRound, the contract already treats the queued change as effective for commit/reveal — even if applyUpdates has not run.

Bee should

  • As soon as the effective round is reached, use the values from:
    • nodeEffectiveStake(owner)
    • overlayOfAddress(owner)
    • heightOfAddress(owner)
  • Do not wait for applyUpdates before participating with the new overlay/height/stake.
  • Still call applyUpdates(owner) afterward to persist on-chain state and settle token transfers.

3. applyUpdates is for settlement and sync, not for game eligibility

Bee should

  • Continue calling applyUpdates(owner) after the wait period (and on a retry schedule).
  • Expect BZZ payouts for withdrawals/exits only inside applyUpdates.
  • Accept that stored account.stake may lag behind what commit/reveal already use, until applyUpdates runs.

4. Handle frozen nodes blocking withdrawal/exit payout

When

A queued withdrawal or exit is due, but the node is still frozen.

Behavior

applyUpdates(owner) reverts with FrozenWithdrawal().

Bee should

  • Catch that revert.
  • Retry applyUpdates after the freeze expires.
  • Leave the frozen withdrawal/exit at the queue head; it will execute on the next successful applyUpdates.

5. Participation eligibility comes from Redistribution, not StakeRegistry alone

Bee should

  • Keep using Redistribution.isParticipatingInUpcomingRound(owner, depth) as the source of truth.
  • Not reconstruct eligibility from StakeRegistry getters alone, because redistribution combines staking state with round/anchor logic.

6. Effective stake is a single live value

Before

Bee may have used a committed vs potential stake split.

Now

Use nodeEffectiveStake(owner) as the only live effective stake.

Bee should

  • Remove any logic that depends on the old committed/potential model.
  • Treat effective stake as 0 while frozen, even if BZZ is still locked on-chain.

7. Frozen nodes can queue changes but cannot play

Bee should

  • Allow operators to submit staking updates while frozen.
  • Treat the node as non-participating while frozen (nodeEffectiveStake == 0).
  • Not expect due withdrawals/exits to pay out until the freeze no longer blocks them.

Important

Freeze is account-level and survives exit/restake on the same registry. After restaking on the same contract, the node stays excluded until block.number > freezeUntilBlock(owner).


8. Exit closes the staking queue

After exit() is queued

  • No further staking actions are allowed for that owner (QueueClosed()).
  • Bee should treat this as a terminal pending action for that stake position.

Once the exit effective round is reached

  • Effective stake becomes 0 immediately for commit/reveal.
  • Commit/reveal will revert with NotStaked() even before applyUpdates runs.

9. Plan for future state, not just current state

For changes not yet effective

Standard getters do not include them yet.

Bee should

  • Use effectiveFromRound from the enqueue call/event to know when a change activates.
  • For forward planning, use:
    • nodeEffectiveStakeLookahead(owner, n)
    • overlayOfAddressLookahead(owner, n)
    • heightOfAddressLookahead(owner, n)
  • Interpret n = 0 as now, n = 1 as one round ahead.

10. Respect queue capacity

Bee should

  • Read UPDATE_QUEUE_MAX_LENGTH before enqueueing multiple updates.
  • Handle UpdateQueueFull() if the queue is full.

11. Recommended operator workflow

For any staking change, Bee should follow:

  1. Submit the staking change and store effectiveFromRound.
  2. Wait until currentRound() >= effectiveFromRound.
  3. Immediately use previewed overlay/height/stake for commit/reveal.
  4. Call applyUpdates(owner) to persist state and settle token movement (especially for withdraw/exit).

12. Claim payout behavior (Redistribution)

Bee should

  • Treat claim() as all-or-nothing: if postage payout fails, the whole claim reverts.
  • Retry claim later once the underlying issue is resolved.
  • Not assume a round is fully settled until claim succeeds.

Suggested Bee implementation tasks

Task Behavior
Delayed staking state machine Track pending updates by effectiveFromRound; stop assuming post-tx immediate effect
Read wait params on-chain Replace hardcoded delays with WAIT_BASE, WAIT_OVERLAY_CHANGE, WAIT_WITHDRAWAL
Preview-based commit/reveal Switch overlay/height/stake at effective round without waiting for applyUpdates
Scheduled applyUpdates Run after wait period; retry on schedule; required for BZZ payout
Frozen withdrawal retry On FrozenWithdrawal(), retry after freeze expiry
Simplify stake model Use only nodeEffectiveStake; remove committed/potential split
Freeze-aware participation Exclude frozen nodes from game; keep freeze across exit/restake on same registry
Exit handling After queued exit, block further staking ops; expect NotStaked once effective
Lookahead planning Use lookahead getters / effectiveFromRound for future-state UI and logic
Queue limit guard Check UPDATE_QUEUE_MAX_LENGTH before enqueueing
Claim retry Retry failed claims; do not mark round complete on partial failure

@acud

acud commented Jun 18, 2026

Copy link
Copy Markdown

thanks. i'll try to look into the diff though i'm afraid it is wide enough to require a complete pass over the entire codebase to see how it all fits.

regarding Frozen withdrawal retry - i suggest not to have any automagic retry mechanism. if we can inform a user that is performing a withdrawal that there's a freeze, we can expect that they will retry manually later (we can also tell them when).

0xCardiE added 2 commits June 26, 2026 19:42
Add unit tests for AlreadyStaked, InvalidAmount, height minimum on
increaseHeight, OnlyRedistributor, and AccountFreezeExtended. Assert
StakeFrozen from Redistribution claim logs. Include PR test summary doc.
Move PR test summary into STAKING.md. Document missing errors, freeze
behavior, and Redistribution penalty integration. Remove PR_TESTS doc.
@0xCardiE

0xCardiE commented Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

Testing - new staking

Queue-based StakeRegistry: round delays, effective-stake previews, freeze-only penalties (no slash).

Hardhat

177 tests · npx hardhat compile && npm test

Unit (test/Staking.test.ts, 51 tests)

  • Deposits & queue: createDeposit, addTokens, changeOverlay, increaseHeight, withdraw, exit, applyUpdates, FIFO / mixed waits
  • Validation: min stake, AlreadyStaked, InvalidAmount, height limits, queue full / closed
  • Freeze: effective stake = 0, blocked withdrawals, survives exit & migrate, role checks
  • Pause & migrateStake
  • Lookahead views; oracle price does not affect stake

Integration (test/Redistribution.test.ts)

  • Commit/reveal with queued stake & top-ups
  • Effective stake in reveals / winner selection
  • Non-revealer frozen on claim (StakeFrozen asserted)
  • Exit + next-round eligibility

Echidna

Harness: EchidnaStakingHarness · ECHIDNA_CONTRACT=EchidnaStakingHarness yarn echidna

Fuzzes real StakeRegistry: deposit, top-up, overlay, height, withdraw, exit, freeze, pause, migrate.

Properties: no unauthorized admin/freeze; migrate only when paused; registry balance ≥ staked; frozen ⇒ zero effective stake; empty overlay ⇒ zero balance.

Redistribution Echidna harnesses use a mock stake registry - full queue semantics are in the staking harness + Hardhat.

@acud

acud commented Jul 1, 2026

Copy link
Copy Markdown

@0xCardiE, I've tried to review the branch entirely as I wasn't really familiar with the existing state of the contracts, so I reviewed essentially this branch against the last tag from July last year. This is effectively a new contract entirely with not much in common with the old one. While at least in theory I don't really have any good input about the contract code, I will pose the following questions about your requested changes from the Bee codebase:

  1. You mentioned that bee should implement scheduled operations and should follow the block height in which certain updates are to happen - I would really try to avoid having to implement a state machine that follows the blockchain (think: load balanced RPC nodes that yield different state between queries; reorgs). Is there no way to achieve the same functionality of "knowing when to do what" solely by calling the exposed contract methods?
  2. Since depositing and withdrawals are purely user initiated (right now those are exposed via the API only), I can't see why we should undertake any scheduling or retrying. The user should have all the relevant data via API calls to do any scheduled operation, be it a retry or even an applyUpdates
  3. Why again are we rolling out a contract that has a fully baked path that requires users to migrate stake? why can't we split the stake depositing into a separate contract that would allow us to overlay it with different stake management layers such as this one, or a different one the future? Honest question: is it reasonable to ask users to migrate their stakes?

@0xCardiE

0xCardiE commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

@0xCardiE, I've tried to review the branch entirely as I wasn't really familiar with the existing state of the contracts, so I reviewed essentially this branch against the last tag from July last year. This is effectively a new contract entirely with not much in common with the old one. While at least in theory I don't really have any good input about the contract code, I will pose the following questions about your requested changes from the Bee codebase:

  1. You mentioned that bee should implement scheduled operations and should follow the block height in which certain updates are to happen - I would really try to avoid having to implement a state machine that follows the blockchain (think: load balanced RPC nodes that yield different state between queries; reorgs). Is there no way to achieve the same functionality of "knowing when to do what" solely by calling the exposed contract methods?
  2. Since depositing and withdrawals are purely user initiated (right now those are exposed via the API only), I can't see why we should undertake any scheduling or retrying. The user should have all the relevant data via API calls to do any scheduled operation, be it a retry or even an applyUpdates
  3. Why again are we rolling out a contract that has a fully baked path that requires users to migrate stake? why can't we split the stake depositing into a separate contract that would allow us to overlay it with different stake management layers such as this one, or a different one the future? Honest question: is it reasonable to ask users to migrate their stakes?

Great if you can do some Code Review on contract but wasn't expecting that from bee team, so no worries about that part. We had some reviews from Research already. Also its true its completely new contract as most of the logic is changed.

So lets go to the points.

  1. Agree Bee should not implement a local staking state machine. Bee only needs to call contract views at transaction time (nodeEffectiveStake, overlayOfAddress, currentRound, phase helpers). applyUpdates is for storage materialization and withdrawal payouts, not for game eligibility. No local round tracking required beyond what we already do for commit/reveal phases. Will update that in PR.

  2. Generally agree for user-initiated flows. The user decides when to call applyUpdates to collect withdrawn tokens. This is more of maybe good to have for better UX so they can schedule withdrawal over bee, so they dont need to make custom solutions for it. Or that we at lease provide some script they can easily install to solve this. Just my recommendation, its bee team decision in the end.

  3. I hear you :) this has been debated for few years now. Higher ups wanted that we have this flow for now. Which means new contracts and new states for each new contract. This is mostly chosen from security concerns and there is good points for that. We have a possible PR that could change that I made and we will see if we go that path, but probably not before this PR is merged feat: Versioned Registry Router + Upgradeable Proxies for All Core Contracts #310

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants