🧩 Advanced Storage: Mappings, Arrays & Structs

Master complex data structures and their storage layout

🔬 Advanced Storage Patterns

For upgradeable contracts and complex systems, advanced storage patterns are essential to avoid collisions, enable modular upgrades, and maintain flexibility as your contract evolves.

🎯 Interactive: Pattern Comparison Tool

Explore 4 advanced storage patterns for upgradeable contracts:

🔄

Proxy Pattern

Medium

Separates logic and storage—proxy holds state, implementation holds logic

Architecture:
Proxy Contract (Storage)
↓ delegatecall
Implementation (Logic)
✅ Pros:
  • Upgradeable logic
  • Preserve state across upgrades
  • Single proxy, multiple implementations
❌ Cons:
  • Storage collision risk
  • Initialization complexity
  • delegatecall gas overhead
Use Case
Most common pattern for upgradeable contracts (e.g., UUPS, Transparent)
Gas Impact
+2,600 gas per delegatecall
Example Implementation:
// Proxy stores all state
contract Proxy {
  address public implementation;
  mapping(address => uint) public balances;
  
  fallback() external {
    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()) }
    }
  }
}

Storage Collision Prevention

The biggest risk in upgradeable contracts is storage collision—when V2 accidentally overwrites V1's data. Here's how each pattern handles it:

🔄
Proxy Pattern
Risk: Medium (must maintain layout)

Implementation can't have its own storage—must inherit from proxy's storage structure.

❌ Don't: Add variables before inherited ones in V2
✅ Do: Always append new variables at end
🎯
Unstructured Storage
Risk: Low (deterministic slots)

Uses keccak256 hash to place storage at pseudo-random slots, avoiding sequential collision.

bytes32 slot = keccak256("my.unique.namespace");
Result: Slots so far apart, collision is mathematically impossible
♾️
Eternal Storage
Risk: None (separate contract)

Storage is completely separate—logic contracts can be replaced without touching storage.

✅ V1 and V2 both reference same storage contract
No collision possible—storage layout never changes
💎
Diamond Pattern
Risk: Medium-High (shared storage across facets)

All facets share diamond's storage—must coordinate slot usage across all modules.

⚠️ Challenge: Multiple facets must not overlap storage
✅ Solution: Use struct-based storage with unique namespaces

Choosing the Right Pattern

PatternBest ForComplexityGas Cost
Proxy (UUPS/Transparent)Most upgradeable contractsMediumGood
Unstructured StorageModern proxies (with above)MediumExcellent
Diamond (EIP-2535)Large, modular systemsHighFair
Eternal StorageSimple upgrades, low trafficLowPoor

Real-World Examples

🏦Aave V3
Uses: Transparent Proxy + Unstructured Storage
Complex lending protocol with upgradeable logic, secure storage isolation, and governance-controlled upgrades.
👻Aavegotchi
Uses: Diamond Pattern (EIP-2535)
NFT game with modular facets for different features (staking, lending, gameplay), avoiding 24KB contract limit.
🔷Compound
Uses: Eternal Storage (V1)
Early DeFi protocol used eternal storage for simple upgrades, later migrated to more efficient patterns.
🔒OpenZeppelin
Uses: UUPS Proxy + Unstructured
Standard library for upgradeable contracts, combining proxy with unstructured storage for maximum safety.

⚠️ Storage Upgrade Best Practices

Test upgrades on testnet with real data first
Use storage gap for future variables: uint[50] __gap;
Document slots used by each contract version
Never reorder existing state variables
Don't change variable types (uint256 → uint128)
Don't remove variables from middle of layout
Don't inherit new contracts before existing ones
Don't use selfdestruct in upgradeable contracts