For educational purposes only; not investment advice. Investing may result in loss.
Direct answer
A proxy storage collision occurs when implementation code reads or writes a proxy storage slot with a different meaning from the layout that created the existing state. An upgrade can then make a balance look like an address, clear an owner, corrupt a mapping root, or overwrite upgrade-control data.
With delegatecall, implementation bytecode executes in the proxy’s context: storage, balance, and address(this) belong to the proxy. Variable names are not stored on-chain, so the EVM follows only the slot and byte offset computed by the new code.
An upgrade must therefore preserve the deployed layout, not merely compile successfully or expose the same functions. Compare compiler-generated layouts against the exact deployed version before authorizing an upgrade.
How it works
Solidity normally places state variables from slot 0 onward in declaration order after C3 linearization of inheritance. Values smaller than 32 bytes may share a slot; structs and arrays introduce additional placement rules, while mappings and dynamic arrays derive their data locations from a base slot. A change that shifts a base slot also changes where its derived data is found.
There are four distinct collision boundaries:
- Proxy versus implementation: proxy-owned fields such as the implementation or administrator must not occupy slots used by application state. ERC-1967 assigns standardized, compiler-avoided slots for implementation, beacon, and administrator data.
- Old versus new implementation: existing application variables must keep compatible slots, offsets, and types. Appending a variable may be safe, but inserting, reordering, deleting, or changing a variable type can reinterpret existing words.
- Inheritance: adding state to a base contract or changing the inheritance order can shift storage declared by descendants, even when the descendant source is unchanged.
- Reserved or namespaced storage: a correctly consumed storage gap can reserve space for a base contract, and ERC-7201-style namespaces can isolate layouts. Neither technique permits arbitrary changes inside an existing layout.
Example
Suppose version 1 has the following layout:
uint256 totalAssets; // slot 0
address owner; // slot 1Version 2 incorrectly inserts a variable at the beginning:
bool paused; // slot 0, offset 0
uint256 totalAssets; // slot 1
address owner; // slot 2After the upgrade, paused reads the low byte of the old totalAssets, the new totalAssets reads the old owner word as an integer, and owner reads whatever was already in slot 2, often zero. The original words remain in storage, but the new code gives them different meanings. A transaction can succeed while applying authorization or accounting logic to corrupted interpretations.
Risks and upgrade checks
- Generate storage-layout output for both implementations and compare slot, offset, type, and inheritance information against the actual deployed reference contract.
- For a conventional linear layout, append new variables only. Do not reorder existing fields, change their types, remove and reuse them, or modify base contracts without proving compatibility.
- When consuming a storage gap, reduce it by the exact number of reserved slots used. For namespaced storage, keep namespace identifiers unique and validate changes within every existing namespace.
- Do not treat ERC-1967 as complete protection. It separates proxy metadata from compiler-assigned application slots, but it does not make two implementation layouts compatible.
- Test the upgrade and any reinitializer on a fork or state snapshot. Verify owners, roles, balances, allowances, mapping entries, pause state, implementation slots, and rollback or emergency controls before and after execution.
If an upgrade has already caused a suspected collision, pause further upgrades and state-changing calls when governance permits. Preserve the pre-upgrade block number and implementation bytecode, compare raw storage at affected slots, and have qualified contract engineers design and independently review a migration. Repeating upgrades without a proven storage map can destroy more recoverable state.
Common misconceptions
- “The variable names are unchanged, so the layout is safe.” Names do not determine storage positions. Types, order, packing, inheritance, and namespace rules do.
- “Deleting a variable frees its slot for reuse.” Proxy storage persists. Reusing the slot assigns a new interpretation to the old word unless a carefully reviewed migration clears or transforms it.
- “A successful test transaction proves the upgrade is compatible.” A test may touch only a few slots. Layout validation and state-diff tests must cover privileged fields, packed values, mappings, arrays, and inherited storage.
Related topics
- Delegatecall storage risk
- Initializer takeover
- Proxy contract
- Proxy upgrade monitoring
- Upgradeable contract
Sources
- Introduction to Smart Contracts - Solidity Documentation (accessed: 2026-08-21)
- Layout of State Variables in Storage and Transient Storage - Solidity Documentation (accessed: 2026-08-21)
- ERC-1967: Proxy Storage Slots - Ethereum Improvement Proposals (accessed: 2026-08-21)
- Writing Upgradeable Contracts - OpenZeppelin Documentation (accessed: 2026-08-21)