Proxies and Upgradeable Code
Proxies are a common tool to allow for smart contract code to receive upgrades. A proxy contract acts as a pointer to a logic contract using delegatecall, and the logic contract contains the implementation logic. If the proxy contract is modified to point to a different logic contract address, the implementation logic is changed.
Other resources:
1. Is the UUPS proxy initialized?
Incorrect
No
Correct
Yes
Explanation
An uninitialized proxy can be initialized by anyone. The user that calls the initialize function is granted special privileges for the proxy contract.
Links
2. Does the logic contract behind the proxy contain selfdestruct?
Incorrect
Yes
Correct
No, selfdestruct
is not found in the contracts or the contract is not a logic contract behind a proxy contract
Explanation
If the logic contract behind a proxy contains selfdestruct
, the proxy can be deleted, which can cause a denial of service. If a metamorphic contract created with CREATE2 contains selfdestruct
, then the contract code can be replaced in a similar way to upgrading code behind a proxy.
Links
- Parity Wallet hack and OpenZeppelin writeup (note: the contract was initialized but had no protection against a second call to
initialize()
) - Paradigm CTF 2021 "Vault"
- Ethernaut Level 25 "Motorbike"
- OpenZeppelin proxy vulnerability
- iosiro blog post about OpenZeppelin vulnerability
3. Is the contract (not necessarily a proxy contract) created with CREATE2 or CREATE3 and the contract contains selfdestruct?
Incorrect
Yes
Correct
No, selfdestruct
is not found in the contracts or the contract was not created with CREATE2 or CREATE3
Explanation
A CREATE2 or CREATE3 contract that contains selfdestruct can be destroyed and replaced with a new contract at the same address. If a metamorphic contract created with CREATE2 contains selfdestruct
, then the contract code can be replaced in a similar way to upgrading code behind a proxy.
Links
- Rajeev forum post about CREATE2 security implications
- CertiK metamorphic contract detector tool
- a16z metamorphic contract detector tool
- PoC metamorphic contract detector tool
4. Does the upgraded logic contract have the same state variable layout as the contract it replaces?
Incorrect
No
Correct
Yes, new state variables are only appended to the list of existing state variables. Even better, the new state variables are filling slots that were already reserved using a pattern like OpenZeppelin contact's storage gap.
Explanation
If the storage variable layout is changed, a storage collision can happen where the variable names in the upgraded contract to not match the variables that are stored in the corresponding storage slots. The storage layout should always consider inheritance, so flattening the contracts to more easily view the storage variables can sometimes be a useful manual approach. Automated tools can also reveal the storage layout of a contract and help verify that no storage collision happens as a result of a contract upgrade.
Links
- Furucombo (Related writeups here and here)
- Audius (Related writeup here)
- Solidity by Example
- Ethernaut Level 6 "Delegation"
- Ethernaut Level 16 "Preservation"
- Ethernaut Level 24 "Puzzle Wallet"
- Underhanded Solidity 2020 entry 4 from Jaime Iglesias
- OpenZeppelin explanation