Smart Contract Security

Storage Collision Attacks in Upgradeable Smart Contracts

Kennedy OwiroDecember 23, 20258 min read

When a proxy delegates to an implementation via delegatecall, both contracts share the same storage. If their storage layouts don't align perfectly, variables overwrite each other — an owner variable might overwrite the implementation address, or a balance could corrupt an access control flag. Storage collisions have been responsible for some of the most subtle and devastating proxy bugs.

How Storage Collision Happens

// Proxy (slot 0 = implementation address)
contract Proxy {
    address public implementation;  // slot 0
}

// Implementation (slot 0 = owner)
contract Implementation {
    address public owner;  // slot 0 — COLLISION!

    function setOwner(address newOwner) external {
        owner = newOwner;  // This actually overwrites implementation address!
    }
}

Because delegatecall executes the implementation's code in the proxy's storage context, writing to owner (slot 0) in the implementation actually writes to the proxy's slot 0 — overwriting the implementation address.

Types of Storage Collision

1. Proxy-Implementation Collision

Implementation variables overlap with proxy admin slots. Solved by EIP-1967 which uses pseudo-random slots.

2. Upgrade Storage Mismatch

New implementation reorders, removes, or inserts variables — shifting all subsequent slots.

// V1
contract VaultV1 {
    address owner;     // slot 0
    uint256 balance;   // slot 1
    uint256 fee;       // slot 2
}

// V2 — BROKEN: inserted variable shifts everything
contract VaultV2 {
    address owner;     // slot 0
    bool paused;       // slot 1 — NEW! shifted balance to slot 2
    uint256 balance;   // slot 2 — was fee in V1!
    uint256 fee;       // slot 3
}

3. Struct Packing Collision

Structs pack multiple small variables into single slots. Changing struct layout corrupts packed data.

Prevention

// SECURE: Use EIP-1967 for proxy slots
bytes32 constant IMPL_SLOT = bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1);

// SECURE: Append-only storage in upgrades
contract VaultV2 is VaultV1 {
    // Only ADD new variables at the end
    bool public paused;  // slot 3 — after all V1 variables
}

// SECURE: Use storage gaps for future expansion
contract VaultV1 {
    address owner;
    uint256 balance;
    uint256[48] private __gap;  // Reserve 48 slots for future use
}
  • ✅ Use EIP-1967 pseudo-random storage slots for proxy admin data
  • ✅ Never reorder, remove, or insert storage variables between upgrades
  • ✅ Use OpenZeppelin's storage gap pattern for reserving upgrade space
  • ✅ Run OpenZeppelin's upgrades plugin to validate storage compatibility
  • ✅ Use namespaced storage (EIP-7201) for complex upgrade patterns

How Vultbase Detects Storage Collisions

  1. Slither — Storage layout analysis comparing proxy and implementation
  2. Pattern DB — Storage collision patterns from real proxy bugs
  3. Proxy Upgrade Challenge — Validates storage compatibility across versions

Storage collisions are silent killers — your tests pass but storage is secretly corrupted. Audit your proxy setup before upgrading.

storage collisionproxyupgradeabledelegatecallEIP-1967Solidity
Share

Written by

Kennedy Owiro

Founder & CTO, Vultbase

14+ years building security and QA systems at scale. Background in fintech security and Web3 smart contract testing. Built Vultbase's Intelligence Engine with 1,200+ exploit patterns from $40B+ in historical DeFi losses.

Protect your protocol before launch.

Submit your smart contracts for automated security analysis powered by 1,200+ real exploit patterns.

Start Your Audit →