# Upgradeable Contracts में Storage Slot Collisions
Proxy patterns powerful हैं — लेकिन storage layout गलती से millions lost हो सकते हैं।
यहाँ समझें क्यों, और कैसे बचें।
Problem: Shared Storage Space
Normal Contract
contract Token {
mapping(address => uint) public balances; // Slot 0
uint public totalSupply; // Slot 1
}
Storage clear hai — हर variable अपनी slot में।
Proxy Pattern
// Proxy
contract Proxy {
address public implementation; // Slot 0 ❌
fallback() external payable {
// delegatecall to implementation
}
}
// Implementation
contract TokenV1 {
mapping(address => uint) public balances; // Slot 0 ❌❌
uint public totalSupply; // Slot 1
}
Collision: implementation और balances दोनों slot 0 में!
delegatecall executes in PROXY storage
→ balances[user] overwrites implementation address
→ Proxy bricked 💥
Real Incident
Parity Multisig Hack (2017):
// Library (implementation)
contract WalletLibrary {
address[] public owners; // Slot 0
function initWallet(address[] _owners) public {
owners = _owners; // ❌ Overwrites proxy's implementation!
}
}
Attacker called initWallet directly on library → became owner → killed contract।
Loss: $150M+ frozen।
Solution 1: EIP-1967 Standard Slots
Use high random slots for proxy storage।
contract EIP1967Proxy {
// bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)
bytes32 private constant IMPLEMENTATION_SLOT =
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
function implementation() public view returns (address impl) {
assembly {
impl := sload(IMPLEMENTATION_SLOT)
}
}
function upgradeTo(address newImpl) internal {
assembly {
sstore(IMPLEMENTATION_SLOT, newImpl)
}
}
}
Why high slot?
Normal variables start at slot 0 → collision unlikely with slot 0x360894a13...
Implementation Contract
contract TokenV1 {
// Normal storage, starts at slot 0
mapping(address => uint) public balances; // Slot 0 ✅
uint public totalSupply; // Slot 1 ✅
// No collision with proxy (proxy uses high slot)
}
Solution 2: Unstructured Storage
OpenZeppelin pattern:
abstract contract Proxy {
function _implementation() internal view virtual returns (address impl) {
bytes32 slot = _IMPLEMENTATION_SLOT;
assembly {
impl := sload(slot)
}
}
function _setImplementation(address newImpl) internal {
bytes32 slot = _IMPLEMENTATION_SLOT;
assembly {
sstore(slot, newImpl)
}
}
}
Solution 3: Diamond Storage (ERC-7201)
Multiple implementations के लिए — isolated namespaces।
library TokenStorage {
// keccak256("myprotocol.token.storage")
bytes32 constant STORAGE_SLOT =
0x1234567890abcdef...;
struct Layout {
mapping(address => uint) balances;
uint totalSupply;
}
function layout() internal pure returns (Layout storage l) {
bytes32 slot = STORAGE_SLOT;
assembly {
l.slot := slot
}
}
}
contract TokenV1 {
using TokenStorage for *;
function balanceOf(address user) public view returns (uint) {
return TokenStorage.layout().balances[user];
}
function mint(address to, uint amount) public {
TokenStorage.Layout storage s = TokenStorage.layout();
s.balances[to] += amount;
s.totalSupply += amount;
}
}
Benefits:
- Multiple facets (Diamond pattern)
- No collision possible
- Isolated upgrades
Collision Detection
Tool 1: Slither
slither . --detect proxy-collision
Output:
ProxyAdmin.implementation (slot 0) collides with Token.balances (slot 0)
Tool 2: Foundry vm.load
Test storage layout:
contract StorageTest is Test {
Proxy proxy;
function testNoCollision() public {
proxy = new Proxy();
// Implementation should be in high slot
bytes32 implSlot = bytes32(uint(keccak256("eip1967.proxy.implementation")) - 1);
address impl = address(uint160(uint(vm.load(address(proxy), implSlot))));
assertEq(impl, expectedImpl);
}
}
Tool 3: OpenZeppelin Storage Layout Hash
npx hardhat storage-layout
Generates JSON:
{
"storage": [
{ "label": "balances", "slot": "0", "type": "t_mapping" },
{ "label": "totalSupply", "slot": "1", "type": "t_uint256" }
]
}
Compare before/after upgrade — slot changes = danger।
Upgrade Safety Checklist
❌ Unsafe
// V1
contract TokenV1 {
uint public totalSupply; // Slot 0
mapping(address => uint) balances; // Slot 1
}
// V2
contract TokenV2 {
mapping(address => uint) balances; // Slot 0 ❌ Moved!
uint public totalSupply; // Slot 1 ❌ Swapped!
}
Result: totalSupply now reads garbage from balances slot।
✅ Safe
// V1
contract TokenV1 {
uint public totalSupply; // Slot 0
mapping(address => uint) balances; // Slot 1
}
// V2
contract TokenV2 {
uint public totalSupply; // Slot 0 ✅ Same
mapping(address => uint) balances; // Slot 1 ✅ Same
uint public newFeatureFlag; // Slot 2 ✅ Appended
}
Rules:
Gap Pattern
Reserve slots for future upgrades:
contract TokenV1 {
uint public totalSupply;
mapping(address => uint) balances;
// Reserve 50 slots for future variables
uint[48] private __gap;
}
V2 can add 48 more variables safely:
contract TokenV2 {
uint public totalSupply;
mapping(address => uint) balances;
uint public newVar1; // Uses gap slot 0
uint public newVar2; // Uses gap slot 1
uint[46] private __gap; // Reduced by 2
}
Real Incidents
1. Compound cToken (2020)
Wrong storage layout in upgrade → markets corrupted।
Fix: Emergency pause, rollback।
2. Audius (2022)
Storage collision allowed attacker to change governance।
Loss: $6M drained।
Root cause: Missing gap, wrong inheritance order।
Testing Storage Layout
contract UpgradeTest is Test {
ProxyAdmin admin;
TransparentProxy proxy;
TokenV1 v1;
TokenV2 v2;
function testUpgradeSafety() public {
// Deploy V1
v1 = new TokenV1();
proxy = new TransparentProxy(address(v1), address(admin), "");
TokenV1 token = TokenV1(address(proxy));
token.mint(alice, 1000 ether);
// Snapshot storage
uint aliceBalBefore = token.balanceOf(alice);
uint supplyBefore = token.totalSupply();
// Upgrade to V2
v2 = new TokenV2();
admin.upgrade(proxy, address(v2));
// Check storage preserved
TokenV2 tokenV2 = TokenV2(address(proxy));
assertEq(tokenV2.balanceOf(alice), aliceBalBefore, "Balance corrupted");
assertEq(tokenV2.totalSupply(), supplyBefore, "Supply corrupted");
}
}
Prevention Stack
| Layer | Tool | Purpose |
|-------|------|---------|
| Design | EIP-1967 | Standard high slots |
| Code | Diamond storage | Namespace isolation |
| Test | Foundry vm.load | Storage assertions |
| CI | Slither | Auto-detect collisions |
| Audit | Manual review | Inheritance order |
Conclusion
Storage collisions silent killers हैं — no compile error, no warning।
Best practices:
__gap patterns2026 में tools better हैं, लेकिन developer awareness critical है।
एक गलत upgrade = protocol dead। Take storage layout seriously। 🔒