Proxy Pattern

Smart contract design separating storage and logic, enabling upgrades by changing implementation while preserving state.

The proxy pattern is a fundamental smart contract architecture that separates storage from logic, enabling contract upgrades without losing state or changing addresses. This pattern is essential in production DeFi because smart contracts are immutable by default—once deployed, their code cannot be modified. Proxy patterns solve this by redirecting all calls through a proxy contract that delegates execution to a separate implementation contract, which can be swapped out for upgrades.

How Proxy Patterns Work

The pattern relies on Ethereum's delegatecall opcode, which executes code from another contract but uses the calling contract's storage:

1contract Proxy {
2 address public implementation;
3
4 fallback() external payable {
5 address impl = implementation;
6 assembly {
7 // Copy calldata
8 calldatacopy(0, 0, calldatasize())
9
10 // Delegatecall to implementation
11 let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
12
13 // Copy return data
14 returndatacopy(0, 0, returndatasize())
15
16 // Return or revert based on result
17 switch result
18 case 0 { revert(0, returndatasize()) }
19 default { return(0, returndatasize()) }
20 }
21 }
22}

When users interact with the proxy:

  1. The proxy receives the call
  2. It forwards the call to the implementation via delegatecall
  3. The implementation executes using the proxy's storage
  4. Results are returned to the caller

Upgrades simply change the implementation address to point to new code.

Common Proxy Patterns

Transparent Proxy (OpenZeppelin)

Separates admin and user calls to prevent function selector clashes:

1fallback() external payable {
2 if (msg.sender == admin) {
3 // Admin calls go to proxy's admin functions
4 } else {
5 // User calls delegate to implementation
6 _delegate(implementation);
7 }
8}

Pros: Clear separation, battle-tested Cons: Higher gas cost (admin check on every call)

UUPS (EIP-1822)

Moves upgrade logic into the implementation contract:

1// In implementation contract
2function upgradeTo(address newImplementation) external onlyOwner {
3 _setImplementation(newImplementation);
4}

Pros: Lower gas costs, smaller proxy Cons: Risk of bricking if upgrade function has bugs

Diamond Standard (EIP-2535)

Maps function selectors to multiple implementation contracts (facets):

1// Route calls to different facets based on selector
2function _fallback() internal {
3 address facet = selectorToFacet[msg.sig];
4 require(facet != address(0), "Function not found");
5 _delegate(facet);
6}

Pros: Overcomes 24KB limit, granular upgrades Cons: More complex, requires careful management

Security Vulnerabilities

Storage Collisions

The most critical risk—proxy and implementation must use compatible storage layouts:

1// DANGEROUS: Storage collision
2contract ProxyV1 {
3 address public implementation; // Slot 0
4 address public admin; // Slot 1
5}
6
7contract ImplementationV1 {
8 uint256 public value; // Slot 0 - COLLIDES with implementation!
9}

Mitigation: Use reserved storage slots or unstructured storage:

1bytes32 constant IMPLEMENTATION_SLOT =
2 keccak256("eip1967.proxy.implementation");
3
4function _getImplementation() internal view returns (address impl) {
5 bytes32 slot = IMPLEMENTATION_SLOT;
6 assembly {
7 impl := sload(slot)
8 }
9}

Uninitialized Implementation

Constructors don't run in proxy context. Implementations need explicit initializers:

1// VULNERABLE: No initialization protection
2contract Implementation {
3 address public owner;
4
5 function initialize(address _owner) external {
6 owner = _owner; // Can be called multiple times!
7 }
8}
9
10// SAFE: With initialization guard
11contract Implementation is Initializable {
12 address public owner;
13
14 function initialize(address _owner) external initializer {
15 owner = _owner; // Can only be called once
16 }
17}

Unprotected Upgrade Functions

Upgrade functions without proper access control allow complete takeover:

1// CRITICAL VULNERABILITY
2function upgradeTo(address newImpl) external {
3 implementation = newImpl; // Anyone can upgrade!
4}
5
6// SECURE: With access control + timelock
7function upgradeTo(address newImpl) external onlyOwner {
8 require(timelockExpired(), "Timelock active");
9 implementation = newImpl;
10}

Self-Destruct in Implementation

If an implementation contains selfdestruct, it can brick the proxy:

1// DANGEROUS: Can destroy implementation
2function emergencyShutdown() external onlyOwner {
3 selfdestruct(payable(owner)); // Breaks all proxies using this impl
4}

Audit Checklist for Proxy Patterns

When auditing upgradeable contracts:

  • Storage layout compatibility between versions
  • Initializer functions have replay protection
  • Upgrade functions properly access-controlled
  • Timelock or multisig required for upgrades
  • No selfdestruct in implementation
  • No delegatecall to untrusted addresses in implementation
  • Constructor vs initializer logic correct
  • Storage gaps for future upgrades in base contracts

Storage Gaps

Base contracts should reserve storage for future variables:

1contract BaseV1 {
2 uint256 public value;
3
4 // Reserve 50 slots for future upgrades
5 uint256[50] private __gap;
6}
7
8contract BaseV2 is BaseV1 {
9 uint256 public newValue; // Uses one gap slot
10
11 uint256[49] private __gap; // Reduced gap
12}

Why Proxies Matter in DeFi

Major protocols use proxy patterns extensively:

ProtocolPatternPurpose
Uniswap V3Minimal proxy (clones)Pool deployment
Aave V3Transparent proxyProtocol upgrades
CompoundTransparent proxyGovernance upgrades
OpenSeaUUPSMarketplace updates

Understanding proxy patterns is essential for auditing any upgradeable protocol. The separation of storage and logic creates unique attack surfaces that require careful analysis of both the proxy mechanics and the implementation code.

Need expert guidance on Proxy Pattern?

Our team at Zealynx has deep expertise in blockchain security and DeFi protocols. Whether you need an audit or consultation, we're here to help.

Get a Quote

oog
zealynx

Subscribe to Our Newsletter

Stay updated with our latest security insights and blog posts

© 2024 Zealynx