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;34 fallback() external payable {5 address impl = implementation;6 assembly {7 // Copy calldata8 calldatacopy(0, 0, calldatasize())910 // Delegatecall to implementation11 let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)1213 // Copy return data14 returndatacopy(0, 0, returndatasize())1516 // Return or revert based on result17 switch result18 case 0 { revert(0, returndatasize()) }19 default { return(0, returndatasize()) }20 }21 }22}
When users interact with the proxy:
- The proxy receives the call
- It forwards the call to the implementation via
delegatecall - The implementation executes using the proxy's storage
- 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 functions4 } else {5 // User calls delegate to implementation6 _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 contract2function 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 selector2function _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 collision2contract ProxyV1 {3 address public implementation; // Slot 04 address public admin; // Slot 15}67contract 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");34function _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 protection2contract Implementation {3 address public owner;45 function initialize(address _owner) external {6 owner = _owner; // Can be called multiple times!7 }8}910// SAFE: With initialization guard11contract Implementation is Initializable {12 address public owner;1314 function initialize(address _owner) external initializer {15 owner = _owner; // Can only be called once16 }17}
Unprotected Upgrade Functions
Upgrade functions without proper access control allow complete takeover:
1// CRITICAL VULNERABILITY2function upgradeTo(address newImpl) external {3 implementation = newImpl; // Anyone can upgrade!4}56// SECURE: With access control + timelock7function 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 implementation2function emergencyShutdown() external onlyOwner {3 selfdestruct(payable(owner)); // Breaks all proxies using this impl4}
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
selfdestructin implementation - No
delegatecallto 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;34 // Reserve 50 slots for future upgrades5 uint256[50] private __gap;6}78contract BaseV2 is BaseV1 {9 uint256 public newValue; // Uses one gap slot1011 uint256[49] private __gap; // Reduced gap12}
Why Proxies Matter in DeFi
Major protocols use proxy patterns extensively:
| Protocol | Pattern | Purpose |
|---|---|---|
| Uniswap V3 | Minimal proxy (clones) | Pool deployment |
| Aave V3 | Transparent proxy | Protocol upgrades |
| Compound | Transparent proxy | Governance upgrades |
| OpenSea | UUPS | Marketplace 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.
Articles Using This Term
Learn more about Proxy Pattern in these articles:
Related Terms
Diamond Standard
EIP-2535 proxy pattern allowing a single contract to delegate calls to multiple implementation contracts (facets).
Facet
Modular logic contracts in the Diamond Standard that contain specific functionality accessed through the diamond proxy.
Delegatecall
EVM opcode that executes another contract's code in the calling contract's storage context, enabling proxy patterns and code reuse.
Timelock
Smart contract mechanism enforcing mandatory delay between initiating and executing critical protocol changes for transparency.
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

