Upgrade Mechanism

TimelockController-gated UUPS proxy upgrade flow with Safe multisig governance

DEPLOYER Dev / CI SAFE MULTISIG TIMELOCK_TIMELOCK_PROPOSER_ROLE NEW IMPLEMENTATION ContractV2.sol bare logic, no proxy TIMELOCK CONTROLLER TimelockController MIN_DELAY enforced before execution UPGRADE_UPGRADE_EXECUTOR_ROLE = address(0) → anyone ACCESS MANAGER hasRole(UPGRADER_ROLE, timelock) ✓ central role registry ── UUPS proxy contracts ── ERC1967 PROXY Old Implementation V1 delegatecall target (before) New Implementation V2 delegatecall target (after) ① deploy implementation ② schedule(upgrade implementation) ③ execute() → role ✓ ④ upgradeToAndCall(v2) impl address TIMELOCK_TIMELOCK_PROPOSER_ROLE Safe Multisig only UPGRADE_EXECUTOR_ROLE address(0) → anyone UPGRADER_ROLE TimelockController SENTINEL_ROLE EOA for instant cancel
Deploy implementation
Schedule (multisig proposal)
Execute after delay
Proxy upgrade call
Step Who Action Effect
Deployer Run deployImplementation.ts New logic contract deployed (bare, no proxy)
Safe Multisig Run scheduleUpgrade.tsTimelockController.schedule() Upgrade queued, MIN_DELAY countdown begins
Anyone Run executeUpgrade.tsTimelockController.execute() AccessManager verifies UPGRADER_ROLE
TimelockController proxy.upgradeToAndCall(newImpl) Proxy now delegates to V2 implementation

Security Properties

Time delay
All upgrades must wait MIN_DELAY seconds after scheduling. Community and auditors can inspect the queued implementation before execution.
No admin backdoor
TimelockController's admin = address(0). Role changes to the timelock must go through the timelock itself — fully self-sovereign.
Multisig governance
Only the Safe multisig can propose upgrade implementations (TIMELOCK_PROPOSER_ROLE) by scheduling the upgrade payload. Requires M-of-N signers to reach consensus before scheduling.
Open execution
UPGRADE_EXECUTOR_ROLE = address(0) — anyone can trigger execution after the delay. No single point of failure at execution time.