Skip to content

feat: add upgrade timelock, guardian veto, and rollback history - #64

Merged
bakarezainab merged 1 commit into
LatterFixxx:mainfrom
davidmaronio:feat/issue-37-upgrade-timelock
Jul 29, 2026
Merged

feat: add upgrade timelock, guardian veto, and rollback history#64
bakarezainab merged 1 commit into
LatterFixxx:mainfrom
davidmaronio:feat/issue-37-upgrade-timelock

Conversation

@davidmaronio

Copy link
Copy Markdown
Contributor

Summary

Implements a mandatory timelock state machine for contract WASM upgrades, per the acceptance criteria in #37.

  • Upgrade proposal state machine: propose_upgrade(new_wasm_hash) (admin-only) stores a single pending proposal with ready_at = now + timelock. execute_upgrade() reverts if called before ready_at, or if the proposal was already executed or vetoed. Only one proposal can be in flight at a time.
  • Timelock delay: configurable via set_upgrade_timelock (admin-only), defaulting to 48 hours, with a 1-hour safety floor so it can't be configured down to (near) zero.
  • veto_upgrade: cancels the pending proposal during its timelock window. Callable by the contract admin (self-correction of a mistaken proposal) or any address holding the new Role::Guardian (the actual emergency-stop path against a malicious/compromised admin-issued proposal).
  • Guardian role: rather than a parallel role system, this reuses the contract's existing access_control role registry — Role::Guardian is a new variant, installed the normal way via grant_role(admin, guardian_address, Role::Guardian).
  • Rollback via history log: every hash the contract is actually upgraded to is appended to an on-chain, append-only history (get_upgrade_history), capped at the most recent 50 entries. There's no separate "rollback" bypass — rolling back means calling propose_upgrade again with a previously recorded hash, going through the exact same timelock + veto flow as any forward upgrade, since a rollback deserves no less scrutiny than a forward upgrade. Design rationale is documented in the upgrade.rs module doc comment.

New module: src/upgrade.rs, wired into TaskManagerContract in src/lib.rs as propose_upgrade, execute_upgrade, veto_upgrade, get_upgrade_timelock, set_upgrade_timelock, get_pending_upgrade, get_upgrade_history. New events in src/events.rs (upg_prop, upg_exec, upg_veto, upg_tl).

Test plan

New src/upgrade_test.rs (15 tests, all passing):

  • propose_upgrade sets Pending status with the correct ready_at
  • propose_upgrade rejects non-admin callers
  • propose_upgrade rejects a second proposal while one is still pending
  • execute_upgrade rejects premature execution (immediately after proposing, and one second before ready_at)
  • execute_upgrade succeeds once the timelock has elapsed, applies the WASM swap, and records a history entry
  • execute_upgrade rejects non-admin callers
  • execute_upgrade rejects a proposal that's already executed
  • veto_upgrade by a guardian blocks execution even after the timelock would have elapsed
  • veto_upgrade also works for admin self-correction
  • veto_upgrade rejects unauthorized (non-admin, non-guardian) callers
  • veto_upgrade rejects when there's no pending proposal
  • set_upgrade_timelock changes the delay applied to future proposals
  • set_upgrade_timelock rejects values below the 1-hour safety floor
  • set_upgrade_timelock rejects non-admin callers

Verified from the contract crate dir:

  • cargo test — 78 passed (63 pre-existing + 15 new), 0 failed
  • cargo clippy --all-targets -- -D warnings — clean
  • cargo fmt --check on the touched files (upgrade.rs, upgrade_test.rs, access_control.rs, events.rs, lib.rs) — clean

Note: cargo fmt --check on the full crate surfaces pre-existing formatting drift in multisig.rs, multisig_test.rs, test.rs, and twap_oracle.rs unrelated to this change — left untouched as out of scope.

Closes #37

Add a mandatory timelock state machine gating contract WASM upgrades:
propose_upgrade stores a pending proposal with a configurable delay
(default 48h, 1h safety floor), execute_upgrade rejects early calls and
only swaps the WASM once the timelock has elapsed, and veto_upgrade lets
the admin or an emergency guardian cancel a pending proposal before it
can execute.

The guardian is modeled as a new access_control::Role::Guardian on the
existing role registry rather than a parallel role system. Every applied
upgrade is appended to an on-chain history log so a rollback is just a
propose_upgrade call with a previously recorded hash, going through the
same timelock and veto protections as any other upgrade.

Closes LatterFixxx#37
@bakarezainab
bakarezainab merged commit 6344786 into LatterFixxx:main Jul 29, 2026
1 check passed
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.

#037: Smart Contract Upgrade Rollback Guard & Timelock Enforcement

2 participants