Upgradeable Smart Contracts: Complete Guide, Examples, Risks and Best Practices
Upgradeable smart contracts are blockchain contracts designed so their application logic can be changed after deployment while users keep interacting with the same public contract address. This is usually done with a proxy pattern: users call a proxy contract, and the proxy forwards the call to a separate implementation contract that contains the actual business logic.
The important beginner idea is this: the proxy keeps the address and the state, while the implementation keeps the code. When the project upgrades, it deploys a new implementation and tells the proxy to use the new implementation from that point forward.
An upgradeable smart contract is a contract architecture where a stable proxy address stores the state and delegates execution to a logic contract. Upgrading usually means changing the implementation address stored by the proxy, not editing already deployed bytecode.
2. Why Upgradeability Exists in a World of Immutable Contracts
Blockchains are designed around immutability. Once a normal smart contract is deployed, its code cannot be edited like a web server or mobile app. This is useful because users can inspect the code and rely on predictable behavior. But real software also needs maintenance: bugs appear, products evolve, standards change, dependencies age and security issues are discovered.
Upgradeable smart contracts try to balance these two needs. They preserve a stable user-facing address, but add a controlled mechanism for changing the logic behind that address. This can be helpful, but it also introduces trust and security risks. Upgradeability should never be treated as a free benefit; it is a powerful permission that must be designed, governed and monitored carefully.
| Need | How upgradeability helps | Risk introduced |
|---|---|---|
| Fixing critical bugs | A patched implementation can replace a flawed one without asking users to migrate manually. | A malicious or careless upgrade can introduce a worse bug. |
| Adding features | A protocol can add new functions, fees, integrations or business rules. | Changing behavior may surprise users or break integrations. |
| Maintaining one address | Frontends, wallets and integrations can keep using the same proxy address. | Users may not realize the code behind the address changed. |
| Governance control | A multisig, timelock or DAO can approve upgrades. | The upgrade authority becomes a high-value attack target. |
3. How Upgradeable Smart Contracts Work
Most upgradeable smart contracts use delegatecall. In a normal external call, Contract A calls Contract B and Contract B uses its own storage. With delegatecall, the proxy executes code from the implementation, but reads and writes the proxy’s storage. That is why users can keep the same balances, ownership records and configuration even after the implementation is replaced.
3.1 Simple Diagram: Proxy and Implementation
During an upgrade, the proxy does not move user balances or redeploy user state. The upgrade changes which implementation address the proxy delegates to. The old implementation may remain on-chain, but the proxy stops using it.
4. The Main Proxy Patterns
The most common upgradeable smart contract designs are Transparent Proxy, UUPS Proxy and Beacon Proxy. They all use the same basic idea, but they put upgrade logic in different places.
| Pattern | Best for | How upgrades are controlled | Main trade-off |
|---|---|---|---|
| Transparent Proxy | Beginner-friendly OpenZeppelin projects and teams that want a clear admin separation. | The proxy has admin logic, often managed through a ProxyAdmin contract. | More expensive to deploy than UUPS and has special admin behavior to avoid function clashes. |
| UUPS Proxy | Projects that want a leaner proxy and are comfortable putting upgrade rules in the implementation. | The implementation includes upgrade functions and an authorization hook such as _authorizeUpgrade. | A bad implementation can accidentally remove or weaken upgrade safety. |
| Beacon Proxy | Many proxies that should all point to the same implementation, such as many instances of similar contracts. | A beacon contract stores the implementation address used by many proxies. | One beacon upgrade affects every proxy connected to that beacon. |
| Diamond / EIP-2535 style | Large modular systems with many facets of functionality. | A diamond proxy routes function selectors to different facet contracts. | More complex to audit and manage; not necessary for most beginner projects. |
4.1 Transparent Proxy Explained
A Transparent Proxy separates ordinary user calls from admin calls. If the caller is the proxy admin, the proxy handles admin functions such as upgrades. If the caller is anyone else, the proxy forwards the call to the implementation. This avoids confusing cases where a proxy function and an implementation function have the same selector.
Transparent proxies are often easier for beginners to reason about because upgrade authority is clearly separated. The downside is that the proxy carries more upgrade logic, which usually means higher deployment cost than UUPS.
4.2 UUPS Proxy Explained
UUPS stands for Universal Upgradeable Proxy Standard. In this design, the proxy is minimal and the implementation contract contains the upgrade function. In OpenZeppelin-style UUPS contracts, the implementation usually inherits UUPSUpgradeable and overrides _authorizeUpgrade to decide who can upgrade.
UUPS can be efficient, but it places more responsibility on the implementation. Every new implementation must preserve the upgrade mechanism and must not accidentally make upgrades public, impossible or unsafe.
4.3 Beacon Proxy Explained
A Beacon Proxy does not directly store the implementation address. Instead, it asks a beacon contract for the current implementation. This is useful when a protocol deploys many proxy instances and wants to upgrade all of them together. For example, a platform might create one proxy per user vault, and all vaults follow the implementation stored in the same beacon.
5. A Beginner Example: Upgradeable Counter Contract
The following simplified example shows the shape of an upgradeable contract. It uses OpenZeppelin-style upgradeable patterns. In real projects, use the current OpenZeppelin packages and deployment plugins rather than hand-rolling proxy logic.
5.1 Version 1: Basic Counter
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
contract CounterV1 is Initializable, UUPSUpgradeable, OwnableUpgradeable {
uint256 public count;
function initialize(address initialOwner) public initializer {
__Ownable_init(initialOwner);
__UUPSUpgradeable_init();
count = 0;
}
function increment() external {
count += 1;
}
function _authorizeUpgrade(address newImplementation)
internal
override
onlyOwner
{}
}
Notice the initializer. Upgradeable contracts usually do not use constructors for proxy state setup. The initialize function performs the setup that a constructor would normally do, and the initializer modifier prevents it from being called repeatedly.
5.2 Version 2: Adding a New Function Safely
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "./CounterV1.sol";
contract CounterV2 is CounterV1 {
function decrement() external {
require(count > 0, "Counter is already zero");
count -= 1;
}
}
This upgrade is safe from a storage-layout perspective because it does not reorder, remove or change existing state variables. It only adds behavior. If Version 2 needed a new state variable, it should be added after the existing variables, not before them.
5.3 Version 2 with a New State Variable
contract CounterV2 is CounterV1 {
uint256 public maxCount; // added after existing variables
function setMaxCount(uint256 newMax) external onlyOwner {
maxCount = newMax;
}
function increment() external {
require(maxCount == 0 || count < maxCount, "Max reached");
count += 1;
}
}
The main rule is append-only storage. Existing variables must keep the same order and type, because the proxy storage already contains live data at specific slots.
6. The Most Important Rule: Preserve Storage Layout
Storage layout is the biggest technical risk in upgradeable smart contracts. The EVM stores contract state in numbered storage slots. If Version 1 stores owner in slot 0 and balance in slot 1, Version 2 must not suddenly put another variable in slot 0 or change the type of the variable already there.
| Change in new implementation | Safe? | Why |
|---|---|---|
| Add a new variable at the end | Usually yes | Existing storage slots keep the same meaning. |
| Change uint256 count to address count | No | The same slot is interpreted as a different type. |
| Insert a new variable before old variables | No | Every later variable may shift to a different slot. |
| Delete a variable from the middle | No | Later variables shift and old data remains in storage. |
| Rename a variable without changing type/order | Usually yes technically | The slot is the same, but the new name may mislead developers if meaning changed. |
| Change inheritance order | Dangerous | Parent contract variables can be laid out differently. |
| Add variables to a base contract used by children | Dangerous without gaps or namespaced storage | Child variables may collide with newly added base variables. |
6.1 Storage Gaps and Namespaced Storage
Storage gaps reserve empty slots for future variables in base contracts. A common pattern is uint256[49] private __gap;. Later, if the base contract adds one uint256 variable, the gap can be reduced to 48. This protects child contract storage from being pushed into different slots.
Newer upgradeable-contract designs may also use namespaced storage, where variables are grouped inside structs with a unique storage namespace. OpenZeppelin Contracts Upgradeable version 5 uses the ERC-7201 namespaced storage convention in its upgradeable variants. This helps reduce some inheritance-related layout risks, but it does not remove the need for validation, tests and careful upgrades.
7. Initializers, Reinitializers and Constructors
A normal Solidity constructor runs when the implementation contract is deployed. But users interact with the proxy, and the proxy’s storage is not initialized by the implementation constructor. For this reason, upgradeable contracts use initializer functions.
- Use initialize instead of a constructor for proxy state setup.
- Protect initialize with an initializer modifier so it can run only once.
- Call parent initializers manually and in the correct order.
- Disable initializers on the implementation contract so attackers cannot take over the implementation directly.
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
That constructor locks the implementation contract itself. It does not initialize the proxy; the proxy still needs initialize to be called during deployment or upgrade setup.
8. Practical Deployment Workflow
A safe upgradeable-contract workflow is more than writing Solidity. It includes local testing, storage-layout validation, governance review, deployment discipline and monitoring.
- Write Version 1 using upgradeable libraries, initializers and explicit access control.
- Add tests for normal behavior, access control, initialization and upgrade authorization.
- Deploy to a local fork or testnet using a trusted upgrades plugin or deployment tool.
- Verify the proxy, implementation and admin or governance contracts on a block explorer.
- Before every upgrade, compare storage layouts and run automated upgrade validation.
- Deploy the new implementation first, then execute the upgrade through the authorized admin, multisig, timelock or DAO.
- After upgrade, run post-upgrade checks: read key state variables, test important calls, check events and monitor errors.
8.1 Example Upgrade Checklist
| Checkpoint | Question to answer before upgrading |
|---|---|
| Business reason | What bug, feature or security issue requires this upgrade? |
| Storage safety | Did we only append storage or use a validated namespaced storage pattern? |
| Initializer safety | Does the new version require a reinitializer, and can it be called only once? |
| Access control | Who can upgrade, pause, mint, withdraw or change fees after this version? |
| Testing | Do unit tests, fork tests and upgrade tests pass? |
| Audit/review | Has another engineer or external auditor reviewed the diff? |
| User transparency | Will users, integrators or governance voters receive clear notice? |
| Rollback plan | If something goes wrong, can we pause, upgrade again or otherwise limit damage? |
9. Security Risks of Upgradeable Smart Contracts
Upgradeable contracts add a new class of risk because the system can change after users have deposited assets or integrated with it. Some risks are technical, while others are governance and trust risks.
9.1 Storage Collision
A storage collision happens when a new implementation reads or writes the wrong storage slot. This can corrupt balances, owners, allowances, configuration and accounting data. Prevent this by using automated storage-layout checks and by treating state variable changes as high-risk changes.
9.2 Uninitialized Proxy or Implementation
If initialize is not called on the proxy, an attacker may call it first and become owner. If the implementation contract is left initializable, attackers may take over the implementation and create unexpected risks. Always initialize the proxy during deployment and lock the implementation.
9.3 Weak Upgrade Authorization
An upgrade function protected by onlyOwner is only as secure as the owner. A single externally owned account is usually too risky for important contracts. Use a multisig, timelock, DAO governance, role-based access control or another appropriate mechanism. For high-value contracts, separate emergency pause powers from upgrade powers.
9.4 Malicious or Compromised Upgrades
Upgradeability means users must trust the upgrade process. A compromised admin key or malicious governance proposal can replace the implementation with code that steals funds or changes rules. Timelocks, public proposals, independent monitoring and clear governance processes reduce this risk.
9.5 Function Selector Clashes
Proxy functions and implementation functions can have clashing selectors. Transparent proxies reduce this risk by separating admin calls from user calls. UUPS designs require careful attention to upgrade functions and access control.
9.6 Breaking Integrations
Even if storage is safe, an upgrade can break dApps, bots, indexers and wallets if it changes function behavior, events, revert reasons or ABI expectations. Treat public interfaces as commitments. Avoid breaking changes unless absolutely necessary and well communicated.
9.7 Dangerous Opcodes and Patterns
Upgradeable implementation contracts should avoid unsafe low-level operations such as selfdestruct and unrestricted delegatecall. A destroyed or compromised implementation can break every proxy that delegates to it. Keep low-level code minimal and heavily reviewed.
10. Upgradeable vs Non-Upgradeable Contracts
| Factor | Upgradeable contract | Non-upgradeable contract |
|---|---|---|
| Bug fixes | Can patch by upgrading implementation if governance allows. | Cannot change deployed code; may need migration to a new contract. |
| User trust | Users must trust the upgrade authority and process. | Users can rely more strongly on fixed bytecode. |
| Complexity | Higher: proxy, storage layout, initializers, admin controls. | Lower: one deployed contract with normal constructor. |
| Gas/deployment | Usually more expensive and each call may include proxy overhead. | Usually simpler and cheaper to interact with. |
| Governance burden | Requires upgrade policy, monitoring and key management. | Less ongoing governance for code changes. |
| Best for | Evolving protocols, large systems, contracts likely to need maintenance. | Simple, final, trust-minimized contracts and immutable primitives. |
11. When Should You Use Upgradeable Smart Contracts?
Use upgradeability only when the benefits clearly outweigh the added risk. For many beginner projects, a non-upgradeable contract is simpler and safer. For protocols holding significant value or expected to evolve, upgradeability may be practical if governance is mature.
11.1 Good Use Cases
- DeFi protocols that may need emergency patches or risk-parameter changes.
- Applications with long roadmaps and expected feature additions.
- Large NFT or token systems with marketplace, metadata or compliance integrations that may evolve.
- Factory systems where many instances need consistent upgrades through a beacon.
- Protocols governed by a transparent DAO, multisig and timelock process.
11.2 Cases Where Upgradeability May Be a Bad Fit
- Simple contracts with fixed behavior, such as a basic escrow or one-off sale contract.
- Projects that market themselves as fully immutable or trustless.
- Teams without enough testing, auditing and operational security capacity.
- Contracts where users would not accept an admin with power to change rules later.
12. Best Practices for Upgradeable Smart Contracts
- Use battle-tested libraries and tools. OpenZeppelin upgradeable contracts and upgrades plugins are common choices for EVM development.
- Never write proxy assembly from scratch unless you have deep EVM expertise and a strong reason.
- Use initializer and reinitializer modifiers correctly. Avoid public setup functions that can be called repeatedly.
- Lock the implementation contract with _disableInitializers.
- Preserve storage layout. Append variables, use gaps or namespaced storage and run automated validation.
- Use a multisig and timelock for upgrade authority whenever real funds or user trust are involved.
- Emit upgrade events and monitor them. Users and security teams should know when an implementation changes.
- Keep upgrades small. A focused bug fix is easier to review than a large rewrite.
- Test upgrades on a fork with real-like state before mainnet deployment.
- Document the upgrade policy publicly: who can upgrade, delay period, emergency powers and how users can verify changes.
- Have an emergency response plan, including pause controls if appropriate.
- Use audits for high-value systems, and make sure auditors review upgrade logic, storage layout and governance controls, not just business logic.
13. Common Mistakes Beginners Make
| Mistake | Why it is dangerous | Better approach |
|---|---|---|
| Using a constructor for setup | The proxy storage will not be initialized by the implementation constructor. | Use initialize with initializer. |
| Forgetting to initialize the proxy | Anyone may be able to initialize and become owner. | Initialize atomically during deployment. |
| Changing state variable order | Live storage becomes corrupted. | Only append storage or use validated namespaced storage. |
| Using normal OpenZeppelin contracts instead of upgradeable variants | Normal contracts may rely on constructors. | Use @openzeppelin/contracts-upgradeable where needed. |
| Making upgradeTo public without authorization | Anyone could replace the implementation. | Use strict _authorizeUpgrade or proxy admin controls. |
| Using a single hot wallet as admin | One compromised key can compromise the contract. | Use multisig, timelock and hardware wallets. |
| Skipping post-upgrade checks | A bad upgrade may go unnoticed until users are harmed. | Run smoke tests and monitoring immediately after upgrade. |
14. Testing Upgradeable Smart Contracts
Testing upgradeability means testing both the contract behavior and the upgrade process. You want to know that Version 1 works, Version 2 works, the upgrade is authorized, existing state survives and unauthorized users cannot upgrade.
14.1 Important Test Categories
- Initialization tests: initialize can run once, sets all required values and cannot be called again.
- Authorization tests: only the correct admin, multisig or governance executor can upgrade.
- Storage persistence tests: balances, owners, mappings and configuration remain correct after upgrade.
- Reinitializer tests: new variables added in Version 2 are initialized once and cannot be reset by attackers.
- Negative tests: invalid upgrades, unauthorized upgrades and incompatible storage layouts fail.
- Fork tests: simulate the upgrade using real deployed state on a fork before mainnet execution.
14.2 Simple Upgrade Test Idea
- Deploy CounterV1 behind a proxy.
- Call increment() three times.
- Confirm count == 3.
- Upgrade proxy to CounterV2.
- Confirm count is still 3.
- Call decrement().
- Confirm count == 2.
- Try upgrading from a non-owner account and confirm it reverts.
15. Governance and User Trust
The hardest question is not only “Can the contract be upgraded?” but “Who is allowed to upgrade it, and under what conditions?” For a serious protocol, users need to understand the upgrade authority before they deposit funds. A project that can upgrade instantly from one private wallet is very different from a project that requires a public DAO vote and a 48-hour timelock.
| Governance model | Pros | Cons |
|---|---|---|
| Single owner wallet | Simple and fast for prototypes. | Too risky for important contracts; one key controls upgrades. |
| Multisig | Several signers must approve, reducing single-key risk. | Still depends on signer security and coordination. |
| Timelock | Users get notice before upgrades execute. | Emergency fixes are slower unless there is a carefully limited emergency path. |
| DAO governance | More transparent and community-driven. | Can be slow, complex and vulnerable to governance attacks. |
| Immutable after finalization | Can start upgradeable and later renounce or remove upgrades. | Finalization removes the ability to patch future bugs. |
16. Real-World Scenario: Upgrading a Token Contract
Imagine a project launches an ERC-20 token with staking rewards. In Version 1, users can stake tokens and claim rewards. After launch, the team discovers that the reward calculation rounds down too aggressively for small users. A non-upgradeable contract might require a migration: deploy a new staking contract, ask users to unstake, move tokens and update all frontends. An upgradeable contract can patch the reward calculation while keeping the same staking address and state.
However, this same upgrade power could be abused. A malicious Version 2 could add a hidden withdrawal function, change reward rules unfairly or block some users. That is why the project should use transparent governance, code review, timelocks and public verification for every implementation.
17. Practical Best-Practice Architecture
Recommended baseline for a valuable upgradeable protocol:
- Proxy pattern: Transparent or UUPS using trusted libraries
- Upgrade authority: Multisig as proposer/executor, preferably with a timelock
- Emergency control: Limited pause role, separated from upgrade role where possible
- Deployment: Reproducible scripts, verified source code, documented addresses
- Testing: Unit tests + upgrade tests + storage validation + fork simulation
- Review: Internal review + external audit for high-value contracts
- Monitoring: Alerts for Upgraded events, admin changes and unusual calls
- Documentation: Public upgrade policy and user-facing risk disclosure
18. Myths and Misconceptions
| Misconception | Reality |
|---|---|
| Upgradeable contracts are not really immutable. | The deployed proxy and implementation bytecode are immutable, but the proxy can be pointed to a new implementation. |
| Upgradeability is always safer. | It can fix bugs, but it also creates admin, governance and storage-layout risks. |
| Only the Solidity code matters. | Deployment scripts, proxy admin ownership, timelocks and monitoring are equally important. |
| A storage-layout checker replaces audits. | Validation helps catch common mistakes, but it cannot prove business logic is safe. |
| Users do not care about upgrade authority. | Sophisticated users, auditors and integrators often treat upgrade authority as a core trust assumption. |
19. FAQ: Upgradeable Smart Contracts
19.1 Are smart contracts upgradeable by default?
No. A normal deployed smart contract cannot have its code edited. Upgradeability must be designed into the architecture, usually with a proxy pattern.
19.2 Does upgrading change the contract address?
Usually no. Users keep interacting with the same proxy address. The implementation address behind the proxy changes.
19.3 Where is the data stored in an upgradeable contract?
The important application state is stored in the proxy. The implementation contains logic, and delegatecall makes that logic operate on the proxy storage.
19.4 Can an upgradeable smart contract become non-upgradeable later?
Yes, some designs can transfer upgrade authority to a burn address, renounce ownership or upgrade to an implementation without upgrade logic. This should be done carefully because it may prevent future bug fixes.
19.5 Is UUPS better than Transparent Proxy?
Not always. UUPS is leaner and often cheaper to deploy, but it requires careful implementation-level upgrade authorization. Transparent proxies can be easier to reason about for many teams.
19.6 What is the biggest technical risk?
Storage layout corruption is one of the biggest risks. Reordering, deleting or changing state variables can make the new implementation read old data incorrectly.
19.7 Should beginners use upgradeable contracts?
Beginners should learn the concept, but avoid using upgradeability for real funds until they understand proxies, initializers, storage layout, access control, testing and governance.
19.8 Do upgradeable contracts need audits?
High-value upgradeable systems should be audited. The audit should include proxy architecture, upgrade authorization, storage layout, deployment scripts and governance process.
19.9 Can users verify an upgrade?
Yes. Users can inspect the proxy address, implementation address, verified source code, emitted upgrade events and governance transaction history on block explorers or monitoring tools.
19.10 What is an implementation contract?
It is the contract that contains the logic executed by the proxy. A new implementation is deployed when the system is upgraded.
20. Final Takeaway
Upgradeable smart contracts are useful when a project genuinely needs maintainability, but they are not automatically better than immutable contracts. They replace one kind of risk, permanent bugs in fixed bytecode, with another kind of risk, powerful upgrade authority and complex storage safety requirements. A good upgradeable-contract system uses trusted proxy patterns, safe initializers, storage-layout validation, strong governance, careful testing, public documentation and continuous monitoring.
For beginners, the safest mental model is simple: the proxy is the permanent address and storage; the implementation is replaceable logic. If you protect the upgrade process and preserve storage layout, upgradeability can be a practical tool. If you treat it casually, it can become one of the most dangerous parts of your protocol.
Sources Consulted and Checked
These sources were consulted and checked while preparing this document to support accuracy and technical reliability.
- OpenZeppelin Upgrades Plugins: Writing Upgradeable Contracts
- OpenZeppelin Upgrades Plugins: Proxy Upgrade Pattern
- OpenZeppelin Contracts Proxy API
- ERC-1822: Universal Upgradeable Proxy Standard
- Solidity Documentation: Security Considerations and Known Bugs
- Demystifying the Characteristics for Smart Contract Upgrades, arXiv 2024
- A Large-Scale Exploratory Study on the Proxy Pattern in Ethereum, arXiv 2025
Reader Advice
This article is provided for educational and informational purposes only. It explains general smart-contract concepts and does not constitute personalized legal, financial, investment, cybersecurity, or technical advice or a recommendation to deploy, upgrade, or interact with any contract. Smart contracts and blockchain systems can involve coding errors, governance failures, security exploits, loss of assets, irreversible transactions, and changing regulatory obligations. Laws, rules, policies, technical standards, software versions, and statistics may change over time and vary by jurisdiction, so readers should verify current information through official documentation and qualified professionals, carefully test and audit any implementation, and assess their own risks before making decisions.