Back to Blog
Smart ContractsSolidityWeb3

Smart Contract Upgradability: Security Trade-offs You Need to Know

February 11, 20265 min readRedVolt Team

Smart contracts are supposed to be immutable. That's a feature — code that can't be changed can't be tampered with. But it's also a problem — code that can't be changed can't be fixed.

Upgradeable contracts solve the fix problem, but they reintroduce trust assumptions that immutability was meant to eliminate. Here's how to navigate the trade-offs.

The Upgrade Paradox

Immutable Contracts

  • Can't fix bugs after deployment
  • Users trust the deployed code forever
  • No admin key risk
  • Full decentralization from day one

Upgradeable Contracts

  • Can fix bugs and add features
  • Users must trust the upgrade authority
  • Admin key compromise = protocol compromise
  • Centralization until ownership is renounced or transferred to governance

ℹ️The Reality

Most production DeFi protocols use upgradeable contracts — at least during their early phases. The ability to fix critical bugs outweighs the trust trade-off, as long as the upgrade mechanism is properly secured. The key is minimizing the trust assumptions and having a clear path to immutability.

The Upgrade Patterns

Transparent Proxy Pattern

Transparent Proxy

How it works

Users interact with a Proxy contract that delegates all calls to an Implementation contract. Upgrading means pointing the proxy to a new implementation. Storage lives in the proxy; logic lives in the implementation.

The admin problem

The proxy admin can change the implementation address — pointing it to malicious code. The admin has god-mode access to the protocol.

Transparent vs non-transparent

Transparent proxies route admin calls to the proxy itself and user calls to the implementation. This prevents function selector clashes but adds gas overhead.

UUPS (Universal Upgradeable Proxy Standard)

UUPS Pattern

How it works

Similar to transparent proxy, but the upgrade logic lives in the implementation contract instead of the proxy. The implementation includes an upgradeToAndCall() function.

Advantage

The upgrade mechanism can be removed in a future upgrade — providing a path to true immutability. Also cheaper gas for users since there's no admin routing overhead.

Risk

If you deploy an implementation without the upgrade function, or with a bug in it, you can permanently brick the proxy — losing all funds and state.

Beacon Proxy Pattern

Used when multiple proxies share the same implementation (e.g., many instances of the same vault contract):

  • All proxies read their implementation address from a single Beacon contract
  • Upgrading the beacon upgrades all proxies simultaneously
  • Useful for factory patterns with many clones

Diamond Pattern (EIP-2535)

Multiple implementation contracts (facets) behind a single proxy. Each function selector routes to a different facet:

  • Maximum modularity — upgrade individual features without touching others
  • Complex — more code means more attack surface
  • Storage management is tricky — shared storage across facets requires careful layout

The Vulnerabilities

1. Uninitialized Implementation

critical

The most common upgrade-related vulnerability. We see it in 25% of audits involving upgradeable contracts:

1

Team deploys proxy + implementation

The proxy is initialized correctly via initialize(), but the implementation contract itself isn't locked

2

Attacker calls initialize() on implementation directly

The implementation contract accepts the call and the attacker becomes the owner

3

Attacker self-destructs or upgrades

The attacker calls selfdestruct() (pre-Dencun) or upgrades to malicious code. Since the proxy delegates to this address, the entire protocol is compromised.

🛑The Fix Is One Line

Add _disableInitializers() in the implementation's constructor. This prevents anyone from calling initialize() on the implementation directly. It's one line of code that prevents a critical vulnerability.

This was the root cause of several major exploits, including the Wormhole hack we analyzed in our cross-chain bridge vulnerabilities article.

2. Storage Collision

critical

When upgrading, the new implementation must maintain the same storage layout as the old one:

  • Never remove or reorder existing storage variables
  • Only append new variables at the end
  • Be careful with inheritance order changes — they affect storage layout
  • Use OpenZeppelin's storage gap pattern (uint256[50] private __gap) to reserve space for future variables

3. Function Selector Clashing

high

The proxy routes calls based on the 4-byte function selector. If the proxy's admin functions have the same selector as an implementation function, calls may be misrouted.

4. Upgrade Authority Compromise

critical

If the upgrade authority (owner, multisig, governance) is compromised, the attacker can upgrade to an implementation that drains all funds:

Weak Upgrade Authority

  • Single EOA can upgrade immediately
  • No timelock on upgrades
  • Upgrade authority on hot wallet
  • No community visibility into pending upgrades

Strong Upgrade Authority

  • Multisig (3-of-5 minimum) required for upgrade
  • 48-hour timelock on all upgrades
  • Keys on hardware wallets, geographically distributed
  • Upgrades announced publicly with diff review period

The Upgrade Security Checklist

01

Initialize

_disableInitializers() in implementation constructor. Call initialize() on proxy immediately after deployment.

02

Storage

Never reorder storage. Use gaps. Test layout compatibility between versions.

03

Authority

Multisig + timelock. No single key can upgrade. Hardware wallets for all signers.

04

Immutability Path

Plan to renounce upgrade authority once the protocol is battle-tested.

As we detailed in our Solidity security patterns handbook and smart contract audit checklist, secure upgradeability is a multi-layered concern that touches code, governance, and operational security.

How We Audit Upgradeable Contracts

Our Smart Contract Auditor specifically checks:

  1. Implementation initialization — Is the implementation locked against re-initialization?
  2. Storage layout — Do upgrades maintain storage compatibility?
  3. Upgrade authority — Who can upgrade, and what controls exist?
  4. Timelock and governance — Are upgrades subject to community review?
  5. Selector clashes — Are there function selector conflicts between proxy and implementation?
  6. Path to immutability — Is there a plan to eventually remove upgrade capability?

Building with upgradeable contracts? Our Smart Contract Auditor catches initialization bugs, storage collisions, and authority issues automatically. For complex upgrade patterns (Diamond, multi-proxy), our expert review provides deep architectural analysis.

Want to secure your application or smart contract?

Request an Expert Review