Context
docs/GOVERNANCE.md § "Contract upgrade governance" states, verbatim, that "the upgrade.rs module does not yet implement propose/accept — that is a follow-up tracked in the issue backlog." It was not actually tracked. This is that issue.
contracts/reputation/src/upgrade.rs has exactly two operations: init (one-shot, panics on re-init) and apply. There is no rotation path at all — once the upgrade admin is bound it can never be changed. If that key is lost, the contract can never be upgraded again; if it is compromised, it cannot be revoked.
This is a sharper problem than the main admin key, because upgrade(new_wasm_hash) replaces the contract's entire code.
Requirements
Mirror contracts/reputation/src/admin.rs, which already solves this correctly for the main admin:
propose_upgrade_admin(new_admin) — callable only by the current upgrade admin
accept_upgrade_admin() — callable only by the proposed address, so a typo cannot brick the role
cancel_upgrade_proposal()
pending_upgrade_admin() -> Option<Address>
- A
DataKey variant for the pending value
Tests
Extend contracts/reputation/tests/upgrade.rs following the shape of contracts/reputation/tests/multisig.rs: propose/accept happy path; a non-admin cannot propose; a non-proposed address cannot accept; cancel clears the pending value; the old upgrade admin can no longer call upgrade after handover.
Notes
~60 lines plus tests, no dependency on anything else. Split out of #875, whose multisig bullet is an operational action rather than engineering — this is the one real code gap in that issue.
Related risk worth checking while in here: upgrade::apply panics with "upgrade admin not initialized" unless init_upgrade was actually called on the deployed testnet contract, and contract_version() returns 0 in that case. Confirming which is true on CCZ54NTE… determines whether #785 can ship as an in-place WASM upgrade or needs a fresh deploy.
Context
docs/GOVERNANCE.md§ "Contract upgrade governance" states, verbatim, that "theupgrade.rsmodule does not yet implement propose/accept — that is a follow-up tracked in the issue backlog." It was not actually tracked. This is that issue.contracts/reputation/src/upgrade.rshas exactly two operations:init(one-shot, panics on re-init) andapply. There is no rotation path at all — once the upgrade admin is bound it can never be changed. If that key is lost, the contract can never be upgraded again; if it is compromised, it cannot be revoked.This is a sharper problem than the main admin key, because
upgrade(new_wasm_hash)replaces the contract's entire code.Requirements
Mirror
contracts/reputation/src/admin.rs, which already solves this correctly for the main admin:propose_upgrade_admin(new_admin)— callable only by the current upgrade adminaccept_upgrade_admin()— callable only by the proposed address, so a typo cannot brick the rolecancel_upgrade_proposal()pending_upgrade_admin() -> Option<Address>DataKeyvariant for the pending valueTests
Extend
contracts/reputation/tests/upgrade.rsfollowing the shape ofcontracts/reputation/tests/multisig.rs: propose/accept happy path; a non-admin cannot propose; a non-proposed address cannot accept; cancel clears the pending value; the old upgrade admin can no longer callupgradeafter handover.Notes
~60 lines plus tests, no dependency on anything else. Split out of #875, whose multisig bullet is an operational action rather than engineering — this is the one real code gap in that issue.
Related risk worth checking while in here:
upgrade::applypanics with"upgrade admin not initialized"unlessinit_upgradewas actually called on the deployed testnet contract, andcontract_version()returns0in that case. Confirming which is true onCCZ54NTE…determines whether #785 can ship as an in-place WASM upgrade or needs a fresh deploy.