When upgrading a proxy to a new implementation, the storage layouts of both contracts must be compatible. The existing storage layout cannot be modified. For example, if the first implementation stores an address principal
variable in the first storage slot, then the new implementation must also store the same variable in the same slot. Subsequent storage slots must follow the same order in both contracts. The only allowed change is appending new state variables after the existing ones.
Violating this rule can lead to unintended changes in variable values due to storage collisions.
Upgrading the implementation to LevelTwo
will result in a storage collision.
Inspired by the OpenZeppelin documentation (https://docs.openzeppelin.com/upgrades-plugins/proxies), we can illustrate example where potential storage collision issues might occur.
LevelOne | LevelTwo | |
---|---|---|
address principal | address principal | |
bool inSession | bool inSession | |
uint256 schoolFees | uint256 public sessionEnd | <== Storage collision |
... | ... |
A problem occurs during the upgrade to LevelTwo
if the sessionEnd
variable occupies the same storage slot as schoolFees
in the proxy contract, leading to unintended value changes.
Note The provided contracts are not upgradeable in their original form, so the code shown below has been modified from the competition version to demonstrate the storage collision vulnerability.
In the LevelOne.sol
file, the graduateAndUpgrade
function needs to be modified.
In the LevelTwo.sol
file, we need to add the following code to make the contract compatible with the EIP-1967 standard.
Add the following code to the LevelOneAndGraduateTest.t.sol
file, inside the LevelOneAndGraduateTest
contract.
The schoolFees
variable from the LevelOne
contract and the sessionEnd
variable from the LevelTwo
contract are stored in the same slot. This results in a storage collision, where the sessionEnd
variable in the LevelTwo
contract overwrites the value of the sessionEnd
variable from the LevelOne
contract.
Manual Review
Foundry
The order and number of storage variables in the LevelTwo
contract must match those in the LevelOne
contract. This ensures that all variables from the first implementation are stored in the same slots in the second implementation.
Any additional variables in the second contract should be appended after the variables already declared in the first implementation.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.