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.ts → TimelockController.schedule() Upgrade queued, MIN_DELAY countdown begins
③ Anyone Run executeUpgrade.ts → TimelockController.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.