DeFi Security Alliance

EVM patterns

Upgradeable contract audit: storage, initialization and release authority

An upgrade changes the rules that interpret existing state. An upgradeable contract audit must bind the starting deployment, candidate implementation and executed payload to a tested migration and authority model.

Grid of stacked storage blocks with one misaligned blue block sliding into place, representing an upgradeable contract audit.

Key facts

Disclosure signal
36 of 50 readable reports contain upgrade-related wording
Archive coverage
16 of 17 sampled archives have a match
Scope limit
Mentions include checklists and component descriptions
Acceptance boundary
Storage, initialization, authority and deployment readback

Proxy patterns in scope

An upgradeable contract audit reviews the state transition from one deployed implementation and permission configuration to another. The candidate source is only part of the input. Acceptance also requires evidence that existing storage remains interpretable, initialization runs as intended and the authorized upgrade path cannot be bypassed.

Define the starting state before reviewing the patch.

Record the chain, proxy address, implementation address and the block used to observe them. Include the upgrade authority and any intermediate controller. A repository tag without that deployment map cannot establish which existing state the candidate will inherit.

Transparent and UUPS proxies place upgrade behavior in different locations.

OpenZeppelin's reference describes transparent proxies with administrative upgrade logic in the proxy, while UUPS implementations supply the upgrade mechanism used through an ERC1967Proxy. The distinction determines which code and permissions must remain reachable after the upgrade. An apparently valid implementation can remove an upgrade route by design, so permanence must be an explicit decision.

A beacon creates a different scope.

Multiple proxies can follow a beacon whose implementation changes together. Reviewing one representative proxy's application state may be useful, but the deployment inventory must still identify the whole affected set and any configuration differences. A shared implementation does not imply identical initialized state.

Proxy pattern and the evidence the audit must follow
PatternUpgrade control locationRelease evidence
TransparentProxy administration and its controllerProxy, administrative controller and candidate implementation
UUPSUpgrade logic reached through the implementationAuthorization override and compatible implementation route
BeaconSeparate beacon supplying implementationBeacon owner and affected proxy inventory
Minimal cloneDepends on the actual clone designDo not infer an upgrade route from the word proxy

OpenZeppelin proxy documentation distinguishes these patterns. The review must identify the deployed variant and library version rather than borrowing assumptions from another pattern.

Storage-layout diff: preserve meaning, not just slot counts

A layout comparison asks whether the candidate will interpret old bytes as the same logical state.

A matching number of variables is irrelevant if their order, types or packing change. A renamed field can preserve layout while changing its intended meaning; a mechanically compatible layout can still require a business-state migration.

For conventional inherited storage, preserve the existing field ordering and examine changes to base contracts.

Adding a field to a base can move fields in a child. Reserved gaps are intended to accommodate additions, but their size and packing must be checked against the compiled layout. Do not calculate compatibility by counting source lines.

Namespaced storage provides another organization model.

ERC-7201 defines a namespace annotation and a root-location convention. The annotation is not runtime enforcement: the implementation must actually use the declared namespace correctly. A namespace also does not move data from a previously used location. Migrating an existing deployment to a different layout requires an explicit, reviewed transition.

OpenZeppelin's upgrade guidance states that its validation checks changes within recognized namespaces, subject to compiler information and the supported toolchain. Preserve those compiler outputs in the release packet. A validation pass establishes a set of structural checks, not the correctness of every application-level transformation or custom assembly access.

Test existing state, not only a fresh deployment.

An empty mapping cannot reveal whether balances belonging to real keys became unreachable. A useful isolated upgrade rehearsal seeds representative positions and boundary values under the old implementation, applies the actual upgrade payload and checks their meaning under the new implementation. A fork may supply realistic state; synthetic fixtures should expose states that the chosen block lacks.

  1. Export layouts for both implementations using the pinned build configuration.
  2. Compare field location, type and packing across every relevant base or namespace. Flag custom storage access for manual review.
  3. Define any intended migration as a separate transformation with preconditions and postconditions.
  4. Rehearse the exact upgrade against populated state and verify balances, roles and accounting invariants afterward.

ERC-7201 namespace requirements and OpenZeppelin upgrade validation guidance define structural constraints. Their checks do not replace the application-specific assertions.

Initializer checks: new state needs an authorized transition

A constructor executes in the deployment context of the implementation.

It does not initialize the proxy's application storage merely because that proxy later delegates to the implementation. Proxy state therefore needs an initialization path appropriate to its design, with authorization and one-time or versioned execution constraints.

Check the deployment transaction as well as the function body. If a proxy becomes accessible before required initialization completes, another account may reach an exposed initializer. An initializer guard that allows exactly one call does not by itself establish that the intended account gets that call. Atomic deployment and initialization can close a gap, but the actual deployment mechanism must be reviewed.

Inheritance adds another obligation. Parent initializers require deliberate invocation, and their ordering must match the intended construction of state. OpenZeppelin's tools check several initializer mistakes, including missing or repeated parent calls. Its documentation also states that reinitializers are not validated as deployment initializers by default because their purpose cannot be inferred automatically.

Treat every upgrade-time reinitializer as a migration operation. State who may call it, which version it consumes and what happens if it is invoked twice or skipped. When the upgrade and initialization can be combined, preserve the combined payload in the review evidence. Testing a manually called helper after the upgrade is not equivalent to testing the production transaction.

The implementation address itself is another target. OpenZeppelin recommends disabling initialization on the implementation to prevent unintended takeover of that separate instance. Do not describe this as initializing the proxy or as a guarantee against every proxy vulnerability. The implementation and proxy hold different storage contexts, and both must be understood.

Initializer acceptance cases
CaseExpected resultEvidence to retain
Fresh proxy initializationIntended state and authority are installedDeployment payload and post-state
Repeated initializationRejected by the intended version ruleRejection reason and unchanged state
Unauthorized migration callerCannot perform restricted transitionCaller-specific test
Skipped upgrade migrationDetected before release acceptancePost-upgrade invariant failure or explicit prohibition
Direct implementation initializationDisabled where the design requires itImplementation-level check

For a review packet, include the initializer arguments in readable form. An encoded transaction alone is difficult to compare with a specification and can conceal a wrong administrator or asset address.

Upgrade governance and timelocks

The authority graph is part of the executable system. Follow every route that can replace implementation logic, change the upgrader or alter the controller that authorizes those actions. A role diagram that stops at a multisig misses enabled modules, role administrators and recovery mechanisms that may reach the same operation.

A timelock is useful only where the protected operation must pass through it. Verify which account proposes, which account executes and which account can administer those permissions. If another address can call the upgrade directly, the documented delay does not constrain that route. A pause guardian with upgrade authority is no longer merely a pause guardian.

The delay also needs a purpose. Users may need time to inspect the payload or exit before new logic becomes active. That purpose can fail if withdrawals are paused throughout the notice period or if the proposed implementation can change before execution without a new authorization. Evaluate the surrounding operating model instead of treating a nonzero delay as sufficient.

ERC-1967 recommends events when implementation and administrative slots change, including initialization. Events help observers track transitions, but monitoring should still verify the resulting state. An event with a plausible address is not a substitute for checking which implementation the proxy now uses.

Write negative acceptance cases for alternative authority. Attempt the operation with the old deployer, a removed role holder and any emergency account that should not upgrade. Where a wallet module can execute without the ordinary owner-signature path, include that module in the review scope. A successful standard multisig transaction proves only that one intended route works.

Upgrade acceptance requires authority and state evidence The path connects authorized payload, then state transition, then release readback. Each stage carries a different acceptance condition.Authorized payloadWho may change logicState transitionLayout and migrationRelease readbackObserved implementation
The release is accepted only after the executed payload and resulting state match the reviewed candidate.
Authorized payload
Who may change logic.
State transition
Layout and migration.
Release readback
Observed implementation.

The admin key management guide provides the custody and revocation record for these authorities. Keep those operational controls consistent with the code-level role model.

What changes for the audit after each upgrade

An earlier report remains evidence about the earlier scope. Reuse its design explanations and resolved finding history, then identify the delta that changes the current security argument. A small diff can alter a sensitive authorization check, while a larger mechanical refactor may preserve behavior. Line count alone is a poor measure of review effort.

Start the delta record with reachable behavior. List changed entry points, dependencies, storage locations and permissions, then identify which invariants depend on them. A new oracle adapter can change borrowing assumptions without modifying the lending formula. A dependency upgrade can move guard storage even when the application's own fields remain unchanged.

Fix rounds deserve the same treatment. The fact that a change addresses a finding does not mean it introduces no other behavior. Ask the reviewer to examine the actual remediation revision and relevant regression evidence. Preserve unresolved or acknowledged items instead of rewriting the report summary to imply that every risk disappeared.

Separate a rollback plan from a reverse upgrade assumption. Once users have interacted with new logic or storage has been transformed, returning to old bytecode may reinterpret state incorrectly. A rehearsal should establish the conditions under which rollback is possible. If those conditions cannot be maintained, the incident plan needs another recovery route.

The release record should allow a person outside the implementation team to compare the reviewed candidate with the executed change. Include the final implementation, configuration and transaction identifiers in a durable record. Do not substitute a mutable branch name for a revision. When production readback differs from the packet, resolve the difference before claiming the deployment is covered.

Delta that can invalidate earlier evidence
ChangeReview questionAcceptance artifact
Library or compiler updateDoes generated behavior or storage change?Pinned build and compatibility analysis
New dependencyWhich assumptions move outside the protocol?Integration threat model and tests
Permission changeCan another actor reach a sensitive operation?Updated authority graph
MigrationDo existing positions preserve their intended meaning?Before-and-after assertions
RemediationDoes the fix close the path without a new regression?Reviewed fix revision

The audit request template helps specify delta review and fix rounds. The scope builder can organize the candidate packet around those deliverables.

Our disclosure sample: upgrade vocabulary in public reports

The fresh report census found upgrade, initializer, proxy or storage-layout wording in 36 of 50 readable reports. This is a broad disclosure signal. It includes configuration descriptions and checklist language, so it cannot be called a count of upgrade vulnerabilities.

Method

Retrieved
Member frame
23 firms in the fixed research roster, including historical names.
Report frame
155 previously sampled archive entries.
Selection
The first 3 entries per firm from the earlier deterministic archive sample, fetched again for this study. No content-based selection.
Readable denominator
50 selected reports.
Archive coverage
17 sampled archives.
Cap exclusions
105 report entries excluded before reading.
Missing archive coverage
6 firms have no report in this frame.
Signal
Case-insensitive term matching in converted report text. Exact expressions and surrounding text are retained in the output.

Results

Fresh disclosure census on
ObservationCountDenominator
Readable reports5050 selected reports
Reports with the specified term family3650 readable reports
Archives with a matching report1617 sampled archives

The signal appeared in 16 of the 17 sampled archives. We read selected project-level examples separately before using them below. Their interpretation depends on the report text, not on the automatic total.

Limits

  • The frame includes multiple languages, non-contract assessments and older reports. It is a disclosure sample, not a random sample of deployed protocols.
  • A matching term may be a checklist item, a library identifier or a project finding. Only separately reviewed examples support finding-level conclusions.
  • Archive ownership does not establish report authorship. Some archives contain outside assessments of the archive owner's software.
  • No live exploit was executed. An absent match can reflect terminology or text extraction and cannot establish that a firm lacks the capability.

Reproduction uses seo/research/security-scope-disclosures-survey.py; its dated output is seo/research/security-scope-disclosures-survey-2026-09-05.json. These files remain in the repository and are not published as downloads. The output preserves source responses, retrieval outcomes and the selection rules for later verification.

Member reports with upgradeability evidence

A useful report example connects a migration or initializer concern to a specific implementation. The sample contains such evidence, but it also contains many generic references. An audit buyer should request the former and keep the latter in its proper role as background.

Specific evidence opened during this review
ReportRelevant disclosureBoundary
BlockSec Morph Emerald upgradeMigration discussion for initialization and reentrancy guard stateA warning about moving from direct slots to namespaced storage, not proof that a live deployment was exploited
Quantstamp NomadQSP-40 implementation initializer observationAn informational acknowledged item in a historical report
Cyberscope EstiapaymentsImplementation and inherited upgradeable componentsA component disclosure, not itself an upgrade finding

The BlockSec example matters because the application can appear unchanged while a library migration changes where protection state lives. Its discussion identifies initialization and reentrancy guard storage as properties to preserve. Copying the new library import without a state migration argument would not answer that concern.

The Nomad initializer item illustrates a different reporting boundary. Its existence does not establish that it caused the later bridge incident. That causal claim requires the deployed exploit path and its relationship to the reviewed revision. Keep a historical finding's status separate from an incident investigation.

Before selecting a team, ask for a comparable upgrade review and examine its storage and authority evidence. The member directory and Halborn analysis provide starting points for provider research. The decisive artifact remains a scope that covers your proxy pattern, migration and final release.

An acceptable upgrade leaves a clear chain of evidence: observed starting state, reviewed candidate, authorized payload and verified resulting state. Missing one element does not automatically prove a vulnerability, but it leaves a release claim unsupported. Resolve that gap as a concrete engineering task with an owner and a reproducible check.

For Vyper implementations, retain compiler-specific layout and module rules as part of the comparison.

Keep the evidence together. A layout report stored without its compiler configuration can become difficult to reproduce, and a migration test stored without the old implementation cannot establish the starting state. The release packet should retain both sides of the comparison so later investigators can repeat the same acceptance decision.

Frequently asked questions

Can a frontend-only release reuse the previous contract audit?

The contract evidence can remain relevant if the contract and its configuration are unchanged. Review the frontend changes for altered transaction construction or user permissions and state the separate scope.

What if the proxy's implementation slot is empty?

Do not conclude that no implementation exists. Identify the actual proxy pattern, including a possible beacon or custom mechanism, and resolve its execution target before making a coverage claim.

Should an emergency patch automatically skip the migration rehearsal?

No. Define the available containment options and preserve the checks needed for the specific state transition. Urgency changes operational choices but does not establish storage compatibility.