Skip to content

Governance and Upgrades

RunTimeAdmin edited this page Oct 30, 2026 · 22 revisions

Governance & Upgrades

**Referenced Files in This Document** - [SplitAuthority.s.sol](https://github.com/RunTimeAdmin/Sigvara/blob/main/script/SplitAuthority.s.sol) - [Upgrade.s.sol](https://github.com/RunTimeAdmin/Sigvara/blob/main/script/Upgrade.s.sol) - [SigvaraIdentity.sol](https://github.com/RunTimeAdmin/Sigvara/blob/main/src/SigvaraIdentity.sol) - [SigvaraStaking.sol](https://github.com/RunTimeAdmin/Sigvara/blob/main/src/SigvaraStaking.sol) - [SplitAuthorityFork.t.sol](https://github.com/RunTimeAdmin/Sigvara/blob/main/test/SplitAuthorityFork.t.sol) - [Upgrade.t.sol](https://github.com/RunTimeAdmin/Sigvara/blob/main/test/Upgrade.t.sol) - [5042002.json](https://github.com/RunTimeAdmin/Sigvara/blob/main/deployments/5042002.json) - [TimelockedGrants.sol](https://github.com/RunTimeAdmin/Sigvara/blob/main/src/TimelockedGrants.sol) - [InflowGuard.sol](https://github.com/RunTimeAdmin/Sigvara/blob/main/src/InflowGuard.sol) - [0005-governance-and-emergency-powers.md](https://github.com/RunTimeAdmin/Sigvara/blob/main/docs/adr/0005-governance-and-emergency-powers.md)

Table of Contents

  1. Introduction
  2. Project Structure
  3. Core Components
  4. Architecture Overview
  5. Detailed Component Analysis
  6. Dependency Analysis
  7. Performance Considerations
  8. Troubleshooting Guide
  9. Conclusion
  10. Appendices

Introduction

This document explains Sigvara’s governance and upgrade model. The system starts with an EOA deployer holding administrative control, then migrates to a Safe multisig for day-to-day administration and an OpenZeppelin TimelockController for upgrades. Contracts use UUPS proxies so implementations can be upgraded without changing proxy addresses. The timelock enforces a public notice period before any code change executes. The roles that move funds or change an agent's standing, and the calls that decide who counts as bonded and where slashed funds go, wait for it too. The Safe controls the oracle and guardian grants, revocations, parameter changes within fixed bounds, and the ruling on and veto of slashes.

The migration is phased and defensive: the deployer never renounces authority until the Safe and timelock are proven to work on-chain. After migration, the deployer cannot upgrade or grant admin roles; only the Safe can propose through the timelock, and anyone can execute after the delay has elapsed.

This page describes the contracts and scripts on main. The handover is rehearsed on a fork of the Arc testnet and has not been run on a live deployment. The testnet proxies have run this code since 26 October 2026 (apart from the bound on upholding a dispute, which they take at their next upgrade), but they were initialised by the September code, so there the admin also grants the slasher, committee and staking-core roles and can grant itself UPGRADER_ROLE.

Project Structure

The governance and upgrade logic lives primarily in two Forge scripts and four core contracts:

  • script/SplitAuthority.s.sol: Phased migration from deployer EOA to Safe + TimelockController.
  • script/Upgrade.s.sol: Safe, auditable UUPS upgrade script that deploys new implementations and verifies slot movement.
  • src/SigvaraIdentity.sol, src/SigvaraStaking.sol, plus related contracts: UUPS-upgradeable contracts gated by UPGRADER_ROLE.
  • src/TimelockedGrants.sol and src/InflowGuard.sol: shared bases that make grants of the fund-moving roles wait for the timelock, while the admin can still revoke at once, and that give a guardian a bounded pause on new inflows.
  • test/SplitAuthorityFork.t.sol and test/Upgrade.t.sol: Fork and unit tests validating migration and upgrade behavior. test/SplitAuthority.t.sol runs every phase offline against fresh registries and a stand-in Safe.
  • deployments/5042002.json: Live deployment artifact recording proxy addresses and implementation history.
graph TB
subgraph "Governance"
SAFE["Safe Multisig"]
TL["TimelockController"]
end
subgraph "Proxies"
ID_PROXY["SigvaraIdentity Proxy"]
REP_PROXY["SigvaraReputation Proxy"]
STK_PROXY["SigvaraStaking Proxy"]
ORB_PROXY["SigvaraOracleBond Proxy"]
end
subgraph "Implementations"
ID_IMPL["SigvaraIdentity Implementation"]
REP_IMPL["SigvaraReputation Implementation"]
STK_IMPL["SigvaraStaking Implementation"]
ORB_IMPL["SigvaraOracleBond Implementation"]
end
SAFE --> |"Proposes via Timelock"| TL
TL --> |"Executes after delay"| ID_PROXY
TL --> |"Executes after delay"| REP_PROXY
TL --> |"Executes after delay"| STK_PROXY
TL --> |"Executes after delay"| ORB_PROXY
ID_PROXY --> ID_IMPL
REP_PROXY --> REP_IMPL
STK_PROXY --> STK_IMPL
ORB_PROXY --> ORB_IMPL
Loading

Diagram sources

Section sources

Core Components

  • SplitAuthority: A five-phase Forge script that moves DEFAULT_ADMIN_ROLE to a Safe and UPGRADER_ROLE to a TimelockController across all target contracts (identity, reputation, staking and oracleBond, which are required, and epochFees, which is optional and skipped when it is absent from the artifact). It reads deployment artifacts, validates Safe configuration, generates Safe calldata, and performs irreversible renunciation only when checks pass.
  • Upgrade: A Forge script that deploys a fresh implementation for a given target, calls upgradeToAndCall on the UUPS proxy, and verifies that the ERC-1967 implementation slot changed. It also checks dependency ordering between contracts, and refuses a proxy that does not answer a getter only the target has (ProxyIsNotTheTarget).
  • UUPS Proxies: All registry contracts inherit UUPS upgradeability and gate _authorizeUpgrade on UPGRADER_ROLE.
  • TimelockController: Deployed with the Safe as proposer and open executors; it enforces a minimum delay (default 48 hours). Phases 3 and 5 refuse an address whose code is not this contract (the metadata solc appends is ignored) or that the deployer did not create, because a constructor can set state that the code does not show. PHASE=renounce then reads the timelock it is given and refuses a delay under 48 hours, a Safe that cannot propose or cancel, execution that is not open to everyone, and an admin role held by the Safe or the deployer.
  • Safe Multisig: Holds DEFAULT_ADMIN_ROLE and proposes upgrades through the timelock. PHASE=renounce refuses a Safe with a threshold under 2, and one that cannot be asked for its threshold unless CONFIRM_UNINSPECTABLE_SAFE=yes-i-checked-the-threshold says the threshold and the owners were read some other way. Phases 2 to 5 refuse a Safe that is an account which has delegated to a contract (EIP-7702), because its own key can still act alone.

Key responsibilities:

  • Role management: Admin vs upgrader separation. UPGRADER_ROLE administers itself, and the roles that move funds or change an agent's standing are administered by it, so the admin cannot grant them.
  • Proposal workflow: Safe proposes, timelock delays, anyone executes.
  • Upgrade safety: UUPS, role gating, dependency checks, storage layout discipline.
  • Backward compatibility: Appended storage fields and slot-pinning tests.

Section sources

Architecture Overview

The governance architecture separates immediate administration from code changes:

  • Admin path: The Safe holds DEFAULT_ADMIN_ROLE. It can grant ORACLE_ROLE and GUARDIAN_ROLE, revoke any role except UPGRADER_ROLE, set parameters within their floors and ceilings, rule on a disputed slash and veto a pending or disputed one. These actions do not require a timelock. It cannot grant UPGRADER_ROLE or the slasher, slashing committee and staking-core roles, and it cannot call setStakeView on identity, setOperatorBond on reputation or setSlashBeneficiary on the bond registry: those need UPGRADER_ROLE.
  • Upgrade path: Only holders of UPGRADER_ROLE can call upgradeToAndCall. After migration, this role belongs to the TimelockController. The Safe schedules an upgrade, waits for the delay, and anyone can execute it. The grants and wiring calls listed above take the same path.
sequenceDiagram
participant Dev as "Developer / Operator"
participant Safe as "Safe Multisig"
participant TL as "TimelockController"
participant Proxy as "UUPS Proxy"
participant Impl as "New Implementation"
Dev->>Safe : Build upgrade proposal
Safe->>TL : schedule(upgradeToAndCall(newImpl), delay)
Note over TL : Public notice period (e.g., 48h)
Dev->>TL : execute(upgradeToAndCall(newImpl))
TL->>Proxy : upgradeToAndCall(newImpl)
Proxy->>Impl : Point at the new implementation
Proxy-->>Dev : Upgrade complete
Loading

Diagram sources

A reinitializer cannot ride along with a timelocked upgrade. Reinitializers are gated on DEFAULT_ADMIN_ROLE, msg.sender inside upgradeToAndCall is still the timelock, and the timelock is not the admin. The upgrade lands with empty data and the Safe calls the reinitializer in a second transaction, so a new reinitializer needs an unset state that fails closed.

Detailed Component Analysis

SplitAuthority Migration Script

The migration is intentionally phased to avoid bricking proxies:

  1. Plan: Read-only report of current role holders, environment validation, and whether each contract's UPGRADER_ROLE is self-administered and its fund-moving roles are timelocked. The four launch registries (identity, reputation, staking, oracleBond) must be in the artifact in every phase that reads them (plan, grant, safe-calldata and renounce; deploy-timelock only creates the timelock): the script stops with MissingTarget at one that is missing, zero or without code, because a skipped proxy would keep the deployer's rights while the last phase still printed "done". epochFees is optional.
  2. Deploy Timelock: Create a TimelockController with Safe as proposer and open executors.
  3. Grant Admin: Additive step in which the deployer grants DEFAULT_ADMIN_ROLE to the Safe and UPGRADER_ROLE to the timelock on each target contract (PHASE=grant; grant-admin is the old name).
  4. Safe Calldata: Generate the exact call the Safe must make on each target: revoke the deployer's DEFAULT_ADMIN_ROLE. It can only land if the Safe holds admin and can really execute on this chain, which is what proves the Safe works.
  5. Renounce: Irreversible phase in which the deployer drops UPGRADER_ROLE, the one role it still holds, only if all checks pass.

Between phases 4 and 5, check the grant history with BEFORE_RENOUNCE=1 SAFE=.. TIMELOCK=.. COMMITTEE=.. ORACLE=.. GUARDIAN=.. node scripts/check-role-holders.mjs, and go on only if it passes. The script's own checks read the contracts it is shown, and AccessControl cannot list holders, so only the history shows a holder nobody expected. Once the deployer is gone the upgrader role answers only to the timelock, so an extra holder is cheap to remove before phase 5 and slow to remove after it. In this mode the deployer is expected to hold UPGRADER_ROLE beside the timelock. Run the same command without BEFORE_RENOUNCE after phase 5, when the timelock alone should hold it.

flowchart TD
Start(["Start Migration"]) --> Plan["Phase 1: plan<br/>Read-only report"]
Plan --> DeployTL["Phase 2: deploy-timelock<br/>Create TimelockController"]
DeployTL --> GrantAdmin["Phase 3: grant<br/>Admin to Safe, upgrader to timelock"]
GrantAdmin --> SafeCalldata["Phase 4: safe-calldata<br/>Safe revokes the deployer's admin"]
SafeCalldata --> RenounceCheck{"All checks pass, including check-role-holders with BEFORE_RENOUNCE=1?"}
RenounceCheck --> |No| Stop["Stop. Do not renounce."]
RenounceCheck --> |Yes| Renounce["Phase 5: renounce<br/>Deployer drops UPGRADER_ROLE"]
Renounce --> End(["Migration Complete"])
Loading

Diagram sources

Key safeguards:

  • Phase 3 is additive; the deployer still holds roles if something fails.
  • Phase 4 requires execution from the Safe to prove it works on-chain.
  • Phase 5 refuses unless:
    • CONFIRM=yes-drop-the-deployer is set.
    • Safe is not the deployer, cannot act alone, has a threshold of 2 or more (or one vouched for with CONFIRM_UNINSPECTABLE_SAFE), and is not an EIP-7702 delegated account.
    • Timelock is the TimelockController that phase 2 creates from the deployer (also checked in phase 3), with a delay of at least 48 hours, the Safe as proposer and canceller, open execution and no admin but itself.
    • Every target has granted admin to Safe and upgrader to timelock.
    • The Safe has already removed the deployer's admin on every target, which is the evidence that it can execute there.
    • On every target UPGRADER_ROLE administers itself, and the admin cannot grant the slasher, slashing committee or staking-core role. A registry initialized by older code fails these two checks.

Section sources

Upgrade Script and UUPS Pattern

The upgrade script performs a controlled, verifiable upgrade:

  • Reads the proxy address from the deployment artifact or an override.
  • Deploys a fresh implementation.
  • Calls upgradeToAndCall with optional initialization data.
  • Verifies the ERC-1967 implementation slot changed.
  • Checks dependencies to prevent upgrading one contract while another remains behind.
  • Refuses a proxy that does not answer a getter only the target has, since an implementation of one kind installs cleanly on a proxy of another and would brick it.
  • Broadcasts with the key it is given and cannot act as the timelock. After the handover, deploy the implementation from any funded key, have the Safe schedule upgradeToAndCall on the timelock, and let anyone execute it once the delay has run.
sequenceDiagram
participant User as "User"
participant Script as "Upgrade Script"
participant Impl as "New Implementation"
participant Proxy as "UUPS Proxy"
User->>Script : Run with TARGET and DEPLOYER_PRIVATE_KEY
Script->>Script : Resolve proxy address
Script->>Script : Check dependencies
Script->>Impl : Deploy new implementation
Script->>Proxy : upgradeToAndCall(newImpl, initData)
Proxy-->>Script : New implementation slot updated
Script-->>User : Log old/new implementation
Loading

Diagram sources

Dependency checks:

  • Reputation depends on Identity having operatorChangedAt.
  • Staking depends on Identity having stakeView.
  • If a registry is not wired yet, checks are skipped.

Section sources

Contract-Level Upgrade Authorization

Each registry implements UUPS upgrade authorization gated by UPGRADER_ROLE:

  • SigvaraIdentity._authorizeUpgrade restricts upgrades to UPGRADER_ROLE.
  • SigvaraStaking exposes UPGRADER_ROLE and uses UUPS upgradeability.
  • Other contracts follow the same pattern.
  • Each registry's initialize makes UPGRADER_ROLE its own role admin. Otherwise the admin Safe could grant itself the role and upgrade in the next transaction, which would make the timelock a delay on the honest path and nothing on a compromised admin.
classDiagram
class SigvaraIdentity {
+UPGRADER_ROLE()
+initialize(admin, stakingCore)
+_authorizeUpgrade(address)
}
class SigvaraStaking {
+UPGRADER_ROLE()
+initialize(...)
+depositStake(...)
+initiateWithdrawal(...)
+executeSlash(...)
}
class UUPSUpgradeable {
<<abstract>>
+upgradeToAndCall(address, bytes)
+_authorizeUpgrade(address)
}
SigvaraIdentity --|> UUPSUpgradeable
SigvaraStaking --|> UUPSUpgradeable
Loading

Diagram sources

Section sources

Role Administration and Emergency Powers

After the handover the powers divide as follows. The Safe's powers are not delayed, so its owners and threshold matter as much as the timelock does.

Power Holder Delay
UUPS upgrades, grants of UPGRADER_ROLE, the slasher role (bond registry), the slashing committee role (staking, reputation) and the staking-core role (identity, reputation), and the calls setStakeView (identity), setOperatorBond (reputation) and setSlashBeneficiary (bond registry) UPGRADER_ROLE, held by the timelock 48 hours by default
Grants of ORACLE_ROLE and GUARDIAN_ROLE, revoking any role except UPGRADER_ROLE, parameter changes, resolveDispute, vetoSlash DEFAULT_ADMIN_ROLE, held by the Safe None
pauseInflows GUARDIAN_ROLE, a separate and lighter key None, and it ends by itself after 3 days
  • TimelockedGrants is the other direction. A role's admin would normally be the only party able to revoke it, so a compromised slasher would have to be removed through the same 48 hours it needs to do its damage. The base contract lets DEFAULT_ADMIN_ROLE revoke at once any role that UPGRADER_ROLE administers, except UPGRADER_ROLE itself.
  • The parameters the admin sets have floors and ceilings in the contracts, so a typo or a compromised key cannot move one outside them: the slash challenge period (3 to 30 days), the unbonding periods (1 to 90 days), the reputation challenge window (1 hour to 7 days) and the maturity rate (above 0, at most 100 points a day). The minimum stake and the bond amount only have to be above zero.
  • InflowGuard lets the guardian close only the ways something new enters: registerAgent, depositStake, proposeReputation and proposeIfEmpty, and depositBond. It never covers withdrawals and claims, unbonding, disputes, rulings, slash execution, finalizing or rejecting a score already proposed, operator transfers, or any admin call. A pause lasts 3 days, longer than the 48 hour upgrade delay, so it can bridge an emergency upgrade. The next one cannot begin until a day after the last would have ended, counted from its start even if it was lifted early. The guardian or the admin lifts a pause early, and the admin revokes a guardian at once.
  • A change of stake view, reputation's operator registry or slash beneficiary therefore takes 48 hours of notice, so a redeployed staking contract is a scheduled operation. (SigvaraEpochFees, which is not deployed, lets the admin set its own operator registry at once.) Taking a role away is immediate.

Section sources

Storage Layout and Backward Compatibility

Contracts append new storage fields at the end to preserve live state across upgrades:

  • Comments explicitly mark appended mappings and structs.
  • Tests assert that stake, scores, and other state survive upgrades.
  • Slot-pinning tests ensure mappings remain where they were.

Examples:

  • Identity appends slashSuspended, stakeView, transfer tracking, and operator index.
  • Staking appends unbondingPeriod and dispute-related fields, then claimable and lastDroppedAt.
  • Reputation appends proposer and finalizableAt to the pending score, and the bond registry appends its two signer mappings.
  • InflowGuard keeps its state in an ERC-7201 namespaced slot, so adding the pause to a live proxy shifts no existing variable.

Section sources

Fork Test Validation

The fork test rehearses the migration against a live-like chain:

  • Confirms the deployer loses upgrade and admin powers.
  • Confirms the Safe can still administer roles.
  • Pins that on the live testnet proxies, which were deployed before UPGRADER_ROLE administered itself, the Safe can still give itself upgrade rights and skip the timelock.
  • Confirms upgrades require the timelock delay.
  • Confirms other roles remain untouched.
  • Confirms an in-flight slash drill still settles. The test skips itself once the drill proposal is no longer Pending, which is the case now that the drill was executed on 3 October 2026.

Section sources

Dependency Analysis

The upgrade process must respect inter-contract dependencies:

  • Identity is foundational; it provides DID registration and status.
  • Reputation depends on Identity’s operatorChangedAt.
  • Staking depends on Identity’s stakeView.
  • Identity's stakeView points back at staking, and repointing it is a timelocked call, so replacing the staking contract is a scheduled operation.
  • Oracle Bond and Epoch Fees participate in governance roles but are not central to identity/staking flows.
graph LR
ID["SigvaraIdentity"] --> REP["SigvaraReputation"]
ID --> STK["SigvaraStaking"]
REP --> STK
ORB["SigvaraOracleBond"] -.-> GOV["Governance Roles"]
EF["SigvaraEpochFees"] -.-> GOV
Loading

Diagram sources

Section sources

Performance Considerations

  • Timelock delay prevents rushed upgrades and gives observers time to react.
  • Open executors mean scheduled changes cannot be quietly withheld.
  • UUPS upgrades are gas-efficient compared to replacing proxies, but still require careful testing.
  • Large operator agent lists can be expensive off-chain; paged views exist to mitigate scanning costs.

Troubleshooting Guide

Migration Fails Before Renounce

Symptoms:

  • Safe cannot execute required calls.
  • Timelock delay is zero or missing.
  • Target contracts did not receive admin/upgrader roles.

Actions:

  • Re-run PHASE=plan to inspect current roles and environment variables.
  • Ensure the Safe address has code and is not the deployer.
  • Execute the generated Safe calldata exactly as instructed.
  • Do not proceed to renounce until the Safe's removal of the deployer's admin has landed on every target.

Section sources

Renounce Refuses on a Role Check

Symptoms:

  • PHASE=renounce reverts DeployerStillAdmin, UpgraderRoleNotSelfAdministered or RoleNotTimelocked.

Actions:

  • DeployerStillAdmin: the Safe has not yet executed the revocation that PHASE=safe-calldata prints.
  • The other two: the target was initialized by code from before the role split, as the Arc testnet proxies were, so the admin can still grant UPGRADER_ROLE or a fund-moving role and skip the timelock. PHASE=plan reports which case each contract is. Do not work around the refusal; renouncing cannot be undone.

Section sources

Renounce Refuses the Timelock or the Safe

Symptoms:

  • PHASE=grant or PHASE=renounce reverts TimelockHasNoCode, TimelockCodeNotRecognized, TimelockNotCreatedByDeployer or SafeIsADelegatedAccount, or PHASE=renounce reverts TimelockDelayTooShort, TimelockCannotBeProposedTo, TimelockCannotBeCancelledBy, TimelockExecutionNotOpen, TimelockHasOutsideAdmin, DeployerCanActAlone, SafeThresholdTooLow or SafeNotInspectable.

Actions:

  • A timelock is built in its constructor, and afterwards only the timelock can change its own delay or roles, so a bad one is replaced and not repaired. Run PHASE=deploy-timelock again with the default delay, point TIMELOCK at the new address and run PHASE=grant so that it receives UPGRADER_ROLE. Revoke UPGRADER_ROLE from the old timelock on every registry while the deployer still can; check-role-holders.mjs with BEFORE_RENOUNCE=1 fails on a second holder.
  • TimelockHasNoCode: the address is a typo, or exists only on another chain. Phases 3 and 5 both refuse it.
  • TimelockCodeNotRecognized: the code is not the TimelockController this repository compiles, which is what phase 2 deploys. The error prints the hash found and the one expected (cast codehash and forge inspect TimelockController deployedBytecode | cast keccak). The metadata solc appends is left out of the comparison, so line endings in a checkout do not matter. If phase 2 built the timelock, run the phase from the commit that deployed it. Otherwise use the one phase 2 builds.
  • TimelockNotCreatedByDeployer: the code is right but the address is not one the deployer created. A constructor can set state that the code does not show, such as an operation already scheduled, so only a timelock made by PHASE=deploy-timelock is accepted. Deploy a new one with that phase.
  • SafeNotInspectable: the address cannot be asked for its threshold, so it is not a Safe. Use a Safe. If it is another multisig that was checked some other way, set CONFIRM_UNINSPECTABLE_SAFE=yes-i-checked-the-threshold for the renounce run; nothing in the script has looked at it.
  • SafeIsADelegatedAccount: the address is an account that has delegated to a contract (EIP-7702), 23 bytes of code beginning 0xef0100. Its own key can act alone whatever threshold the contract reports. Use a Safe.
  • DeployerCanActAlone and SafeThresholdTooLow: raise the Safe's threshold to 2 or more in the Safe itself before renouncing. A Safe can change its own threshold later, so this is launch policy and not a point of no return, but the refusal stays until it is met.
  • Do not work around a refusal; renouncing cannot be undone.

Section sources

Upgrade Reverts Due to Dependencies

Symptoms:

  • Upgrading reputation or staking reverts because Identity is behind.
  • Live chains break after partial upgrades.

Actions:

  • Upgrade Identity first.
  • Use the upgrade script’s built-in dependency checks.
  • Verify the target contracts expose required functions before broadcasting.

Section sources

Upgrade Does Not Change Implementation

Symptoms:

  • Script logs old and new implementation are identical.
  • Proxy slot unchanged.

Actions:

  • Confirm DEPLOYER_PRIVATE_KEY holds UPGRADER_ROLE.
  • Ensure the script actually called upgradeToAndCall.
  • Check that the implementation was deployed and differs from the old one.

Section sources

Emergency Scenarios

  • If the Safe is misconfigured, do not renounce. Keep the deployer roles and fix the Safe.
  • If the timelock delay is too short or too long, adjust through governance proposals once migrated.
  • If an upgrade breaks functionality, rely on the fact that proxy addresses do not change; roll back by scheduling a previous implementation through the timelock.
  • For a live bug while a fix is built, the guardian pauses new registrations, stake deposits, score proposals and bond deposits for three days, which outlasts the 48 hour upgrade delay. Exits, claims, disputes, rulings and slash execution keep working.
  • To remove a compromised slasher, committee member or staking core, the admin revokes the role at once. Granting a replacement takes the timelock's delay on a registry initialized by the current code.

Section sources

Conclusion

Sigvara’s governance model separates administration from code changes. The Safe manages the oracle and guardian roles, parameters within fixed bounds, and the ruling on and veto of slashes, while the TimelockController enforces a public delay for upgrades and for the grants and wiring that can move funds. The phased migration ensures the deployer never loses control prematurely, and the upgrade script provides auditable, dependency-aware upgrades. Storage layout discipline and tests protect live state across versions. For mainnet operations, treat the timelock as the single most important security boundary: proposals should be reviewed publicly, executed only after the delay, and recorded in the deployment artifact.

Appendices

Step-by-Step Upgrade Guide

  1. Prepare the new implementation locally.
  2. Set TARGET to the contract being upgraded.
  3. Simulate the upgrade without broadcasting.
  4. Review dependency checks and ensure dependent contracts are current.
  5. Before the handover, broadcast with the deployer key. After it, deploy the implementation from any funded key, have the Safe schedule upgradeToAndCall on the timelock, and let anyone execute it once the delay has run. If the new code has a reinitializer, the Safe calls it in a second transaction.
  6. Record the new implementation using the repository’s recording script.
  7. Verify the implementation slot changed and state is preserved.

Section sources

Best Practices for Governance Participation

  • Treat timelock proposals as public commitments; review them before execution.
  • Prefer multi-signature Safe governance with threshold greater than one.
  • Keep the timelock delay meaningful and consistent across chains.
  • Validate that all dependent contracts are upgraded in order.
  • Record implementation addresses and histories in the deployment artifact.

Section sources

Overview

Clone this wiki locally