Proxy and upgradeability vulnerabilities
Upgradeable contracts separate storage (the proxy) from logic (the implementation), so the logic can be replaced. That flexibility brings its own bugs: storage collisions between versions, implementations left uninitialised, unprotected upgrade functions, and an admin key that becomes the single largest risk in the system.
Common flaws
- Storage collisions. Adding, removing or reordering state variables between versions, or clashing with proxy admin slots.
- Uninitialised implementations. Constructors do not run for proxies, so an initialiser is used. If the implementation itself is never initialised, an attacker may initialise it and take control, sometimes destroying it.
- Re-initialisation. Initialisers that can be called more than once.
- Unprotected upgrade functions. Missing or incorrect access control on the function that changes the implementation, a classic issue in UUPS proxies.
- Function selector clashes. Proxy functions and implementation functions sharing a selector.
- Admin key risk. Whoever can upgrade can replace all the logic. That key needs the protection of the treasury, because it effectively is the treasury.
What to check before every upgrade
- Storage layout compared with the previous version.
- Initialisers protected, and implementations initialised or locked.
- Upgrade authority behind a timelock and multisig.
- The new version reviewed. See re-audits.
Questions
Frequently asked questions
Should we make our contracts upgradeable?
Only if you need to. Upgradeability lets you fix bugs, but it also means users trust whoever controls the upgrade. If you keep it, put upgrades behind a timelock and a multisig, and tell users.
What is a storage collision?
When a new implementation reads or writes a storage slot that the old one used for something else, or that the proxy uses for its own data. Existing values are then interpreted as the wrong thing.
Published Updated
