The Signal
THE SIGNAL

Where Web3 founders, talent, and partners meet.

Daily Digest · Free
PLATFORM
  • Partners Directory
  • All Categories
  • Marketplace
  • Find a Partner
  • Docs
  • Escrow
INTELLIGENCE
  • Web3 News
  • Daily Digests
  • Intel Reports
  • Web3 Events
  • RSS Feed
  • Substack ↗
GET INVOLVED
  • Get Listed
  • Get Your Verified Badge
  • Submit an Event
  • Become an Operative
  • Refer a Client
  • Book a Call
COMPANY
  • About
  • How It Works
  • Manifesto
  • Media Kit
  • Privacy
  • Terms
© 2026 THE SIGNAL · All rights reserved.Operated by Nomdon Tech Ltd · No. 15462747 · England
PRIVACYTERMSCOOKIES
THE SIGNAL
The Signal
THE SIGNAL

Where Web3 founders, talent, and partners meet.

Daily Digest · Free
PLATFORM
  • Partners Directory
  • All Categories
  • Marketplace
  • Find a Partner
  • Docs
  • Escrow
INTELLIGENCE
  • Web3 News
  • Daily Digests
  • Intel Reports
  • Web3 Events
  • RSS Feed
  • Substack ↗
GET INVOLVED
  • Get Listed
  • Get Your Verified Badge
  • Submit an Event
  • Become an Operative
  • Refer a Client
  • Book a Call
COMPANY
  • About
  • How It Works
  • Manifesto
  • Media Kit
  • Privacy
  • Terms
© 2026 THE SIGNAL · All rights reserved.Operated by Nomdon Tech Ltd · No. 15462747 · England
PRIVACYTERMSCOOKIES
THE SIGNAL
Home/Intelligence/Smart Contract Upgradeability Patterns: Proxy, Diamond, and Beacon Explained

Smart Contract Upgradeability Patterns: Proxy, Diamond, and Beacon Explained

Immutable code is the ideal, but production smart contracts need upgrade paths. This guide compares every major upgradeability pattern — Transparent Proxy, UUPS, Diamond, and Beacon — with storage layout risks, gas benchmarks, governance hooks, and real-world decision criteria.

THE SIGNAL
Published by
THE SIGNAL Editorial Team
April 3, 2026|Updated Apr 30, 2026
|30 min read
smart contract upgradeabilitydevelopmentsecurity

Key Takeaways

  • Why Upgradeability Matters
  • The Foundation: delegatecall and Proxy Contracts
  • Pattern 1: Transparent Proxy (OpenZeppelin)
  • Pattern 2: UUPS (Universal Upgradeable Proxy Standard)
  • Pattern 3: Diamond Standard (EIP-2535)

Smart Contract Upgradeability Patterns: Proxy, Diamond, and Beacon Explained

Smart contracts are often described as immutable — "code is law." In practice, over 60% of production contracts on Ethereum mainnet use some form of upgradeability. Bugs happen, business logic evolves, and regulatory requirements change. The question is not whether you need an upgrade path, but which pattern minimizes risk while preserving the trust guarantees that make blockchains valuable.

This guide walks through every major smart contract upgradeability pattern in 2026, compares their trade-offs, and helps you choose the right one for your project.

Why Upgradeability Matters

Deploying an immutable contract sounds clean — until you discover a critical vulnerability after launch. The history of DeFi is littered with examples:

  • •The DAO hack (2016): $60M exploit in an immutable contract, requiring an Ethereum hard fork
  • •Wormhole bridge (2022): $320M stolen via an unpatched proxy implementation bug
  • •Euler Finance (2023): $197M flash loan exploit that required months of negotiation instead of a simple patch

Upgradeability gives teams the ability to fix vulnerabilities, add features, and adapt to changing requirements — but it also introduces centralization risk and new attack surfaces. Every pattern represents a different point on the security vs. flexibility spectrum.

The Foundation: delegatecall and Proxy Contracts

All proxy-based upgrade patterns rely on Solidity's delegatecall opcode. Understanding this mechanism is essential before evaluating any pattern.

How delegatecall Works

When Contract A uses delegatecall to call Contract B, the code from Contract B executes in the context of Contract A — meaning it reads and writes Contract A's storage, uses Contract A's msg.sender and msg.value, and returns results to Contract A's caller.

// Simplified proxy pattern
contract Proxy {
    address public implementation;

    fallback() external payable {
        address impl = implementation;
        assembly {
            calldatacopy(0, 0, calldatasize())
            let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize())
            switch result
            case 0 { revert(0, returndatasize()) }
            default { return(0, returndatasize()) }
        }
    }
}

The proxy holds state (storage) and the implementation holds logic. Upgrading means pointing the proxy to a new implementation address. Users always interact with the proxy address, which never changes.

Pattern 1: Transparent Proxy (OpenZeppelin)

The Transparent Proxy is the most widely adopted upgrade pattern, championed by OpenZeppelin and used by hundreds of production protocols.

Architecture

Three contracts work together:

  1. •Proxy contract: Stores state and forwards calls via delegatecall
  2. •Implementation contract: Contains the business logic
  3. •ProxyAdmin contract: Manages upgrades (only the admin can call upgradeTo)

How It Solves Function Selector Clashing

The core innovation of the Transparent Proxy is its approach to function selector collisions. If both the proxy and the implementation have a function with the same selector (e.g., upgradeTo(address)), which one gets called?

The Transparent Proxy solves this by routing based on caller identity:

  • •Admin calls → routed to the proxy's own functions (upgradeTo, changeAdmin)
  • •All other calls → forwarded to the implementation via delegatecall
// Transparent Proxy routing logic (simplified)
function _fallback() internal override {
    if (msg.sender == _getAdmin()) {
        // Admin can only call proxy management functions
        // NOT forwarded to implementation
    } else {
        // Everyone else: delegatecall to implementation
        _delegate(_getImplementation());
    }
}

Pros and Cons

ProsCons
Battle-tested (thousands of deployments)Higher gas on every call (admin check)
Simple mental modelAdmin cannot interact with the dApp through the proxy
Strong tooling (OpenZeppelin Upgrades, Hardhat plugin)Extra deployment cost for ProxyAdmin
Clear separation of concernsSingle point of upgrade authority

Gas Overhead

Every transaction pays ~2,100 extra gas for the admin address storage read. For high-frequency DeFi contracts processing thousands of transactions, this adds up to $50-200/day at typical gas prices.

When to Use Transparent Proxy

  • •Standard DeFi protocols and token contracts
  • •Projects using OpenZeppelin's upgrade tooling
  • •Teams that want maximum community familiarity and audit coverage
  • •Contracts where the admin check gas cost is negligible relative to total gas usage

Pattern 2: UUPS (Universal Upgradeable Proxy Standard)

UUPS (EIP-1822) moves the upgrade logic from the proxy into the implementation contract itself. This creates a lighter proxy but shifts responsibility to the developer.

Architecture

Two contracts:

  1. •Minimal proxy: Only contains delegatecall forwarding — no admin logic, no upgradeTo
  2. •Implementation contract: Contains business logic AND the upgradeTo function
// UUPS Implementation (simplified)
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

contract MyProtocol is UUPSUpgradeable, OwnableUpgradeable {
    uint256 public value;

    function initialize(address owner) public initializer {
        __Ownable_init(owner);
        __UUPSUpgradeable_init();
    }

    function setValue(uint256 _value) external {
        value = _value;
    }

    // Required: authorization check for upgrades
    function _authorizeUpgrade(address newImplementation)
        internal override onlyOwner
    {}
}

The Critical Risk: Forgetting _authorizeUpgrade

If a developer deploys a UUPS implementation without the _authorizeUpgrade function (or removes it in a new version), the contract becomes permanently non-upgradeable. Worse, if the authorization check is missing or improperly implemented, anyone can upgrade the contract to a malicious implementation.

This is not a theoretical risk. The Wormhole bridge exploit partially stemmed from a UUPS implementation vulnerability where the implementation contract was left uninitialized.

Pros and Cons

ProsCons
Lower gas per call (no admin check)Developer must remember upgrade logic in every version
Cheaper proxy deploymentRisk of bricking if upgrade function removed
Smaller proxy bytecodeLess intuitive for new developers
Can remove upgradeability by deploying version without upgradeToRequires careful implementation inheritance

Gas Savings vs Transparent Proxy

UUPS saves approximately 2,100 gas per transaction compared to the Transparent Proxy. For a contract processing 10,000 transactions per day, this amounts to roughly $70-300/day in savings at typical 2026 gas prices.

When to Use UUPS

  • •Gas-sensitive contracts with high transaction volume
  • •Teams comfortable with the additional developer responsibility
  • •Projects that may want to permanently remove upgradeability in the future
  • •Protocols where the admin also needs to interact with the dApp

Pattern 3: Diamond Standard (EIP-2535)

The Diamond pattern takes a radically different approach: instead of one implementation contract, a Diamond can delegate to multiple implementation contracts (called "facets"), each handling different functions.

Architecture

        ┌──────────────┐
        │   Diamond     │
        │   (Proxy)     │
        │               │
        │  Selector →   │
        │  Facet Map    │
        └──┬───┬───┬────┘
           │   │   │
     ┌─────┘   │   └─────┐
     ▼         ▼         ▼
  ┌──────┐ ┌──────┐ ┌──────┐
  │Facet │ │Facet │ │Facet │
  │  A   │ │  B   │ │  C   │
  └──────┘ └──────┘ └──────┘

The Diamond maintains a mapping of function selectors to facet addresses. When a call arrives, the Diamond looks up which facet handles that selector and delegatecalls to it.

// Diamond function routing (simplified)
fallback() external payable {
    // Look up facet address for this function selector
    address facet = selectorToFacet[msg.sig];
    require(facet != address(0), "Function does not exist");

    assembly {
        calldatacopy(0, 0, calldatasize())
        let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0)
        returndatacopy(0, 0, returndatasize())
        switch result
        case 0 { revert(0, returndatasize()) }
        default { return(0, returndatasize()) }
    }
}

The diamondCut Function

Adding, replacing, or removing functions is done through a single diamondCut call that accepts an array of modifications:

struct FacetCut {
    address facetAddress;
    FacetCutAction action; // Add, Replace, Remove
    bytes4[] functionSelectors;
}

function diamondCut(
    FacetCut[] calldata _diamondCut,
    address _init,
    bytes calldata _calldata
) external;

Pros and Cons

ProsCons
No contract size limit (24KB bypassed)Complex architecture — higher audit cost
Granular upgrades (single function)Selector collision risk across facets
Single address for unlimited functionalityStorage management requires AppStorage or Diamond Storage
Atomic multi-facet upgradesSteeper learning curve for developers
Built-in introspection (EIP-2535 loupe)Fewer auditors specialize in Diamonds

Storage Management

Diamonds require careful storage management because all facets share the proxy's storage. Two patterns exist:

Diamond Storage — Each facet uses a unique storage slot based on a hash:

bytes32 constant STORAGE_POSITION = keccak256("myprotocol.facetA.storage");

struct FacetAStorage {
    uint256 value;
    mapping(address => uint256) balances;
}

function getStorage() internal pure returns (FacetAStorage storage s) {
    bytes32 position = STORAGE_POSITION;
    assembly { s.slot := position }
}

AppStorage — A single struct shared by all facets (simpler but less modular):

struct AppStorage {
    uint256 totalSupply;
    mapping(address => uint256) balances;
    // All facets read/write this struct
}

When to Use Diamond

  • •Large protocols exceeding the 24KB contract size limit
  • •Systems requiring modular, independent feature upgrades
  • •Protocols where different facets have different governance timelines
  • •Projects like Aavegotchi and Louper that need unlimited extensibility at a single address

Pattern 4: Beacon Proxy

The Beacon pattern is purpose-built for upgrading many proxy instances simultaneously. Instead of each proxy storing its own implementation address, all proxies point to a shared Beacon contract that returns the current implementation.

Architecture

  ┌──────────┐
  │  Beacon   │ ── stores implementation address
  └────┬──────┘
       │ getImplementation()
  ┌────┼────────────┐
  │    │            │
  ▼    ▼            ▼
┌────┐┌────┐    ┌────┐
│Prx1││Prx2│... │PrxN│  ← all upgraded at once
└────┘└────┘    └────┘
// Beacon Proxy (simplified)
contract BeaconProxy {
    address immutable beacon;

    fallback() external payable {
        address impl = IBeacon(beacon).implementation();
        assembly {
            calldatacopy(0, 0, calldatasize())
            let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize())
            switch result
            case 0 { revert(0, returndatasize()) }
            default { return(0, returndatasize()) }
        }
    }
}

Pros and Cons

ProsCons
Upgrade hundreds of instances in one transactionExtra gas per call (external call to beacon)
Minimal per-proxy deployment costAll instances must share the same logic
Clean factory pattern integrationCannot upgrade individual instances independently
Ideal for clone-heavy architecturesLess common — fewer audit templates

Gas Overhead

Each call to a Beacon Proxy adds an extra 2,600 gas for the external call to the Beacon contract. However, upgrades are dramatically cheaper — one transaction upgrades all instances instead of N separate upgradeTo calls.

When to Use Beacon Proxy

  • •Factory patterns deploying many identical contracts (vaults, escrows, pools)
  • •Protocols like Compound managing hundreds of cToken instances
  • •Systems where uniform behavior across all instances is required
  • •Architectures where upgrade cost scales linearly with instance count using other patterns

Pattern 5: Immutable (No Upgrade)

The most secure option is no upgradeability at all. If the contract does what it needs to do and nothing more, immutability provides the strongest trust guarantee.

When Immutable is the Right Choice

  • •Token contracts (ERC-20/721) with fixed supply logic
  • •Simple escrows with well-defined release conditions
  • •Cryptographic verifiers (signature checks, Merkle proofs)
  • •Timelock contracts where the logic must never change
  • •Any contract where a bug fix is less damaging than the centralization risk of upgradeability

The Hybrid Approach

Many production systems combine patterns. For example:

  • •Core token: Immutable ERC-20
  • •Staking logic: UUPS proxy (upgradeable by governance)
  • •Treasury: Diamond proxy (modular investment strategies)
  • •Vault factory: Beacon proxy (uniform vaults, batch upgrades)

Storage Layout: The Silent Killer

Storage collisions are the most dangerous risk across all proxy patterns. Because the proxy and implementation share storage, any change to the storage layout between versions can corrupt data.

The Rules

  1. •Never remove or reorder existing storage variables
  2. •Only append new variables at the end of the contract
  3. •Never change the type of an existing variable
  4. •Use storage gaps for future-proofing:
contract MyContractV1 {
    uint256 public value;
    address public admin;
    uint256[48] private __gap; // Reserve 48 slots for future use
}

contract MyContractV2 {
    uint256 public value;
    address public admin;
    uint256 public newField;     // Uses first gap slot
    uint256[47] private __gap;   // Gap shrinks by 1
}

Common Storage Collision Scenarios

ScenarioWhat HappensHow to Prevent
Removing a variableAll subsequent slots shift — catastrophic corruptionNever remove, only deprecate
Reordering variablesValues read from wrong slotsMaintain strict ordering
Changing uint128 to uint256Overwrites adjacent variableUse same-sized replacements only
Inheriting in different orderBase class storage shiftsLock inheritance chain order

OpenZeppelin's @openzeppelin/upgrades-core includes a storage layout checker that validates compatibility between versions. Running npx @openzeppelin/upgrades-core validate before every upgrade is non-negotiable.

Security Considerations and Audit Requirements

Per-Pattern Audit Focus Areas

PatternKey Audit Focus
Transparent ProxyAdmin key management, ProxyAdmin ownership
UUPS_authorizeUpgrade present in ALL versions, initializer protection
DiamondSelector collision across facets, storage isolation, diamondCut access control
BeaconBeacon ownership, implementation validation, factory security
ImmutableStandard vulnerability analysis (reentrancy, overflow, access control)

Governance Integration

Production upgrade patterns should integrate with governance mechanisms:

  • •Timelock: All upgrades pass through a 24-72h timelock, giving users time to exit
  • •Multisig: Require M-of-N signatures (typically 3/5 or 4/7) for upgrade execution
  • •On-chain governance: Token-weighted voting for protocol upgrades (Compound Governor, OpenZeppelin Governor)
  • •Emergency pause: Circuit breaker that freezes the contract without requiring a full upgrade
// Governance-gated upgrade pattern
function _authorizeUpgrade(address newImpl) internal override {
    require(msg.sender == address(timelock), "Only timelock");
    require(
        IGovernor(governor).proposalExecuted(currentProposalId),
        "Proposal not executed"
    );
}

Initializer Protection

All proxy patterns must protect against re-initialization attacks:

// ALWAYS use OpenZeppelin's initializer modifier
function initialize(address owner) public initializer {
    __Ownable_init(owner);
}

// ALWAYS call _disableInitializers in the constructor
constructor() {
    _disableInitializers();
}

Comparison Matrix

CriteriaTransparent ProxyUUPSDiamondBeaconImmutable
Gas per call+2,100Baseline+~200 (selector lookup)+2,600Baseline
Deploy costHigh (3 contracts)Medium (2 contracts)High (Diamond + facets)Low per instanceLowest
Upgrade cost1 tx per proxy1 tx per proxy1 tx (atomic multi-facet)1 tx for ALL instancesN/A
Max contract size24KB24KBUnlimited24KB24KB
ComplexityLowMediumHighLow-MediumLowest
Audit cost$$$$$$$$$$
Centralization riskMediumMediumMedium-HighMediumNone

Decision Framework

Use this flowchart to choose your pattern:

  1. •Can the contract be immutable? → Yes → Immutable. Always prefer immutability when possible.
  2. •Do you deploy many identical instances? → Yes → Beacon Proxy
  3. •Will the contract exceed 24KB? → Yes → Diamond (EIP-2535)
  4. •Is gas per transaction critical? → Yes → UUPS
  5. •Do you want maximum simplicity and tooling? → Yes → Transparent Proxy
  6. •Do you need modular, independent feature upgrades? → Yes → Diamond

Conclusion

Smart contract upgradeability is not a one-size-fits-all decision. Each pattern trades off gas efficiency, complexity, security surface area, and governance overhead. The Transparent Proxy remains the safe default for most projects. UUPS saves gas at the cost of developer discipline. The Diamond standard unlocks unlimited modularity for complex protocols. Beacon proxies shine when batch-upgrading fleets of identical contracts. And immutability — when achievable — remains the gold standard for trust.

Whatever pattern you choose, the non-negotiables are the same: storage layout validation on every upgrade, governance timelocks, comprehensive audit coverage, and initializer protection. Get these right, and upgradeability becomes a strength rather than a liability.


The Signal connects Web3 teams with vetted smart contract auditors and upgrade specialists. Browse our security partner directory →

PreviousNavigating the Current Web3 Investment Outlook: A Deep Dive into Funding TrendsNextSolana vs Ethereum for dApp Development: Performance, Cost, and Ecosystem Compared

Related Intelligence

How Web3 Founders Can Find Blockchain Developers and Agencies Efficiently

Jun 13, 2026

Clutch Alternatives for Finding Web3 and Blockchain Service Providers: A Practical Buyer Checklist

Jun 12, 2026

Educational Signal — Security & Auditing — 2026-06-12

Educational Signal — Security & Auditing — 2026-06-12

Jun 12, 2026

Need Web3 Consulting?

Get expert guidance from The Arch Consulting on blockchain strategy, tokenomics, and Web3 growth.

Learn More

Table of Contents

Share Article

XLI

Share Article

XLI
Home/Intelligence/Smart Contract Upgradeability Patterns: Proxy, Diamond, and Beacon Explained

Smart Contract Upgradeability Patterns: Proxy, Diamond, and Beacon Explained

Immutable code is the ideal, but production smart contracts need upgrade paths. This guide compares every major upgradeability pattern — Transparent Proxy, UUPS, Diamond, and Beacon — with storage layout risks, gas benchmarks, governance hooks, and real-world decision criteria.

THE SIGNAL
Published by
THE SIGNAL Editorial Team
April 3, 2026|Updated Apr 30, 2026
|30 min read
smart contract upgradeabilitydevelopmentsecurity

Key Takeaways

  • Why Upgradeability Matters
  • The Foundation: delegatecall and Proxy Contracts
  • Pattern 1: Transparent Proxy (OpenZeppelin)
  • Pattern 2: UUPS (Universal Upgradeable Proxy Standard)
  • Pattern 3: Diamond Standard (EIP-2535)

Smart Contract Upgradeability Patterns: Proxy, Diamond, and Beacon Explained

Smart contracts are often described as immutable — "code is law." In practice, over 60% of production contracts on Ethereum mainnet use some form of upgradeability. Bugs happen, business logic evolves, and regulatory requirements change. The question is not whether you need an upgrade path, but which pattern minimizes risk while preserving the trust guarantees that make blockchains valuable.

This guide walks through every major smart contract upgradeability pattern in 2026, compares their trade-offs, and helps you choose the right one for your project.

Why Upgradeability Matters

Deploying an immutable contract sounds clean — until you discover a critical vulnerability after launch. The history of DeFi is littered with examples:

  • •The DAO hack (2016): $60M exploit in an immutable contract, requiring an Ethereum hard fork
  • •Wormhole bridge (2022): $320M stolen via an unpatched proxy implementation bug
  • •Euler Finance (2023): $197M flash loan exploit that required months of negotiation instead of a simple patch

Upgradeability gives teams the ability to fix vulnerabilities, add features, and adapt to changing requirements — but it also introduces centralization risk and new attack surfaces. Every pattern represents a different point on the security vs. flexibility spectrum.

The Foundation: delegatecall and Proxy Contracts

All proxy-based upgrade patterns rely on Solidity's delegatecall opcode. Understanding this mechanism is essential before evaluating any pattern.

How delegatecall Works

When Contract A uses delegatecall to call Contract B, the code from Contract B executes in the context of Contract A — meaning it reads and writes Contract A's storage, uses Contract A's msg.sender and msg.value, and returns results to Contract A's caller.

// Simplified proxy pattern
contract Proxy {
    address public implementation;

    fallback() external payable {
        address impl = implementation;
        assembly {
            calldatacopy(0, 0, calldatasize())
            let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize())
            switch result
            case 0 { revert(0, returndatasize()) }
            default { return(0, returndatasize()) }
        }
    }
}

The proxy holds state (storage) and the implementation holds logic. Upgrading means pointing the proxy to a new implementation address. Users always interact with the proxy address, which never changes.

Pattern 1: Transparent Proxy (OpenZeppelin)

The Transparent Proxy is the most widely adopted upgrade pattern, championed by OpenZeppelin and used by hundreds of production protocols.

Architecture

Three contracts work together:

  1. •Proxy contract: Stores state and forwards calls via delegatecall
  2. •Implementation contract: Contains the business logic
  3. •ProxyAdmin contract: Manages upgrades (only the admin can call upgradeTo)

How It Solves Function Selector Clashing

The core innovation of the Transparent Proxy is its approach to function selector collisions. If both the proxy and the implementation have a function with the same selector (e.g., upgradeTo(address)), which one gets called?

The Transparent Proxy solves this by routing based on caller identity:

  • •Admin calls → routed to the proxy's own functions (upgradeTo, changeAdmin)
  • •All other calls → forwarded to the implementation via delegatecall
// Transparent Proxy routing logic (simplified)
function _fallback() internal override {
    if (msg.sender == _getAdmin()) {
        // Admin can only call proxy management functions
        // NOT forwarded to implementation
    } else {
        // Everyone else: delegatecall to implementation
        _delegate(_getImplementation());
    }
}

Pros and Cons

ProsCons
Battle-tested (thousands of deployments)Higher gas on every call (admin check)
Simple mental modelAdmin cannot interact with the dApp through the proxy
Strong tooling (OpenZeppelin Upgrades, Hardhat plugin)Extra deployment cost for ProxyAdmin
Clear separation of concernsSingle point of upgrade authority

Gas Overhead

Every transaction pays ~2,100 extra gas for the admin address storage read. For high-frequency DeFi contracts processing thousands of transactions, this adds up to $50-200/day at typical gas prices.

When to Use Transparent Proxy

  • •Standard DeFi protocols and token contracts
  • •Projects using OpenZeppelin's upgrade tooling
  • •Teams that want maximum community familiarity and audit coverage
  • •Contracts where the admin check gas cost is negligible relative to total gas usage

Pattern 2: UUPS (Universal Upgradeable Proxy Standard)

UUPS (EIP-1822) moves the upgrade logic from the proxy into the implementation contract itself. This creates a lighter proxy but shifts responsibility to the developer.

Architecture

Two contracts:

  1. •Minimal proxy: Only contains delegatecall forwarding — no admin logic, no upgradeTo
  2. •Implementation contract: Contains business logic AND the upgradeTo function
// UUPS Implementation (simplified)
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

contract MyProtocol is UUPSUpgradeable, OwnableUpgradeable {
    uint256 public value;

    function initialize(address owner) public initializer {
        __Ownable_init(owner);
        __UUPSUpgradeable_init();
    }

    function setValue(uint256 _value) external {
        value = _value;
    }

    // Required: authorization check for upgrades
    function _authorizeUpgrade(address newImplementation)
        internal override onlyOwner
    {}
}

The Critical Risk: Forgetting _authorizeUpgrade

If a developer deploys a UUPS implementation without the _authorizeUpgrade function (or removes it in a new version), the contract becomes permanently non-upgradeable. Worse, if the authorization check is missing or improperly implemented, anyone can upgrade the contract to a malicious implementation.

This is not a theoretical risk. The Wormhole bridge exploit partially stemmed from a UUPS implementation vulnerability where the implementation contract was left uninitialized.

Pros and Cons

ProsCons
Lower gas per call (no admin check)Developer must remember upgrade logic in every version
Cheaper proxy deploymentRisk of bricking if upgrade function removed
Smaller proxy bytecodeLess intuitive for new developers
Can remove upgradeability by deploying version without upgradeToRequires careful implementation inheritance

Gas Savings vs Transparent Proxy

UUPS saves approximately 2,100 gas per transaction compared to the Transparent Proxy. For a contract processing 10,000 transactions per day, this amounts to roughly $70-300/day in savings at typical 2026 gas prices.

When to Use UUPS

  • •Gas-sensitive contracts with high transaction volume
  • •Teams comfortable with the additional developer responsibility
  • •Projects that may want to permanently remove upgradeability in the future
  • •Protocols where the admin also needs to interact with the dApp

Pattern 3: Diamond Standard (EIP-2535)

The Diamond pattern takes a radically different approach: instead of one implementation contract, a Diamond can delegate to multiple implementation contracts (called "facets"), each handling different functions.

Architecture

        ┌──────────────┐
        │   Diamond     │
        │   (Proxy)     │
        │               │
        │  Selector →   │
        │  Facet Map    │
        └──┬───┬───┬────┘
           │   │   │
     ┌─────┘   │   └─────┐
     ▼         ▼         ▼
  ┌──────┐ ┌──────┐ ┌──────┐
  │Facet │ │Facet │ │Facet │
  │  A   │ │  B   │ │  C   │
  └──────┘ └──────┘ └──────┘

The Diamond maintains a mapping of function selectors to facet addresses. When a call arrives, the Diamond looks up which facet handles that selector and delegatecalls to it.

// Diamond function routing (simplified)
fallback() external payable {
    // Look up facet address for this function selector
    address facet = selectorToFacet[msg.sig];
    require(facet != address(0), "Function does not exist");

    assembly {
        calldatacopy(0, 0, calldatasize())
        let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0)
        returndatacopy(0, 0, returndatasize())
        switch result
        case 0 { revert(0, returndatasize()) }
        default { return(0, returndatasize()) }
    }
}

The diamondCut Function

Adding, replacing, or removing functions is done through a single diamondCut call that accepts an array of modifications:

struct FacetCut {
    address facetAddress;
    FacetCutAction action; // Add, Replace, Remove
    bytes4[] functionSelectors;
}

function diamondCut(
    FacetCut[] calldata _diamondCut,
    address _init,
    bytes calldata _calldata
) external;

Pros and Cons

ProsCons
No contract size limit (24KB bypassed)Complex architecture — higher audit cost
Granular upgrades (single function)Selector collision risk across facets
Single address for unlimited functionalityStorage management requires AppStorage or Diamond Storage
Atomic multi-facet upgradesSteeper learning curve for developers
Built-in introspection (EIP-2535 loupe)Fewer auditors specialize in Diamonds

Storage Management

Diamonds require careful storage management because all facets share the proxy's storage. Two patterns exist:

Diamond Storage — Each facet uses a unique storage slot based on a hash:

bytes32 constant STORAGE_POSITION = keccak256("myprotocol.facetA.storage");

struct FacetAStorage {
    uint256 value;
    mapping(address => uint256) balances;
}

function getStorage() internal pure returns (FacetAStorage storage s) {
    bytes32 position = STORAGE_POSITION;
    assembly { s.slot := position }
}

AppStorage — A single struct shared by all facets (simpler but less modular):

struct AppStorage {
    uint256 totalSupply;
    mapping(address => uint256) balances;
    // All facets read/write this struct
}

When to Use Diamond

  • •Large protocols exceeding the 24KB contract size limit
  • •Systems requiring modular, independent feature upgrades
  • •Protocols where different facets have different governance timelines
  • •Projects like Aavegotchi and Louper that need unlimited extensibility at a single address

Pattern 4: Beacon Proxy

The Beacon pattern is purpose-built for upgrading many proxy instances simultaneously. Instead of each proxy storing its own implementation address, all proxies point to a shared Beacon contract that returns the current implementation.

Architecture

  ┌──────────┐
  │  Beacon   │ ── stores implementation address
  └────┬──────┘
       │ getImplementation()
  ┌────┼────────────┐
  │    │            │
  ▼    ▼            ▼
┌────┐┌────┐    ┌────┐
│Prx1││Prx2│... │PrxN│  ← all upgraded at once
└────┘└────┘    └────┘
// Beacon Proxy (simplified)
contract BeaconProxy {
    address immutable beacon;

    fallback() external payable {
        address impl = IBeacon(beacon).implementation();
        assembly {
            calldatacopy(0, 0, calldatasize())
            let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize())
            switch result
            case 0 { revert(0, returndatasize()) }
            default { return(0, returndatasize()) }
        }
    }
}

Pros and Cons

ProsCons
Upgrade hundreds of instances in one transactionExtra gas per call (external call to beacon)
Minimal per-proxy deployment costAll instances must share the same logic
Clean factory pattern integrationCannot upgrade individual instances independently
Ideal for clone-heavy architecturesLess common — fewer audit templates

Gas Overhead

Each call to a Beacon Proxy adds an extra 2,600 gas for the external call to the Beacon contract. However, upgrades are dramatically cheaper — one transaction upgrades all instances instead of N separate upgradeTo calls.

When to Use Beacon Proxy

  • •Factory patterns deploying many identical contracts (vaults, escrows, pools)
  • •Protocols like Compound managing hundreds of cToken instances
  • •Systems where uniform behavior across all instances is required
  • •Architectures where upgrade cost scales linearly with instance count using other patterns

Pattern 5: Immutable (No Upgrade)

The most secure option is no upgradeability at all. If the contract does what it needs to do and nothing more, immutability provides the strongest trust guarantee.

When Immutable is the Right Choice

  • •Token contracts (ERC-20/721) with fixed supply logic
  • •Simple escrows with well-defined release conditions
  • •Cryptographic verifiers (signature checks, Merkle proofs)
  • •Timelock contracts where the logic must never change
  • •Any contract where a bug fix is less damaging than the centralization risk of upgradeability

The Hybrid Approach

Many production systems combine patterns. For example:

  • •Core token: Immutable ERC-20
  • •Staking logic: UUPS proxy (upgradeable by governance)
  • •Treasury: Diamond proxy (modular investment strategies)
  • •Vault factory: Beacon proxy (uniform vaults, batch upgrades)

Storage Layout: The Silent Killer

Storage collisions are the most dangerous risk across all proxy patterns. Because the proxy and implementation share storage, any change to the storage layout between versions can corrupt data.

The Rules

  1. •Never remove or reorder existing storage variables
  2. •Only append new variables at the end of the contract
  3. •Never change the type of an existing variable
  4. •Use storage gaps for future-proofing:
contract MyContractV1 {
    uint256 public value;
    address public admin;
    uint256[48] private __gap; // Reserve 48 slots for future use
}

contract MyContractV2 {
    uint256 public value;
    address public admin;
    uint256 public newField;     // Uses first gap slot
    uint256[47] private __gap;   // Gap shrinks by 1
}

Common Storage Collision Scenarios

ScenarioWhat HappensHow to Prevent
Removing a variableAll subsequent slots shift — catastrophic corruptionNever remove, only deprecate
Reordering variablesValues read from wrong slotsMaintain strict ordering
Changing uint128 to uint256Overwrites adjacent variableUse same-sized replacements only
Inheriting in different orderBase class storage shiftsLock inheritance chain order

OpenZeppelin's @openzeppelin/upgrades-core includes a storage layout checker that validates compatibility between versions. Running npx @openzeppelin/upgrades-core validate before every upgrade is non-negotiable.

Security Considerations and Audit Requirements

Per-Pattern Audit Focus Areas

PatternKey Audit Focus
Transparent ProxyAdmin key management, ProxyAdmin ownership
UUPS_authorizeUpgrade present in ALL versions, initializer protection
DiamondSelector collision across facets, storage isolation, diamondCut access control
BeaconBeacon ownership, implementation validation, factory security
ImmutableStandard vulnerability analysis (reentrancy, overflow, access control)

Governance Integration

Production upgrade patterns should integrate with governance mechanisms:

  • •Timelock: All upgrades pass through a 24-72h timelock, giving users time to exit
  • •Multisig: Require M-of-N signatures (typically 3/5 or 4/7) for upgrade execution
  • •On-chain governance: Token-weighted voting for protocol upgrades (Compound Governor, OpenZeppelin Governor)
  • •Emergency pause: Circuit breaker that freezes the contract without requiring a full upgrade
// Governance-gated upgrade pattern
function _authorizeUpgrade(address newImpl) internal override {
    require(msg.sender == address(timelock), "Only timelock");
    require(
        IGovernor(governor).proposalExecuted(currentProposalId),
        "Proposal not executed"
    );
}

Initializer Protection

All proxy patterns must protect against re-initialization attacks:

// ALWAYS use OpenZeppelin's initializer modifier
function initialize(address owner) public initializer {
    __Ownable_init(owner);
}

// ALWAYS call _disableInitializers in the constructor
constructor() {
    _disableInitializers();
}

Comparison Matrix

CriteriaTransparent ProxyUUPSDiamondBeaconImmutable
Gas per call+2,100Baseline+~200 (selector lookup)+2,600Baseline
Deploy costHigh (3 contracts)Medium (2 contracts)High (Diamond + facets)Low per instanceLowest
Upgrade cost1 tx per proxy1 tx per proxy1 tx (atomic multi-facet)1 tx for ALL instancesN/A
Max contract size24KB24KBUnlimited24KB24KB
ComplexityLowMediumHighLow-MediumLowest
Audit cost$$$$$$$$$$
Centralization riskMediumMediumMedium-HighMediumNone

Decision Framework

Use this flowchart to choose your pattern:

  1. •Can the contract be immutable? → Yes → Immutable. Always prefer immutability when possible.
  2. •Do you deploy many identical instances? → Yes → Beacon Proxy
  3. •Will the contract exceed 24KB? → Yes → Diamond (EIP-2535)
  4. •Is gas per transaction critical? → Yes → UUPS
  5. •Do you want maximum simplicity and tooling? → Yes → Transparent Proxy
  6. •Do you need modular, independent feature upgrades? → Yes → Diamond

Conclusion

Smart contract upgradeability is not a one-size-fits-all decision. Each pattern trades off gas efficiency, complexity, security surface area, and governance overhead. The Transparent Proxy remains the safe default for most projects. UUPS saves gas at the cost of developer discipline. The Diamond standard unlocks unlimited modularity for complex protocols. Beacon proxies shine when batch-upgrading fleets of identical contracts. And immutability — when achievable — remains the gold standard for trust.

Whatever pattern you choose, the non-negotiables are the same: storage layout validation on every upgrade, governance timelocks, comprehensive audit coverage, and initializer protection. Get these right, and upgradeability becomes a strength rather than a liability.


The Signal connects Web3 teams with vetted smart contract auditors and upgrade specialists. Browse our security partner directory →

PreviousNavigating the Current Web3 Investment Outlook: A Deep Dive into Funding TrendsNextSolana vs Ethereum for dApp Development: Performance, Cost, and Ecosystem Compared

Related Intelligence

How Web3 Founders Can Find Blockchain Developers and Agencies Efficiently

Jun 13, 2026

Clutch Alternatives for Finding Web3 and Blockchain Service Providers: A Practical Buyer Checklist

Jun 12, 2026

Educational Signal — Security & Auditing — 2026-06-12

Educational Signal — Security & Auditing — 2026-06-12

Jun 12, 2026

Need Web3 Consulting?

Get expert guidance from The Arch Consulting on blockchain strategy, tokenomics, and Web3 growth.

Learn More

Table of Contents

Share Article

XLI

Share Article

XLI