Skip to content

feat(stake): two-phase timelock on cooldown_slots increases (#242) - #246

Merged
dcccrypto merged 1 commit into
mainfrom
fix/242-cooldown-timelock
Jun 23, 2026
Merged

feat(stake): two-phase timelock on cooldown_slots increases (#242)#246
dcccrypto merged 1 commit into
mainfrom
fix/242-cooldown-timelock

Conversation

@dcccrypto

@dcccrypto dcccrypto commented Jun 23, 2026

Copy link
Copy Markdown
Owner

Implements the N-2 timelock #243 prepared. A compromised admin could set cooldown_slots = MAX (~1yr) in one UpdateConfig tx and instantly lock LP withdrawals. Now a cooldown increase requires a two-phase, ~48h-delayed commit, giving LPs a guaranteed exit window.

Verification: timelock_window_elapsed pure helper + Kani proof (293 properties, 2/2 covers satisfied — non-vacuous); unit tests for helper/accessors/neighbor-collision; instruction round-trip + tombstone tests updated; 138 lib + full integration suite (incl. LiteSVM e2e) pass, 0 failures; build-sbf clean. NOT deployed (cutover-gated).

Closes #242.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a two-phase timelock mechanism for cooldown increases. Administrators can now propose a cooldown increase, wait for the timelock window to elapse, then commit or cancel the change.

Implements the N-2 timelock that #243 prepared (TIMELOCK_SLOTS). The capital-LOCK
vector in #242 is a cooldown_slots INCREASE: a compromised admin could set
cooldown = MAX_COOLDOWN_SLOTS (~1 year) in a single UpdateConfig tx and lock LP
withdrawals instantly. That increase now requires a two-phase, time-delayed commit.

- ProposeCooldownIncrease (tag 7, reclaimed) — admin records the new (larger) value +
  proposal slot. Rejects non-increases (those use UpdateConfig).
- CommitCooldownIncrease (tag 8) — applies the pending value ONLY after TIMELOCK_SLOTS
  (~48h) have elapsed, giving LP holders a guaranteed exit window.
- CancelCooldownIncrease (tag 9) — admin withdraws a pending proposal.
- UpdateConfig now REJECTS a cooldown INCREASE (CooldownIncreaseRequiresTimelock);
  decreases (LP-friendly) and deposit_cap stay immediate.

State: pending_cooldown_slots + cooldown_proposed_at_slot stored in the previously-free
_reserved[10..26] — no struct-size change, no version bump (fresh-start cutover).
3 new errors (25/26/27). FlushToInsurance / SetMarketResolved intentionally stay
immediate (PDA-custody + #227/#229 guards / one-way operational — see N-2 comment).

Verification:
- timelock_window_elapsed extracted as a pure helper; 3 unit tests + 2 accessor tests +
  collision-with-neighbors test; instruction round-trip tests; tombstone tests updated.
- Kani proof kani_timelock_window_elapsed_matches_spec: VERIFIED (293 properties, 2/2
  cover properties satisfied — non-vacuous, both elapsed/not-elapsed reachable, overflow
  is Err not panic).
- 138 lib tests + full integration suite (incl. LiteSVM e2e) pass, 0 failures;
  cargo build-sbf clean. NOT deployed (cutover-gated).

Closes #242.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dcccrypto
dcccrypto merged commit b68bfb0 into main Jun 23, 2026
@dcccrypto
dcccrypto deleted the fix/242-cooldown-timelock branch June 23, 2026 18:34
@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d27721c0-ef89-46ba-9928-6228d802e67a

📥 Commits

Reviewing files that changed from the base of the PR and between 924874f and e82d6a8.

📒 Files selected for processing (7)
  • src/error.rs
  • src/instruction.rs
  • src/processor.rs
  • src/state.rs
  • tests/integration.rs
  • tests/kani.rs
  • tests/unit.rs

📝 Walkthrough

Walkthrough

Implements a two-phase timelock for admin cooldown increases. Three new StakeError variants are added. StakePool gains four accessors for pending cooldown and proposal slot stored in reserved bytes. Three new instructions (ProposeCooldownIncrease, CommitCooldownIncrease, CancelCooldownIncrease) are defined and dispatched. process_update_config now rejects direct cooldown increases. A timelock_window_elapsed helper enforces the slot-window delay. A Kani proof and expanded unit/integration tests validate the implementation.

Changes

Cooldown Increase Timelock

Layer / File(s) Summary
Error variants and StakePool timelock accessors
src/error.rs, src/state.rs
Three StakeError variants (discriminants 25–27) cover timelock enforcement, commit-window expiration, and missing active proposal. Four StakePool public methods read and write pending cooldown and proposal slot from _reserved[10..26] as LE u64 values.
Instruction variants and unpack decoding
src/instruction.rs
ProposeCooldownIncrease (tag 7, carries new_cooldown_slots: u64), CommitCooldownIncrease (tag 8), and CancelCooldownIncrease (tag 9) are added to StakeInstruction. unpack gains length-checked arms for all three and rejects wrong-length or trailing-byte inputs.
Timelock helper, update_config guard, and three new handlers
src/processor.rs
Public timelock_window_elapsed uses checked_add to return Err on overflow. process_update_config now rejects cooldown increases with CooldownIncreaseRequiresTimelock. Three new processor functions implement the propose/commit/cancel workflow using the new state accessors and error variants, with admin-only checks on each path.
Unit tests, integration tests, and Kani proof
src/instruction.rs, src/processor.rs, tests/integration.rs, tests/kani.rs, tests/unit.rs
Unit tests cover timelock_window_elapsed boundary and overflow cases, accessor round-trips, and reserved-byte non-collision. Instruction tests verify correct decoding and payload rejection for the three new variants. Integration tests update tag round-trip assertions for tags 7–9. A Kani harness proves timelock_window_elapsed matches its overflow-and-elapsed spec for all symbolic u64 inputs.

Sequence Diagram(s)

sequenceDiagram
  participant Admin
  participant Processor
  participant StakePool
  participant Clock

  rect rgba(255, 165, 0, 0.5)
    note over Admin,Clock: Phase 1 — Propose
    Admin->>Processor: ProposeCooldownIncrease(new_cooldown_slots)
    Processor->>StakePool: set_pending_cooldown_slots(new_value)
    Processor->>Clock: read slot
    Processor->>StakePool: set_cooldown_proposed_at_slot(slot)
  end

  rect rgba(255, 0, 0, 0.5)
    note over Admin,Processor: Blocked direct path
    Admin->>Processor: UpdateConfig(cooldown_increase)
    Processor-->>Admin: Err(CooldownIncreaseRequiresTimelock)
  end

  rect rgba(0, 128, 0, 0.5)
    note over Admin,Clock: Phase 2 — Commit (after timelock)
    Admin->>Processor: CommitCooldownIncrease
    Processor->>StakePool: cooldown_proposed_at_slot()
    Processor->>Clock: read slot
    Processor->>Processor: timelock_window_elapsed(proposed_at, TIMELOCK_SLOTS, now)
    Processor->>StakePool: pool.cooldown_slots = pending_cooldown_slots
    Processor->>StakePool: set_cooldown_proposed_at_slot(0)
  end

  rect rgba(100, 100, 200, 0.5)
    note over Admin,StakePool: Cancel path
    Admin->>Processor: CancelCooldownIncrease
    Processor->>StakePool: set_cooldown_proposed_at_slot(0)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 Hop, hop — no rushing the lock!
The cooldown must wait on the clock.
Propose sets the stage,
Commit turns the page,
And Cancel clears state 'round the block. ✨

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/242-cooldown-timelock

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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.

[H] No timelock on admin operations — compromised key can lock 100% of TVL for ~1 year in one transaction

1 participant