_Follow along with the video lesson:_ --- ### Exploit - Storage Collision - Remix Example To gain an even better understanding of the storage collision vulnerability, let's take a look at how it works with a [**Remix example**](https://remix.ethereum.org/#url=https://github.com/Cyfrin/sc-exploits-minimized/blob/main/src/storage-collision/StorageCollision.sol&lang=en&optimize=false&runs=200&evmVersion=null&version=soljson-v0.8.20+commit.a1b79de6.js). StorageCollision.sol can be broken down into 3 distinct contracts. - StorageCollisionProxy - This is the simple proxy contract for the protocol. It contains a setImplementation function and a few helper functions, including readStorage, which allows us to read from a storage slot, instead of a variable assignment. - Implementation A - Allows user to setValue, storing a uint256. - Implementation B - An upgrade to Implementation A, stores a modified version of a user's value, possesses a new `initialized` boolean. <details> <summary>StorageCollision.sol</summary> ```js // SPDX-License-Identifier: MIT pragma solidity 0.8.20; import {Proxy} from "@openzeppelin/contracts/proxy/Proxy.sol"; contract StorageCollisionProxy is Proxy { bytes32 private constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; function setImplementation(address newImplementation) public { assembly { sstore(_IMPLEMENTATION_SLOT, newImplementation) } } function _implementation() internal view override returns (address implementationAddress) { assembly { implementationAddress := sload(_IMPLEMENTATION_SLOT) } } // helper function function getDataToTransact(uint256 numberToUpdate) public pure returns (bytes memory) { return abi.encodeWithSignature("setValue(uint256)", numberToUpdate); } function readStorage(uint256 storageSlot) public view returns (uint256 valueAtStorageSlot) { assembly { valueAtStorageSlot := sload(storageSlot) } } receive() external payable { _fallback(); } } contract ImplementationA { uint256 public value; function setValue(uint256 newValue) public { value = newValue; } } contract ImplementationB { // Ah!!! This will ruin the storage of our proxy!! bool public initialized; // Why did we switch the order?? uint256 public value; function setValue(uint256 newValue) public { value = newValue + 2; } } ``` </details> Of note, the `initialized` value will typically default to `0` or `false`, if any number is assigned to this variable, it's going to return `true`. A contract reporting that it's initialized, when it's not, is a very risky situation. Compile `StorageCollision.sol` and deploy our 3 contracts (Proxy.sol will show up in your list of contracts to deploy, you can ignore this file). We can then set `Implementation A` via `StorageCollision::setImplementation`. ::image{src='/security-section-6/45-exploit-storage-collision-remix-example/exploit-storage-collision-remix-example1.png' style='width: 100%; height: auto;'} In order to interact with `Implementation A` in Remix now, we need to copy the address of our StorageCollisionProxy deployment, and add it to the `At Address` field. This will provide us a new contract field with which to interact. ::image{src='/security-section-6/45-exploit-storage-collision-remix-example/exploit-storage-collision-remix-example2.png' style='width: 100%; height: auto;'} Go ahead and set any number (less than type(uint256).max đ) as the value. ::image{src='/security-section-6/45-exploit-storage-collision-remix-example/exploit-storage-collision-remix-example3.png' style='width: 100%; height: auto;'} Update the `At Address` field to configure our contract in Remix to be interacted with via our StorageCollisionProxy. ::image{src='/security-section-6/45-exploit-storage-collision-remix-example/exploit-storage-collision-remix-example4.png' style='width: 100%; height: auto;'} From here, we should be able to updated the implementation address of our protocol by calling `setImplementation` and passing the address for `Implementation B`. We expect the logic of the protocol to change, but elements in storage should remain the same. Storage is handled by our proxy. However, if we call `value` after setting our new implementation address, we see it's actually `0`. ::image{src='/security-section-6/45-exploit-storage-collision-remix-example/exploit-storage-collision-remix-example5.png' style='width: 100%; height: auto;'} Worse than this, our new `initialized` variable seems to have defaulted to `true`! **_What's going on here?!_** In `Implementation B`, we can see that the `bool initialized` variable now holds the position that would be assigned to storage slot 0 and our value variable now resides in storage slot 1. ```js contract ImplementationA { uint256 public value; ``` ```js contract ImplementationB { // Ah!!! This will ruin the storage of our proxy!! bool public initialized; // Why did we switch the order?? uint256 public value; ``` This means, when we're checking our `initialized` value, storage slot 0 of our proxy is being referenced...but slot 0 isn't empty! Slot 0 contains the value we set from a previous implementation, which means `initialized` reads true! We can confirm this by interacting with our StorageCollisionProxy contract and calling `readStorage` and passing our storage slot. ::image{src='/security-section-6/45-exploit-storage-collision-remix-example/exploit-storage-collision-remix-example6.png' style='width: 100%; height: auto;'} Oh my goodness. This should illustrate clearly what a `storage collision` issue looks like, and our `ThunderLoanUpgraded` is falling for it hard. Let's work on a PoC to showcase this in our audit report together, in the next lesson. See you there!
Patrick explains how to set up and run an assertion test for detecting storage collisions during smart contract upgrades.
Previous lesson
Previous
Next lesson
Next
Give us feedback
Solidity Developer
Smart Contract SecurityDuration: 25min
Duration: 1h 18min
Duration: 35min
Duration: 2h 28min
Duration: 5h 03min
Duration: 5h 22min
Duration: 4h 33min
Duration: 2h 01min
Duration: 1h 40min
Testimonials
Read what our students have to say about this course.
Chainlink
Chainlink
Gustavo Gonzalez
Solutions Engineer at OpenZeppelin
Francesco Andreoli
Lead Devrel at Metamask
Albert Hu
DeForm Founding Engineer
Radek
Senior Developer Advocate at Ceramic
Boidushya
WalletConnect
Idris
Developer Relations Engineer at Axelar