Proxy Storage Collision

Upgradeable proxy contracts use delegatecall to run implementation code in the proxy's storage context. If both the proxy and the implementation store variables at the same storage slot, they will overwrite each other — often corrupting the implementation address or ownership data.

How it works

A naive proxy stores the implementation address at slot 0. If the implementation also has a state variable at slot 0 (e.g., an owner), then calling the implementation via the proxy will read and write that same slot — confusing the owner and the implementation address.

Vulnerable pattern

// VULNERABLE — proxy stores implementation at slot 0
contract NaiveProxy {
    address public implementation;  // slot 0
    
    fallback() external {
        (bool ok,) = implementation.delegatecall(msg.data);
        require(ok);
    }
}

// Implementation also has slot 0:
contract MyLogic {
    address public owner;  // COLLISION with proxy's implementation slot!
    
    function initialize(address _owner) public {
        owner = _owner;  // accidentally overwrites proxy.implementation!
    }
}

Safe patterns

// EIP-1967: store implementation at a pseudo-random slot
bytes32 constant IMPL_SLOT = bytes32(uint256(
    keccak256("eip1967.proxy.implementation")
) - 1);

function _setImplementation(address impl) private {
    assembly { sstore(IMPL_SLOT, impl) }
}

// Or use OpenZeppelin's TransparentUpgradeableProxy or UUPS
// which handle storage layout via EIP-1967 automatically

Real-world exploits

  • Multiple protocols using naive proxy patterns have had ownership hijacked due to storage collisions.
  • The Audius governance hack (2022) exploited an uninitialized proxy and storage collision to drain $6 million.

How to prevent it

  • Use EIP-1967 storage slots for proxy admin data — OpenZeppelin's proxies do this correctly.
  • Use OpenZeppelin Upgrades Plugin — it runs automatic storage layout checks during upgrades.
  • Append-only storage: never reorganize state variable order in implementation upgrades.
  • Use UUPS proxies where the upgrade logic is in the implementation, simplifying storage layout.
← Back to Glossary