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, andbeacon. 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
initializecall happen in a single transaction (constructor calldata). -
The
initializermodifier is present on every initialization entry point. -
Every inherited
__Base_initis called exactly once, in the correct order, in the initializer chain. -
All upgradeable bases use the
*Upgradeablevariant (e.g.OwnableUpgradeable, notOwnable). -
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 ofimmutablevariables); 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)
_authorizeUpgradeis 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, orvalidateUpgradeis part of the release process. -
No
selfdestructordelegatecallto 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.