Skip to main content
Ethereum & smart contracts

Upgradeable Smart Contracts

Pomegra Learn

Upgradeable Smart Contracts

Smart contracts are immutable—once deployed, their code cannot change. However, this immutability conflicts with software development realities: bugs need fixing, features need adding, and code requires optimization. Upgradeable contracts provide a compromise, allowing code to change while maintaining storage and address continuity.

The Immutability Problem

Immutability is a core Ethereum feature, providing security and preventing unauthorized changes. Once code is deployed, users can trust it will never change. However, this prevents legitimate improvements:

  • Security patches for discovered vulnerabilities
  • Performance optimizations
  • New features responding to user needs
  • Bug fixes for logic errors

Many contracts face choices between immutability (inability to fix problems) and upgradability (reduced trust). Thoughtful design patterns enable upgrades while maintaining acceptable security.

The Proxy Pattern

The proxy pattern solves the immutability problem through indirection. Instead of users interacting with contracts directly, they interact with proxy contracts that delegate calls to implementation contracts.

Basic Proxy Architecture

When a user calls a function on a proxy, the proxy forwards the call to an implementation contract. The implementation contract executes the code. The proxy handles storage, maintaining user balances and contract state. The implementation handles logic, executing functions.

This separation means implementation contracts can change while the proxy address remains constant. Users always call the same proxy address, but the underlying logic can evolve.

Delegatecall Mechanism

Proxies use the delegatecall opcode, which executes code from one contract in the context of another. When the proxy delegatecalls the implementation, the implementation code runs with the proxy's storage. Changes to storage are stored in the proxy, not the implementation.

This subtle but critical distinction makes upgrades possible. Deploying new implementation contracts and updating the proxy's reference enables code changes without touching stored data.

Upgrade Patterns

Transparent Proxy Pattern

The transparent proxy pattern (pioneered by OpenZeppelin) uses a proxy that intelligently routes calls:

  • Calls from the proxy admin go to proxy management functions (upgrading implementation)
  • Calls from anyone else are delegatecalled to the implementation

This prevents function selector conflicts where the implementation has a function with the same name as proxy functions.

The transparent proxy pattern is simple and secure. A single admin address controls upgrades. This centralization of control is a security trade-off some projects accept for simplicity.

UUPS Pattern

Universal Upgradeable Proxy Standard (UUPS) places upgrade logic in the implementation contract rather than the proxy. The proxy is minimal, with the implementation specifying how upgrades occur.

UUPS reduces proxy complexity and gas costs. However, it requires careful handling. If the implementation is missing upgrade functions, the contract becomes stuck and cannot be upgraded.

UUPS offers more flexibility than transparent proxies. Different implementations can have different upgrade authorization mechanisms. Some might allow community voting on upgrades; others might require multi-signature approval.

Diamond Pattern

The diamond pattern uses facets—separate contracts each implementing specific functionality. A central diamond contract delegates to appropriate facets based on function selectors.

This pattern enables modular contracts where different developers can maintain separate facets. Upgrades involve replacing facets without changing the diamond.

Diamond contracts are more complex but allow unprecedented modularity. Large protocols like Aave use diamond patterns for their intricate ecosystems.

Governance and Upgrade Control

Upgrade mechanisms define authorization—who can trigger upgrades and what process they follow.

Single Admin

The simplest approach grants a single address (usually the deploying developer) permission to upgrade. This is fast and simple but centralized. Users trust the admin not to deploy malicious code.

Many protocols use single admins initially, with intentions to decentralize later as the protocol matures.

Multisignature Control

Multisig contracts require multiple authorized signers (often 2-of-3 or 3-of-5) to approve upgrades. This distributes control, preventing any single person from unilaterally deploying malicious code.

Multisig addresses are often held by team members, governance representatives, or security professionals. This approach balances authority with security.

Governance Voting

Some protocols allow token holders to vote on upgrades. Governance tokens represent voting power, with token holders proposing and voting on changes.

Governance voting is democratic but slow. Voting periods might last a week or more. Emergency governance processes exist for critical security issues.

Timelock Mechanisms

Timelocks delay upgrades after authorization. If the community votes to upgrade, the upgrade doesn't activate immediately. Instead, it activates after a delay (typically 24 to 48 hours).

Timelocks provide windows to cancel upgrades if security concerns emerge. If discovered that a proposed upgrade is malicious, governance can cancel before activation.

Implementation Contracts and Storage

Upgradeable contracts must carefully manage storage to avoid conflicts when upgrading.

Storage Layout Consistency

Implementation contracts must maintain storage layout consistency across upgrades. If version 1 declares uint256 balance, version 2 must preserve this declaration in the same position.

Adding new storage variables should happen at the end. If developers remove variables or change their order, storage pointers break. Stored data becomes misaligned with what code expects.

The storage layout must be:

  1. Version 1 declares balance (storage slot 0)
  2. Version 1 declares owner (storage slot 1)
  3. Version 2 preserves balance and owner
  4. Version 2 adds newVariable (storage slot 2)

Violating this pattern causes catastrophic failures where stored data is interpreted by wrong code.

Initialization Functions

Upgradeable contracts use initialization functions instead of constructors. Constructors run once during deployment; subsequent implementations ignore them.

Upgradeable contracts define initialize() functions, called once by users or governance to set initial state. These functions use initialization flags to ensure they're only called once, preventing re-initialization attacks.

bool private initialized;

function initialize(address owner) public {
require(!initialized, "Already initialized");
initialized = true;
// Set initial state
}

Upgrade Strategies and Safety

Careful Planning

Successful upgrades require comprehensive planning. Tests should validate that both old and new implementations correctly handle existing data. Migration functions might be needed to transform stored data for new implementations.

The process should be:

  1. Develop new implementation thoroughly
  2. Test with real stored data from production
  3. Deploy new implementation without activating
  4. Announce plans and allow community review
  5. Activate upgrade through governance
  6. Monitor for issues

Immutable Fallbacks

Some protocols make certain critical functions immutable, preventing even governance from changing them. This provides security guarantees—users can trust these functions will never change.

Immutable fallbacks require careful selection. Core security functionality should be immutable; non-critical features can be upgradeable.

Redundancy and Rollback

Some protocols maintain multiple implementations, allowing quick rollback if issues emerge. If the new implementation is problematic, governance can switch back to the previous version.

This requires careful storage management—the previous implementation must still be compatible with the current storage state.

Verification and Governance Transparency

Upgradeable contracts' governance should be transparent. Proxy contracts should clearly show:

  • The current implementation address
  • The upgrade authorization mechanism
  • The admin address or governance contract

Etherscan's proxy verification feature helps here. It shows proxy architecture, upgrade history, and current implementation details. Users can review who has upgrade authority and what processes govern changes.

For governance-controlled contracts, voting history should be publicly accessible. Users should see what upgrades were proposed, who voted for them, and what changed.

Risks and Trade-Offs

Complexity

Proxy patterns introduce complexity. Every function call goes through delegatecall, which is slightly more gas-expensive and conceptually harder to understand. Bugs in proxy logic can be catastrophic.

Simple contracts without upgrade needs might be better off immutable.

Trust Assumptions

Upgradeable contracts require trusting the upgrade authority. Even with governance voting, users trust that voters act in their interest. A majority of governance token holders could vote for malicious upgrades.

This is why governance controls and timelocks exist—to make attacks harder and allow time for response.

Storage Bugs

Upgrade patterns are prone to storage-related bugs. Changing storage layout breaks the contract. Accidental overwrites corrupt data. These bugs are insidious—they might not surface immediately.

Careful tooling and testing helps prevent them. Some development frameworks automatically check storage compatibility across upgrades.

Real-World Examples

Aave uses the diamond pattern with complex governance. The protocol upgrades regularly, adding features and improving efficiency. Upgrades are governed by token holders, with timelocks preventing rushed changes.

Uniswap V3 used upgradeable contracts during its initial launches. As the protocol matured and proved reliable, developers moved toward less upgradeable architectures, valuing immutability.

OpenZeppelin Contracts provides widely-used upgradeability libraries. Their transparent proxy and UUPS patterns are industry standard.

The Centralization Question

Upgradeable contracts introduce tension between improvement and decentralization. Fully immutable contracts are maximally decentralized but can't improve. Fully upgradeable contracts can improve but require trusting upgrade authorities.

The cryptocurrency community has accepted this trade-off. Most significant protocols use some form of upgradeability. The question becomes: who has upgrade authority and what process governs them?

Governance voting decentralizes this decision but is imperfect. Voter apathy, whale dominance, and voting fatigue create challenges. Some protocols intentionally move toward immutability once they mature, valuing stability over continued upgradability.

Future Directions

As smart contracts mature, patterns are evolving:

Immutable core layers: Protocols increasingly separate core security logic (immutable) from peripheral features (upgradeable). This provides immutability's assurance for critical functions while allowing non-critical improvements.

Formal verification: As contracts become more complex, formal verification (mathematically proving code properties) prevents bugs that require fixes.

Decentralized governance: Protocols continue improving governance mechanisms, making upgrade decisions more truly decentralized.

On-chain standards: EIPs (Ethereum Improvement Proposals) are establishing standardized upgrade patterns, improving security and compatibility.

Conclusion

Upgradeable contracts are a pragmatic solution to the immutability problem. They allow smart contracts to improve and fix bugs while maintaining persistent addresses and stored data. The proxy pattern has become industry standard, with governance and security mechanisms ensuring upgrades serve user interests.

However, upgradeability isn't free. It introduces complexity, requires trust in upgrade authorities, and enables attacks if governance is compromised. Most successful protocols carefully balance upgradeability (for needed improvements) and immutability (for security guarantees).

Understanding proxy patterns, storage considerations, and governance mechanisms is essential for building and evaluating upgradeable contracts. The choice between immutable and upgradeable architectures depends on use case, community maturity, and risk tolerance.


Related Articles:

External Sources: