fix(contract): replace single-step upgrade with two-step timelock (is… - #1180
Merged
Ejirowebfi merged 1 commit intoAug 28, 2026
Merged
Conversation
…sue Favourorg#6) The previous upgrade() entrypoint performed an immediate WASM swap on a single admin signature with no on-chain event, giving monitoring systems, multisig quorums, and the community zero warning before attacker-controlled code could go live. SECURITY.md classifies a malicious WASM swap as the single Critical-severity threat scenario. This commit resolves the issue with a mandatory two-step upgrade flow and a ~28.8-hour timelock (UPGRADE_TIMELOCK_LEDGERS = 17,280 ledgers): Contract changes (contracts/token-factory/src/lib.rs): - Add UPGRADE_TIMELOCK_LEDGERS constant (17,280 ledgers ≈ 28.8 hours). - Add pending_upgrade_hash: Option<BytesN<32>> and pending_upgrade_ready_at: Option<u64> to FactoryState. - Remove single-step upgrade(). Replace with: propose_upgrade(admin, new_wasm_hash) — records the hash and a ready_at ledger; emits upg_prop event immediately. execute_upgrade(admin, new_wasm_hash) — performs the WASM swap only after the timelock has elapsed and the hash matches exactly; emits upg_exec. Fails with UpgradeNotReady (28), NoUpgradePending (29), or UpgradeHashMismatch (30). cancel_upgrade(admin) — aborts a pending proposal at any time; emits upg_can. Idempotent. - Add Error variants: UpgradeNotReady=28, NoUpgradePending=29, UpgradeHashMismatch=30. - Bump CURRENT_SCHEMA_VERSION to 5. - Add v5 migration step: seeds pending_upgrade_hash/ready_at to None on existing deployments; idempotent. Tests (contracts/token-factory/src/test.rs): - Replace test_upgrade_unauthorized with comprehensive timelock suite: - execute_upgrade fails before timelock elapses (UpgradeNotReady). - execute_upgrade fails with wrong hash (UpgradeHashMismatch). - execute_upgrade fails with no proposal (NoUpgradePending). - cancel_upgrade clears pending proposal; subsequent execute fails. - cancel_upgrade is idempotent when nothing pending. - propose_upgrade emits upg_prop event. - cancel_upgrade emits upg_can event. - propose_upgrade stores correct hash and ready_at in state. - Second proposal overwrites first and resets timelock. - Unauthorized callers rejected on all three new entrypoints. - Add schema v5 migration tests (v4→v5, idempotent at v5, full v0→v5 walk in one call). Docs updated: - docs/incident-response.md: describe the timelock as an active defense; replace polling-only detection with event-based monitoring for upg_prop/ upg_exec/upg_can; update threat model table; split detection section into primary (events) and defense-in-depth (hash polling). - docs/contract-abi.md: document propose_upgrade/execute_upgrade/ cancel_upgrade; add error codes 28–30; add upg_prop/upg_exec/upg_can to events table; add schema v5 to migrate() description. - docs/mainnet-deployment-checklist.md: add step-by-step two-step upgrade procedure and upgrade event monitoring to incident readiness. - README.md: update schema version table with v5; replace upgrade procedure with two-step propose/wait/execute flow including cancel. Closes issue Favourorg#6 from the 2026-08 codebase audit (ISSUES.md).
10 tasks
github-actions Bot
pushed a commit
that referenced
this pull request
Aug 28, 2026
## [1.8.7](v1.8.6...v1.8.7) (2026-08-28) ### Bug Fixes * **auth:** replace timing-unsafe JWT signature comparison with timingSafeEqual ([#1179](#1179)) ([8925440](8925440)), closes [#4](#4) * **contract:** replace single-step upgrade with two-step timelock (issue [#6](#6)) ([#1180](#1180)) ([cb9f98a](cb9f98a)) * split rate-limit buckets, guard cross-tab network change, enforce error discriminant uniqueness ([#1178](#1178)) ([7ddf2c4](7ddf2c4)), closes [#1162](#1162) [#1163](#1163) [#1164](#1164) [#1162](#1162) [#1163](#1163) [#1164](#1164)
|
🎉 This PR is included in version 1.8.7 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The previous upgrade() entrypoint performed an immediate WASM swap on a single admin signature with no on-chain event, giving monitoring systems, multisig quorums, and the community zero warning before attacker-controlled code could go live. SECURITY.md classifies a malicious WASM swap as the single Critical-severity threat scenario.
This commit resolves the issue with a mandatory two-step upgrade flow and a ~28.8-hour timelock (UPGRADE_TIMELOCK_LEDGERS = 17,280 ledgers):
Contract changes (contracts/token-factory/src/lib.rs):
Tests (contracts/token-factory/src/test.rs):
Docs updated:
Closes #1094