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
upgradesplugin to validate storage compatibility - ✅ Use namespaced storage (EIP-7201) for complex upgrade patterns
How Vultbase Detects Storage Collisions
- Slither — Storage layout analysis comparing proxy and implementation
- Pattern DB — Storage collision patterns from real proxy bugs
- 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.