Track 7 · The Breach Lab · lesson 11
Proxies and storage collisions
18 min
Track 3 said deployed code cannot change. Upgradeable contracts get around that, and every method of getting around it reintroduces a risk immutability had removed.
An upgradeable contract is really two: a proxy that holds all the state and
never changes, and an implementation that holds the logic and can be swapped.
The proxy forwards every call to the current implementation using
delegatecall, which runs the implementation's code against the proxy's storage.
So "upgrading" means pointing the proxy at new logic. Which means somebody can point it at any logic — including logic that drains the contract.
Risk one: storage collisions
Because the implementation runs against the proxy's storage, both must agree exactly on the storage layout. Reorder a variable in a new version and it reads a different slot than before.
// Version 1 layout // Version 2 — someone reordered
address public owner; address public token; // slot 0: was owner!
address public token; address public owner; // slot 1: was token!
// After upgrade, 'owner' now reads what used to be 'token' —
// an attacker-controlled address is suddenly the owner.Reordering variables changes which slot each name reads. After the upgrade, 'owner' points at old 'token' data — and control of the contract can shift silently.
Risk two: who holds the upgrade key
Even with perfect layout discipline, someone can change the logic. That someone is now the most powerful entity in the system — able to replace the whole contract with a version that sends every token to themselves.
If that key is a single private key, "immutable, trustless contract" is marketing. The real security is whoever holds the key, which is why serious projects put upgrades behind a multisig and a timelock.
Predict
A protocol is 'decentralised and trustless' but its contracts are upgradeable by one address. What's true?
The $280M Parity freezeOptional
Upgradeability's most famous disaster was not a theft but a freeze. Parity's
multisig wallets shared a single library contract via delegatecall. Someone
triggered that library's selfdestruct, and because every wallet depended on
it, all of them became permanently unusable.
Around $280 million was locked, not stolen — unreachable forever, because the code the proxies pointed at no longer existed. Immutability's absence cut both ways: the logic could be changed, including changed into nothing.
Check
Why must an upgradeable contract's storage layout stay append-only?
Worth remembering
- An upgradeable contract is a fixed proxy holding state plus a swappable implementation holding logic.
- The proxy delegatecalls the implementation, so both must share an identical storage layout.
- Reordering storage causes collisions that can silently hand over control — keep layout append-only.
- Whoever holds the upgrade key can replace the logic entirely; that key is the real trust anchor.
- The Parity freeze locked ~$280M by self-destructing a shared library the proxies depended on.