Dash
ButtonDots
Back

Smart Contract Upgradeability: Proxy Patterns, Storage Layout, and Migration Risks

Smart contract upgradeability is achieved by splitting state and logic across two contracts, so that a proxy holds the storage while a swappable implementation holds the code.

9 min read
08 Sep 2026
Smart Contract Upgradeability: Proxy Patterns, Storage Layout, and Migration Risks
Share:

Smart contract upgradeability is achieved by splitting state and logic across two contracts, so that a proxy holds the storage while a swappable implementation holds the code. Many Ethereum protocol upgrades over the last decade have had unintended effects such as storing data in colliding storage locations, "bricking" a particular type of proxy, or even handing attacker-controlled addresses "upgrade authority" in some form or another. The notion of "upgradeability" in smart contracts sounds very positive, but translates very poorly to actual implementation details. Solidity, the programming language for smart contracts on Ethereum, provides a set of proxy patterns, all of which have tradeoffs in terms of gas cost, the size and complexity of the access control surface, and the danger of slot corruption.

This post details the storage collisions that can occur at the EVM level, the difference between an initializer and a constructor and how this affects storage, and finally the problems of previous migration sequences that have resulted in the locking of funds or permanent destruction of contract logic. Although proxy patterns in Solidity have reached a level of maturity that makes them safe to use, it is important for developers to understand the specific failure modes for each of the patterns in order to use them safely and to write safe upgrade logic.

Why Smart Contract Upgradeability Is a Strategic Decision, Not Just a Technical One

Choosing whether to make a protocol upgradeable shapes trust, governance, and long-term security from day one.

The Immutability Trade-off: Trust vs. Adaptability

Immutability is a feature. Users who interact with a fixed contract know its behavior cannot be silently altered, removing a critical trust assumption. Introducing upgradeability trades that certainty for flexibility, a genuine immutability trade-off that must be explicitly managed through documentation and audits.

When Upgradeability Makes Sense, and When It Doesn't

Upgradeable smart contracts suit protocols under active development or operating in regulatory-sensitive environments where post-launch bug fixes are essential. Narrow, well-audited primitives, ERC-20 tokens, simple vaults, rarely need it; immutable deployment is the safer choice there.

How Upgrade Authority Affects Investor and User Confidence

Upgrade authority, whether held by a single admin key or a multisig, is often the largest centralization risk in protocol governance. Disclosing this in audits and docs directly affects how investors assess decentralization and how users gauge counterparty risk.

The Three Dominant Proxy Patterns for Upgradeable Smart Contracts

There are many patterns, but most involve the following basic architecture: a proxy holds state and a backend that implements logic, connected via delegatecall. Understanding proxy patterns solidity developers commonly reach for is essential, as these are the root causes of many upgrade issues people hit with production smart contracts.

Transparent Proxy: Admin Separation and Function Selector Clashes

Transparent proxies, implemented for example with the OpenZeppelin proxy library, split up the routing of calls based on the caller. Admin calls are routed to the proxy, user calls are forwarded to the implementation. The implementation must take care not to overwrite function selectors of the proxy in storage, and in every user call a storage read is incurred with measurable gas cost in big scaled applications.

UUPS Proxy: Upgrade Logic Lives in the Implementation

EIP-1967 reserves certain storage slots for the implementation contract and the admin address of a proxy. Using this as a basis, the UUPS proxy implementation then moves the upgrade logic to the implementation contract. The key UUPS vs transparent proxy distinction is that this shift introduces a severe tradeoff: if one deploys an implementation contract that is missing the function to perform an upgrade (i.e. the upgrade function is not implemented) then the corresponding proxy is 'bricked' and there is no way to recover from this.

Beacon Proxy: Upgrading Many Contracts in a Single Transaction

Beacon proxy is preferred for a large number of identical contracts deployed by a factory, such as user vaults. A single beacon update is then propagated to all instances in atomic fashion, avoiding the upgrade coordination overhead of individually updating to proxy a contract that has been upgraded. Teams that need this topology implemented and reviewed end to end usually scope it as part of smart contract development services.

Storage Layout Collision: The Silent Killer of Proxy Upgrades

The proxy code and the implementation code live in the same address space delegatecall storage. So one small misplaced variable can very silently corrupt some parts of the code that are running.

How Solidity Assigns Storage Slots and Why Order Matters

In Solidity, the storage slots for state variables are assigned sequentially from slot 0 onwards. In the case of a proxy/implementation, the slot 0 of the proxy (i.e. the outer contract) is also the slot 0 of the implementation (i.e. the inner contract). Thus, any discrepancy between the storage layout in the two contracts will write over the top of the contract's live storage without any error messages or reversion. visual

Inherited Contract Storage and the Linearization Trap

When a new variable is added to a parent contract in the middle of an upgrade, all subsequent storage slots of the child contract will end up being corrupted with user's balances or critical addresses. To safely add variables in base contracts, OpenZeppelin uses storage gap pattern reserving a block of slots (e.g. uint256[50] private __gap) that are then skipped over in subsequent adds.

Using Storage Gaps and Namespaced Storage (ERC-7201) to Future-Proof Layouts

ERC-7201 namespaced storage structures can use keccak256 to derive a deterministic slot for each storage structure member. These incompatibilities can be found by OpenZeppelin's Hardhat and Foundry Upgrades plugins as well as by Slither's layout checker before deployment. Layout validation is also one of the first things covered in a blockchain security audit of a proxy-based system.

Initializers, Constructors, and the Upgrade Initialization Trap

This means the constructor executes on the implementation contract in deployment time, not on the proxy. Therefore the state of the proxy's storage context is never affected by the constructor.

Why Constructors Don't Work in Upgradeable Contracts

Note that a constructor proxy relationship is broken because delegatecall executes within the storage of the proxy, which the implementation's constructor never writes to. Hence all variables declared within the constructor of an implementation will only ever exist in the address space of the implementation, and thus will be useless.

Protecting Initializers From Being Called Twice or by Attackers

Unlike a constructor, an initialize function must be marked as Initializable by OpenZeppelin and called in a manner that prevents re-initialization, a proven attack vector in contract execution.

Reinitializer Patterns for Multi-Phase Upgrade Rollouts

The OpenZeppelin implementation of a reinitializer (function reinitializer(uint64 version)) is used to safely allow for multiple setup of upgradeable contracts in a multi-step migration. Any initializer function on the bare implementation contract should also be called as part of the initial deployment of the contract to prevent an attack to initialize the (currently uninitialized) implementation contract first.

Migration Risks: What Can Go Wrong When You Push an Upgrade

Pushing an upgrade to a live protocol introduces failure modes that extend well beyond compilation errors or logic bugs.

State Migration Failures and Data Corruption Scenarios

State migration is the highest-risk phase of any upgrade. If new storage variables require computed initial values, such as restructuring a mapping, that logic must execute atomically and be tested against a mainnet fork before deployment. A well-defined smart contract migration strategy becomes critical when migration scripts are tested only on local networks, where edge cases in live protocol state don't surface.

Front-Running Upgrade Transactions on Mainnet

Upgrade transaction front-running is a concrete threat: the pending transaction is visible in the mempool, giving sophisticated actors a window to exploit the gap between old and new implementations. OpenZeppelin's TimelockController, configured with a 48 to 72 hour delay, closes this window while also giving users time to exit before a contentious change takes effect.

Rollback Impossibility: Why Upgrades Are Rarely Reversible in Practice

An upgrade rollback restores the proxy pointer but not corrupted storage, making true reversal nearly impossible. Pre-upgrade state snapshots, simulation on a forked mainnet, multisig ceremony execution, and post-upgrade invariant checks form the minimum viable runbook for any protocol serious about operational safety.

Access Control and Governance for Upgrade Authority

Who controls the upgrade process of a protocol is who controls the protocol. Poor key management of security measures compromises them all.

Multisig vs. On-Chain Governance for Upgrade Execution

In terms of risk, a single EOA managing upgrade access control is a single point of failure in the form of a compromised private key fully taking over the entire protocol. A 3-of-5 Gnosis Safe multisig is the bare minimum a mainnet protocol should utilize for user held funds. On-chain governance with Governor Bravo adds further decentralization but introduces the risk of the multisig for upgrades. This can include low quorum and highly concentrated token holders ability to pass malicious proposals.

Timelocks as a User-Protection Mechanism

A timelock controller can also be used to keep upgrades pending for a while (typically 24 to 72 hours) to allow users to withdraw their funds before a possibly contentious upgrade is applied. It is even possible to make such a timelock controller contract-tamper-resistant using OpenZeppelin's TimelockController smart contract.

Progressive Decentralization: Moving From Admin Key to DAO Control

Note that a desired end state of on-chain governance does not have to be the launch point for a protocol. A protocol can launch with multisig, then add a timelock at certain TVL milestones before passing off governance to a DAO as the token circulation broadens, following a pattern of progressive decentralization.

Testing and Auditing Upgradeable Smart Contracts

Forked Mainnet Simulation Before Every Upgrade

Run Foundry upgrade tests or Hardhat forked against live state to find storage collisions and broken initializers that were not found by your clean-state unit tests. Use invariant fuzz testing post-upgrade to test core protocol invariants such as total supply == sum of all balances in the system.

Storage Layout Validation With Automated Tooling

Both the Hardhat Upgrades plugin and the forge-upgrades command from Foundry run automated storage layout checks on upgrade between old and new code and report any conflicts between the two on storage in slots.

What Auditors Specifically Look For in Proxy-Based Systems

Auditing an upgradeable contract includes identification of unprotected initializers, checks for missing UUPS authorization, identification of differences in storage layout, and audit of the length of time of the lock time to ensure it is appropriate for the contract. See the smart contract security framework for more on this.

Should Your Protocol Be Upgradeable? A Decision Framework for Founders

Three diagnostic questions to decide if smart contract upgradeability is ready to be deployed.

Red Flags That Mean You're Not Ready to Ship Upgradeable Contracts

However, being unable to answer the following questions means that your protocol is not ready for mainnet yet: Who has the keys to update? What is the timelock delay? How is storage layout validated? Shipping upgradeable contracts to run on mainnet without having these answers written down expands the attack surface to include the upgrade mechanism itself.

When to Choose Immutability and Build Upgrade Paths at the Application Layer Instead

Whether to opt for a project that is immutable or upgradeable generally depends on several scope conditions. Immutability on projects like ERC-20 tokens is generally the better choice as a new version can be deployed and users migrated via the user interface. The decision on immutability or upgradeability should be included in your security model and communicated with auditors and investors accordingly.

Conclusion

The trade-offs for gas, governance, and failure between Transparent, UUPS, and Beacon proxy upgrade patterns all depend on your contract's storage layout, initializer behavior, and upgrade authority. Storage slot collisions and uninitialized proxy contracts are among the most damaging failure modes, so proper testing of upgradeable contracts with a robust testing setup including forking of test environments and pre- and post-upgrade invariant checking is crucial. Immutability and upgradeability are not opposites, and the OpenZeppelin Upgrades plugin surfaces these kinds of storage and initialization errors in existing codebases before they reach production. Teams scoping an upgrade architecture alongside the rest of their protocol stack can review the available Web3 development capabilities before locking the design in.

FAQ

What are the biggest risks of smart contract upgradeability in production protocols?

The greatest dangers lie in the storage layout being redefined so that old variables get overridden by new ones; initializers not being correctly set up for a contract so it is not properly initialized or configured after an upgrade; the authority to upgrade a contract being a single point of failure and thus prone to being compromised by an attacker; and upgrades being done too quickly and not getting adequate testing on a forked mainnet before being deployed in production. Understanding upgradeable smart contract risks in full before committing to a proxy pattern is essential, as careless implementation can result in large amounts of funds being irretrievably lost.

How does a storage layout collision happen in proxy contracts?

A storage layout collision can occur if a new contract for an implementation of a contract stores its state variables in a different order, or even inserts new variables in between currently stored variables. Since the proxy contract is only there to call the functions of the implementation contract while preserving the storage of the proxy contract, storage collisions will silently corrupt the state of the live contract. OpenZeppelin uses the storage gap pattern for storage, and ERC-7201 namespaced storage as the two primary methods to mitigate this class of issue. Run your typical audits (e.g. slither) and a custom storage diff script on your typical upgrade deploy scripts.

Is UUPS or Transparent Proxy better for a new DeFi protocol?

The case for UUPS (EIP-1822) upgrades for new projects is to keep the upgrade logic within the implementation contract. The overhead for deploying a proxy in these cases is more than offset by not embedding a giant proxy contract. On the other hand, Transparent proxies have the advantage that it is harder to accidentally remove the upgrade mechanism, which can be a big deal for forward facing contracts. For operational safety aware protocols, UUPS may not be the best approach for them, but for teams with good test coverage and smaller budgets, UUPS can be a good choice.

When should a Web3 founder choose a non-upgradeable smart contract instead?

When immutability is a key trust guarantee for your users, then it is better to deploy non-upgradeable contracts such as for decentralized custody, settlement layer contracts or even token contracts where users trust that there will never be any changes to the rules for that contract. Allowing upgradeability at some point in time for such protocols only results in more surface area for potential attacks from a governance perspective once a security bug is found. Such contracts only need a full migration to a fixed version in case of bugs found after deployment. Founders should treat upgradeability as a time-limited scaffold to deploy a protocol and then plan a fixed, immutable version as the protocol matures.

Writing team:
writer avatar
Bogdan
Copywriter

You may also like

new york: 16:15
dubai: 16:15
Kiyv: 16:15
INTRIGUED?
LET'S BUILD TOGETHER
From zero to pitch-ready in weeks. We design MVPs that win investors.
Chat on Telegram
DotsYellow
Book a Call
DotsYellow
Get project estimate
DotsYellow