Upgradeable contracts are not as safe as you think. Architectures for upgradeability can be flawed, locking contracts, losing data, or sabotaging your ability to recover from an incident. Every contract upgrade must be carefully reviewed to avoid catastrophic mistakes. The most common delegatecall
proxy comes with drawbacks that we’ve catalogued before.
Crytic now includes a comprehensive suite of 17 upgradeability checks to help you avoid these pitfalls.
The how-to
Reviewing upgradeable contracts is a complex low-level task that requires investigating the storage layout and organization of functions in memory. We created a sample token that supports upgradeability to help walk through the steps in crytic/upgradeability-demo. This simple demo repository includes:
Any call to Proxy
will use a delegatecall
on MyToken
to execute its logic, while the storage variables will be held on Proxy
. This is a standard setup for most upgradeable contracts.
Consider these two contracts are already deployed on mainnet. However, the code for MyToken has become stale and you need to change its features. It’s time for MyTokenV2! The code for MyTokenV2 is similar to MyToken, with the exception of removing the init()
function and its associated state variable.
Let’s use Crytic to ensure that deploying MyTokenV2
does not introduce new security risks.
Configuration
First, tell Crytic about your upgradeable contracts. Go to your Crytic settings and find this panel:
Here you can configure:
- The contract being upgraded
- The proxy used
- The new version of the contract
Note: (1) and (2) are optional; Crytic will run as many checks as are appropriate.
For example, if you only have the upgradeable contract, and no proxy or new version, Crytic can already look for flaws in the initialization schema. If you have the upgradeable contract and the proxy, but no new version, Crytic can look for function collisions between the implementation and the proxy. If you have multiple upgradeable contracts, or multiple proxies, you can then configure any combination that fits your setup.
Back to MyToken, we have these three contracts:
Once we configure Crytic, the upgradeability checks will run on every commit and pull request, similar to security checks and unit tests:
Crytic’s Findings
Occasionally, Crytic will find serious errors in your upgradeability code (oh no!). We built one such issue into our demo. Here’s what it looks like when Crytic discovers a security issue:
The was_init
storage variable was removed, so balances has a different storage offset in MyToken
and MyTokenV2
, breaking the storage layout of the contract.
This is a common mistake that can be particularly difficult to find by hand in complex codebases with many contracts and inheritances—but Crytic will catch the issue for you!
What else can Crytic find?
Crytic will review (depending on your configuration):
- Storage layout consistency between the upgrades and the proxy
- Function collisions between the proxy and the implementation
- Correct initialization schema
- Best practices for variable usage
Here’s the detailed list of checks:
Num | What it Detects | Impact | Proxy needed | New version needed |
1 | Variables that should not be constant | High | X | |
2 | Function ID collision | High | X | |
3 | Function shadowing | High | X | |
4 | Missing call to init function | High | ||
5 | initializer() is not called | High | ||
6 | Init function called multiple times | High | ||
7 | Incorrect vars order in v2 | High | X | |
8 | Incorrect vars order in the proxy | High | X | |
9 | State variables with an initial value | High | ||
10 | Variables that should be constant | High | X | |
11 | Extra vars in the proxy | Medium | X | |
12 | Variable missing in the v2 | Medium | X | |
13 | Extra vars in the v2 | Informational | X | |
14 | Initializable is not inherited | Informational | ||
15 | Initializable is missing | Informational | ||
16 | Initialize function that must be called | Informational | ||
17 | initializer() is missing | Informational |
Check your contracts with Crytic
In addition to finding 90+ vulnerabilities, Crytic can now detect flaws in your upgradeability code. It is the only platform that can protect your codebase in depth for so many issues. If you want to avoid catastrophic mistakes, use Crytic before deploying any upgradeable contract.
Got questions? Join our Slack channel (#crytic) or follow @CryticCI on Twitter.