Upgradeability Audit Checklist

A condensed, ready-to-use checklist consolidating the material in §4.12.0 through §4.12.4. Every upgradeable contract in scope should be evaluated against the items below. Findings should be reported individually; the consolidated checklist exists as a coverage map, not as a substitute for narrative analysis.

Pattern Identification

  • The proxy pattern is one of: Transparent, UUPS, Beacon, Diamond (EIP-2535). Any custom proxy has an explicit justification.
  • The pattern matches the use case (single contract vs. many instances; size budget; modular vs. monolithic).
  • EIP-1967 slots are used for implementation, admin, and beacon. No implementation variable lands on those slots.

Storage Safety

  • Either every base contract has a documented storage gap (uint256[N] __gap), or the protocol uses ERC-7201 namespaced storage consistently.
  • Storage layout is validated against the prior implementation via @openzeppelin/upgrades-core (or equivalent), as a CI gate on every PR touching upgradeable code.
  • Inheritance order is unchanged across upgrades; new base contracts are appended.
  • No state variable has been reordered, removed, or had its size changed across versions.
  • For diamonds: every facet uses unique namespaced storage; no two facets write to the same slot accidentally.

Initialization

  • (UUPS only) Every implementation calls _disableInitializers() in its constructor.
  • Proxy deployment and initial initialize call happen in a single transaction (constructor calldata).
  • The initializer modifier is present on every initialization entry point.
  • Every inherited __Base_init is called exactly once, in the correct order, in the initializer chain.
  • All upgradeable bases use the *Upgradeable variant (e.g. OwnableUpgradeable, not Ownable).
  • No reinitializer(N) reuses a version number; each is gated by appropriate access control; each is actually called during the matching upgrade.
  • No external call is made mid-initialization before the contract's invariants hold.
  • Constructor logic is limited to _disableInitializers() (and possibly the setting of immutable variables); any state mutation in the constructor is intentional and documented.

Authorization

  • The upgrade authority is a multisig with a sensible threshold (≥ 2-of-3, typically 3-of-5 or higher).
  • Multisig signers are distributed (not all controlled by one entity, not all hot wallets, all on hardware).
  • (UUPS only) _authorizeUpgrade is present, correctly access-controlled, and tested in every implementation; the new implementation cannot accidentally remove or relax it.
  • No EOA holds the upgrade role on a production deployment.
  • Function-selector clashes between proxy admin and implementation are impossible (structural for TPP; verified by tooling for UUPS and Beacon).

Timelock

  • A timelock interposes a meaningful delay (≥ 24 hours; longer for protocols with non-instant withdrawal flows).
  • The timelock cannot be bypassed by an "emergency" or "skip" path without independent authorization.
  • The timelock's own administration is at least as strong as the upgrade authority itself.
  • Queued upgrades are visible on-chain with human-readable parameters, or surfaced by a monitoring service the team operates.
  • The delay is long enough for users to exit their positions before execution.

Implementation Hygiene

  • The new implementation is deployed and verified on Etherscan/equivalent before any upgrade is queued.
  • The implementation address in the queued proposal matches the verified contract.
  • The implementation has been tested against a mainnet fork; the test report is public or shared with reviewers.
  • Validation via forge inspect ... storage-layout, slither-check-upgradeability, or validateUpgrade is part of the release process.
  • No selfdestruct or delegatecall to attacker-controllable addresses is reachable from the implementation.

User Protection

  • Users can withdraw their funds at any time during the timelock window.
  • Pause functionality (if any) cannot be used to prevent withdrawal during the timelock window — or, if it can, the pause role is held by a separate, more constrained authority.
  • No admin function can confiscate, blacklist, or freeze user funds outside of clearly documented compliance flows.
  • Documentation honestly describes who can upgrade, on what timescale, with what oversight. The audit report's centralization section reflects this.

Monitoring and Recovery

  • On-chain monitoring (Defender, Tenderly Alerts, Forta, or equivalent) emits alerts on queued upgrades, executed upgrades, and any change to the upgrade authority.
  • There is a documented procedure for key rotation if a signer is compromised.
  • There is a documented procedure for incident response (pause, communicate, patch, post-mortem).
  • The team has tested the procedures, not just written them.

Documentation and Disclosure

  • The upgrade pathway is documented in user-facing materials.
  • The current implementation address and ProxyAdmin (or equivalent) addresses are published and easy to find.
  • Past upgrades are catalogued with links to the relevant transactions and changelogs.
  • Centralization risks are surfaced explicitly, not hidden behind "decentralized" marketing copy.

Findings to Always Surface

Even when no individual bug exists, the audit report should explicitly state:

  • The proxy pattern in use, and the rationale (if non-obvious).
  • The full upgrade authority chain: which multisig, what threshold, what timelock delay, what governance.
  • The realistic worst-case impact of upgrade-authority compromise, and the realistic time window for user response.
  • Any unusual upgrade paths (factory setImplementation, registry pointers, beacon swaps) that may not be obvious from the proxy itself.

A "no findings" upgradeability section is rare in practice. A complete one — even when no bug is found — gives users the information they need to make their own risk decisions, which is ultimately the point of the audit.