# Research on the Collisions issues between EVM Storage Layout and Upgrade Proxy Pattern 

By [Zer0Luck](https://paragraph.com/@zer0luck-2) · 2023-01-19

---

Goal 🏁
-------

1.  storage layout of EVM Solidity type structure
    
2.  storage slot structure based on target contract analysis
    
3.  storage slot structure utilizing delegatecall and fallback
    
4.  storage slot structure within the upgrade proxy pattern
    
5.  Mitigation and addressing storage collision vulnerabilities.
    

Resource
--------

*   The sample code for this example can be found in the following git repository.
    

[https://github.com/dnsdudrla97/EVM-Storage-Layout-Pwn](https://github.com/dnsdudrla97/EVM-Storage-Layout-Pwn)

Solidity Type-Specific Storage Layout
-------------------------------------

### Storage Internal State Variable Layout Structure

*   State variables are stored in storage in a compact manner, using the same storage slot for multiple values.
    
*   The size of each variable is determined by its type.
    
    *   Except for dynamic-type arrays and mappings, data is stored sequentially, starting with the first state variable.
        
*   Types less than 32 bytes are packed according to the following rules
    
    *   The first item in the storage slot is ordered by lower-order byte.
        
    *   Only as many bytes as are necessary to store the value type are used.
        
    *   If the value type does not fit in the remaining part of the storage slot, it is stored in the next storage slot.
        
    *   Struct and array data always starts on a new slot.
        

    contract SimpleStorageLayout {
      // slots: 0
      uint16 a;
      uint16 b;
      uint16 c;
      uint16 d;
      uint16 e;
      uint16 f;
      uint16 g;
      uint16 h;
      uint16 i;
      uint16 j;
      uint16 k;
      uint16 l;
      uint16 m;
      uint16 n;
      uint16 o;
      uint16 p;
      // slots: 1
      bytes12 calice;
      address alice;
      // slots: 2
      address bob;
      // slots: 3
      uint256 balance;
    }
    

![SimpleStorageLayout contract Storage](https://storage.googleapis.com/papyrus_images/f78c753f31c5533bd0510e42c5248dde6ea5985799203ca4d48c43659b317886.png)

SimpleStorageLayout contract Storage

*   State variables "a-p" specified as `uint16` type use 2 bytes each, for a total of 16 variables that are packed into a single 32-byte sized slot using the 32 byte context. (slots: 0)
    
*   State variables specified as `bytes12` and `address` types use 12 bytes and 20 bytes respectively. Similarly, they are packed into a single 32-byte context slot. (slots: 1)
    
*   Next, state variables specified as `uint256` type use a fixed-size 32-byte context slot. (slots: 3)
    
*   256 bit can be used for all 256-bit hash addresses in Solidity storage and the data storage access method is different from what can be applied to webassembly.
    

### Context Inheritance Storage

*   In the case of contracts using inheritance, the order of state variables starts with the most basic contract and is determined by the `C3 Linearization`, so that a derived variable should come after its base variable. The state variables of other contracts will share the same storage slots.
    

### Dynamic Type Structure Storage Layout

*   Due to sizes that cannot be predicted at compile-time, array types cannot be stored in between other state variables in storage slots.
    
*   Instead, they are treated as taking up only 32 bytes (header slots) and the elements within them are stored in other storage slots, starting with a keccak hash calculation.
    
*   Contracts start storing slots from slot 0 and as previously mentioned, basic fixed types occupy one slot (e.g. uint256), if an array is to be stored, the number of elements in the array is stored in a slot referred to as a `header slot` which is 32 bytes.
    
*   You can access the internal data by adding the number of the `header slot` to the keccak hashing of the element's address. This is similar to the way dynamic arrays are stored in C/C++ etc.
    
*   Solidity does not maintain these pointers anywhere and as a result, memory can be written to all storage locations without allocating memory.
    

    contract ArrayStorage {
        uint256 a;
        uint256 b;
        uint256[] blocks;
        
        function init() public {
            blocks[0] = 0x41414141;
            blocks[1] = 0x42424242;
            blocks[2] = 0x43434343;
        }
    }
    

![ArrayStorage contract Storage](https://storage.googleapis.com/papyrus_images/406b7455f152a43acf46e96d3029ccb7bf39411a7dde7948d3a49f398aeedc09.png)

ArrayStorage contract Storage

*   As seen in the diagram, state variables a and b have fixed size slots of 32 bytes and therefore occupy slots 0 and 1.
    
*   The dynamic array `uint256[]` state variable first forms 'header slots' based on the number of internal elements and occupies a slot to store the total length value.
    
*   The internal element data can be accessed in the form of `keccak(slots(2)) + 0`. Similarly, data that exists at index 0, 1 and 2 can be accessed in the same way.
    
*   Mappings have similar mechanisms, with the exception that all values are located separately and the key value is included in the hash calculation.
    
*   Next, using the example of the struct `pos` being declared and being managed in the 2-dimensional mapping structure `positions`. There is a state variable `dimension` of 32 byte size in the middle.
    

    contract StructStorage {
      struct pos {
          uint128 x;
          uint128 y;
          uint256 z;
      }
      uint256 dimension;
      mapping(uint256 => mapping(uint256 => pos)) positions;
    }
    

*   In the above structure, to reference the storage slot for `data[2][3].z`, it can be calculated using the following method.
    

    0. slots:0 => uint256 dimension 
    1. positions[2] == keccak256(uint256(2).uint256(1))
        - reference: index 2
        - first state variable is slot 0
    2. positions[2][3] == keccak256(uint256(3).keccak256(uint256(2).uint256(1)))
    3. positions[2][3].z == keccak256(uint256(3).keccak256(uint256(2).uint256(1))) + 1
            - Since uint128 x and y are 16 bytes each, we can compress them to 32 bytes and use +1
    

### Storage Layout of bytes and string types

*   Bytes and strings are encoded in the same way, and the encoding generally uses the storage slot for the array itself and the data stored at the corresponding storage slot position, which is similar to `bytes1[]`.
    
*   If the length of the element is less than 32 bytes, it is stored in the same slot with the length.
    
*   If the length of the data is a maximum of 31 bytes, the element is stored in the upper byte (left-aligned) and the least significant byte stores the value of `length * 2`.
    
*   For byte arrays that store data, if the length is 32 or more bytes, the main storage slot `p` stores `length * 2 + 1`, and the data is stored in the usual way in `keccak256(p)`.
    

### Internal Memory Layout Structure

*   In Solidity, 4 32-byte slots are used with a specific byte range(including endpoint) that is used as follows:
    

    0x00 ~ 0x3f (64 byte) => hashing method scratch space
    0x40 ~ 0x5f (32 byte) => allocated memory size (free memory space)
    0x60 ~ 0x7f (32 byte) => zer0 slots
    

*   Scratch space can be used in between instructions (inside inline assembly).
    
*   Slot 0 is used as the initial value of dynamic memory arrays and must never be written to. (Available memory pointers point to the initial address of 0x80)
    
*   New objects are always placed in spare memory pointers and memory is never released.
    
*   Elements in memory arrays in Solidity always occupy a multiple of 32 bytes (this also applies to `bytes1[]`, but not for `bytes` and `string` types)
    
*   Multidimensional memory arrays consist of a pointer to the memory array, the length of the dynamic array is stored in the first slot of the array and the array elements follow.
    

### Management of Temporary Memory Area

*   Temporary memory areas larger than 64 bytes are needed.
    
*   There are some tasks that do not fit in the scratch space.
    
*   Use of memory arrays or dynamic memory allocation is required.
    
*   In this case, memory is allocated using a free memory pointer and the size is stored in the first slot.
    
*   The memory pointer is incremented by the size of the allocated memory and the next allocation is done using this memory pointer.
    
*   The allocated memory is cleared when the function exits.
    

### Differences in Code Level Structure of Memory and Storage Layout

**_Array Type Layout_**

    uint8[4] a;
    

*   Storage => 32 bytes (1 slot)
    
*   Memory => 128 bytes (4 slots, 32 bytes each)
    

**_Struct Type Layout_**

    struct s {
        uint a;
        uint b;
        uint8 c;
        uint8 d;
    }
    

*   Storage => 96 bytes (3 slots: 32 bytes each)
    
*   Memory => 128 bytes (4 slots: 32 bytes each)
    

Upgradable Contract Storage Management
--------------------------------------

*   When operating EVM-based smart contracts on various chains, various bugs may occur or updates may be necessary. However, since it is on the blockchain block, it is impossible to modify. In the past, the process of directly deploying new code and entering the newly created contract address for all associated services was cumbersome due to lack of flexibility, but now various proxy design patterns have emerged, making it possible to use updatable smart contracts.
    
*   The idea is to keep the smart contract address and code in an unchangeable state and have the code executed by another contract and return the result.
    
*   It is easy to change the contract state as much as the variable that saves the other contract address, but the code cannot be changed.
    
*   Because there may be several smart contract versions, recording a new version address while keeping the old version address allows users to switch to a new version at any time.
    
*   Next, we will learn the basic `delegatecall, fallback` commands that are essential to understand in Upgradable Proxy Pattern, and analyze the storage slots on a unit basis.
    
*   We will use the foundry framework to analyze the code structure and conduct transaction tests.
    

[https://book.getfoundry.sh/](https://book.getfoundry.sh/)

### Code Level State Variable Storage Layout

*   The next contract will implement the storage layout discussed earlier in a more clear manner by using state variables and dynamic type storage slot allocation at the code level, in order to better understand it.
    

    contract StorageLayout {
      address immutable owner;
      uint256 public flag;
      mapping(address => uint256) public users;
      bytes public key;
    
      constructor(bytes memory _key) {
          owner = msg.sender;
          key = _key;
      }
    
      function setFlags(address _user, uint256 _flag) external {
          require(msg.sender == owner, "[ERR-01] Only owner can set flags");
          users[_user] = _flag;
      }
    }
    

![StorageLayout Contract Storage](https://storage.googleapis.com/papyrus_images/c16fd7bdacd243362ec236a8a0c1e2ce33492d0a86d6e4c1c7562799c16ca94b.png)

StorageLayout Contract Storage

*   As seen in the above contract storage layout, the `address immutable owner` variable is not stored in storage as it uses the `immutable` keyword, instead it is stored in the contract's memory during execution.
    
*   The next variable, `flag`, occupies 1 storage slot and stores its data. The next mapping type, `users`, occupies 1 storage slot as well, with the dynamic type rule resulting in the use of a header slot, which stores the length of elements within the `users` mapping.
    

    slots:0 ------- flag
    sltos:1 ------- users <header slots:length>
    ;; users[addr] reference addr index
    ...
    slots:keccak256(users)+1 ------- users[addr]
    

*   The "users" variable, which corresponds to the mapping and dynamic array type, uses address index as a key and references a uint256 value, which is padded with 32 bytes. Then, it uses the first index, represented by the value 1, padded with 32 bytes and performs a keccak256 hash to determine the slot number, allowing it to reference the corresponding mapping data.
    

    contract StorageLayoutTest is DSTest {
      Vm internal immutable vm = Vm(HEVM_ADDRESS);
    
      StorageLayout internal storageLayout;
      address internal minsu;
    
      function setUp() public {
          minsu = address(uint160(uint256(keccak256(abi.encodePacked("owner")))));
          vm.label(minsu, "Owner");
      }
    
      function testSetFlags() public {
          vm.startPrank(address(minsu));
          storageLayout = new StorageLayout("BBBB");
          storageLayout.setFlags(minsu, uint256(0x41414141));
          vm.stopPrank();
          bytes32 minsuStorageSlot = keccak256(
              abi.encodePacked(uint256(uint160(minsu)), uint256(1))
          );
    
          uint256 minsuStorageSlotValue = uint256(
              vm.load(address(storageLayout), minsuStorageSlot)
          );
    
          assertEq(minsuStorageSlotValue, storageLayout.users(minsu));
      }
    }
    

*   The `setFlags` function is used to map the value of 0x41414141 to the address "minsu" in the `users` mapping variable. Afterwards, `users[address(minsu)]` will contain the value 0x41414141.
    
*   Therefore, we can directly reference the slot number by calculating it through dynamic mapping slot calculation. By referencing the storage in the format `keccak256(abi.encodePacked(uint256(uint160(minsu)), uint256(1)));`, we can confirm that the value is the same by calculating the corresponding mapping slot number and checking the storage.
    

    [PASS] testSetFlags() (gas: 248370)
    Traces:
    [248370] StorageLayoutTest::testSetFlags() 
      ├─ [0] VM::startPrank(Owner: [0x9E326f4F4C1B9057164Ef592671cF0D37C8040C0]) 
      │   └─ ← ()
      ├─ [180298] → new StorageLayout@0x8b10C21F6dEE107A616205e39313147Ee8e0e19A
      │   └─ ← 785 bytes of code
      ├─ [22600] StorageLayout::setFlags(Owner: [0x9E326f4F4C1B9057164Ef592671cF0D37C8040C0], 1094795585) 
      │   └─ ← ()
      ├─ [0] VM::stopPrank() 
      │   └─ ← ()
      ├─ [0] VM::load(StorageLayout: [0x8b10C21F6dEE107A616205e39313147Ee8e0e19A], 0x80b63dade9d4ca379daeb336da1ac63122f9446a350c48d3ecd78eda052432eb) 
      │   └─ ← 0x0000000000000000000000000000000000000000000000000000000041414141
      ├─ [541] StorageLayout::users(Owner: [0x9E326f4F4C1B9057164Ef592671cF0D37C8040C0]) [staticcall]
      │   └─ ← 1094795585
      └─ ← ()
    

*   For simple contracts like `StorageLayout`, the variables can be manually and easily calculated, however, for more complex contracts that inherit or have multiple variables stored in the same slot, it can become more difficult to work with.
    
*   For more complex contract level, there is an option provided by the foundry command 'storage-layout' that allows you to check the storage layout.
    

    ⚡ forge inspect --pretty StorageLayout storage-layout 
    | Name  | Type                        | Slot | Offset | Bytes | Contract                                   |
    |-------|-----------------------------|------|--------|-------|--------------------------------------------|
    | flag  | uint256                     | 0    | 0      | 32    | src/test/StorageLayout.t.sol:StorageLayout |
    | users | mapping(address => uint256) | 1    | 0      | 32    | src/test/StorageLayout.t.sol:StorageLayout |
    | key   | bytes                       | 2    | 0      | 32    | src/test/StorageLayout.t.sol:StorageLayout |
    

### Code Level delegatecall Storage Layout

*   It is possible to upgrade a contract and change its logic in cases where the code is not modifiable by using the delegatecall command, which allows for the execution of using other contract storage.
    

    contract ImplFlagManagerV1 {
      uint256 public flag;
    
      function getFlag() external view returns (uint256) {
          return flag;
      }
    
      function setFlag(uint256 _flag) external {
          require(0 < _flag, "[ERR-01] Invalid flag");
          flag = _flag;
      }
    }
    
    contract DelegateCallFlagManager {
      uint256 public flag;
    
      function getFlag() external view returns (uint256) {
          return flag;
      }
    
      function delegateCallSet(ImplFlagManagerV1 _impl, uint256 _flag) external {
          bytes memory payload = abi.encodeWithSignature(
              "setFlag(uint256)",
              _flag
          );
          console.logBytes(payload);
          (bool _success, ) = address(_impl).delegatecall(payload);
          require(_success, "[ERR-02] Delegate call failed");
      }
    }
    

*   The `ImplFlagManagerV1` contract uses the 'flag' state variable and allows for the retrieval of the 'flag' variable through the use of the `getFlag()` function and the updating of the 'flag' variable with values greater than zero through the use of the `setFlag(uint256)` function.
    
*   The `DelegateCallFlagManager` contract is similar to the `ImplFlagManagerV1` contract, with the exception of the `delegateCallSet` function.
    

![ImplFlagManagerV1, DelegateCallFlagManager contract storage layout](https://storage.googleapis.com/papyrus_images/86b2d17cfdfc9fe15ad797f9cf749ab172769141fba1c8b53cbcb348685a1d94.png)

ImplFlagManagerV1, DelegateCallFlagManager contract storage layout

*   The `delegateCallSet` function in the `DelegateCallFlagManager` contract uses the `delegatecall` to perform the logic of the `setFlag` function in the `ImplFlagManagerV1` contract, using the storage of the `DelegateCallFlagManager` contract.
    
*   Both contracts should have compatible storage layouts, meaning they should have the same variables assigned to the same storage slots.
    
*   The `delegatecall` is a low-level primitive and possess the following specifications.
    

[https://www.evm.codes/#f4?fork=merge](https://www.evm.codes/#f4?fork=merge)

*   When calling a function in a contract, both the function and the parameters being passed need to be specified. This information needs to be encoded in a well-known format, so that the target contract can interpret it. (ABI encoding)
    
*   In the case of normal function calls, Solidity performs this encoding automatically, but when using `delegatecall`, it must be done manually.
    
*   To understand the internal logic of the `delegateCallSet` function, you should check the description below.
    

    bytes memory payload = abi.encodeWithSignature(
        "setFlag(uint256)",
        _flag
    );
    // <abi.encodeWithSignature>
    // parms1 : function signature
    // parms2 : function parameter
    
    // 0xd40c79f00000000000000000000000000000000000000000000000000000000041414141
    

*   If we look at the encoded structure, the first 4 bytes represent the function signature, and the next 32 bytes represent the parameters. It is currently set to 0x41414141
    

    cast keccak "setFlag(uint256)"
    0xd40c79f00eec2930b946749c3d40887e066d1cf5a1d633e066df20cd9e50491d
    

*   This encoded function data can be passed through a `delegatecall`.
    

    (bool _success, ) = address(_impl).delegatecall(payload);
    

*   This line of code is executing the `ImplFlagManagerV1.setFlag(uint256)` function in the context of the current contract.
    
*   Because it is executed using `delegatecall`, it uses the storage of the calling contract, `DelegateCallFlagManager`.
    
*   `delegatecall` returns two values: a boolean indicating whether the call was successful, and a byte array containing the returned data.
    
*   As there is no separate return handling in this case, you only need to check the success of the call. This is important because when using `delegatecall`, the call's failures are not automatically propagated.
    

    //...
    function delegateCallGet(ImplFlagManagerV1 _impl)
        external
        returns (uint256)
    {
        bytes memory payload = abi.encodeWithSignature("getFlag()");
        console.logBytes(payload);
        (bool _success, bytes memory _ret) = address(_impl).delegatecall(
            payload
        );
        require(_success, "[ERR-02] Delegate call failed");
    
        return abi.decode(_ret, (uint256));
    }
    //...
    

*   Next, we will similarly process the `getFlag()` function through the `delegatecall` command. In this case, since the function retrieves the `flag` data, we will need to handle the return.
    
*   Depending on the return type of the called function, we need to decode it to obtain the byte array in the correct order.
    
*   To handle the data returned by the `delegatecall` call stored in `_ret`, we will cast it to the return type of `getFlag()` which is `uint256`, it's a fixed width type and it's simply represented by big endian, so it's encoded as 32 bytes padded, so we can cast it and decode it to get the result.
    

    return abi.decode(_ret, (uint256));
    

*   The `abi.decode()` function uses two parameters, a byte array containing the encoded value, and a tuple containing the encoded types.
    

**Generalizing delegatecall return handling**

*   In order to use `delegatecall` with any function, it must be able to handle arbitrary return data as well. This must be implemented at the assembly level.
    

    assembly {
        let data := add(_ret, 0x20)
        let size := mload(_ret)
        return(data, size)
    }
    

*   In summary, the `DelegateCallFlagManager` contract uses the `delegatecall` command to execute the logic of the `ImplFlagManagerV1` contract's `setFlag` and `getFlag` functions, while using its own storage. This allows for the logic of the `ImplFlagManagerV1` contract to be updated without modifying its code. The `abi.decode()` function is used to decode the returned data from the `delegatecall` command, and the `return(data, size)` command is used to copy a specified amount of memory starting at a certain address. The returned data can be accessed by referencing memory variables such as `_ret`. This technique allows for the data returned from a `delegatecall` to be easily passed without knowing the encoded value's type.
    

    //...
    function delegateCallGenericGet(ImplFlagManagerV1 _impl)
        external
        returns (uint256)
    {
        bytes memory payload = abi.encodeWithSignature("getFlag()");
        (bool _success, bytes memory _ret) = address(_impl).delegatecall(
            payload
        );
        require(_success, "[ERR-02] Delegate call failed");
        assembly {
            let data := add(_ret, 0x20)
            let size := mload(_ret)
            return(data, size)
        }
    }
    //...
    

### Code-Level fallback function for proxy pattern

*   The fallback function is another useful feature when implementing upgradeable contracts. It allows developers to specify what should happen when a non-existent function is called. The default behavior is to throw an error, but it can be changed.
    

    interface IFallBack {
        function client() external;
    
        function server() external;
    }
    
    contract System {
        event Log(string message);
    
        function client() external {
            emit Log("client, Hello");
        }
    
        fallback() external {
            emit Log("[Fallback] call server");
        }
    }
    

*   The `IFallBack` interface has defined the `client()` and `server()` functions, and a contract named `System` has been defined that includes the `client()` function and the fallback function.
    

    IFallBack ifallback = IFallBack(address(new System()));
    ifallback.client(); // => Log("client, Hello")
    ifallback.server(); // => Log("[Fallback] call server")
    

*   A new instance of the contract `System` can be created and cast as `IFallBack` to call both the `client()` and `server()` functions.
    
*   If the `server()` function, which is not defined in the contract `System`, is called, the fallback function will be called instead.
    
*   One useful fact is that you can access the original call data that triggered the fallback function by using [msg.data](http://msg.data/).
    
*   For example, if the fallback function of the contract `System` is triggered, by checking `msg.data` you can find the signature of the `server()` function, which means that you can determine what function the user intended to call by inspecting msg.data.
    

### Upgradeable Contract Structure

*   By using both delegatecall and fallback functions, a general solution for upgradeable contracts based on proxy can be obtained.
    
*   Two contracts, the proxy contract and the logic contract, are actually deployed for each contract that can be upgraded.
    
*   The proxy contract is the one that stores all the data and the logic contract contains the functions that operate on that data.
    
*   Users interact only with the proxy contract
    
*   When a user calls a function on the proxy, the proxy uses delegatecall to pass the call to the logic contract
    
*   Because the proxy uses delegatecall, the logic contract can execute the function and affect the storage of the proxy contract.
    
*   When using upgradeable contracts, the proxy holds the state and the logic contract holds the code.
    
*   From the user's perspective, the proxy behaves the same as the logic contract. Upgrading the contract simply means that the proxy points to a new logic contract.
    

    contract FallBackProxy {
      address public impl;
    
      function upgradeTo(address _impl) external {
          impl = _impl;
      }
    
      fallback() external payable {
          (bool _success, bytes memory _ret) = impl.delegatecall(msg.data);
          require(_success, "[ERR-02] Delegate call failed");
    
          assembly {
              let data := add(_ret, 0x20)
              let size := mload(_ret)
              return(data, size)
          }
      }
    }
    

*   Based on the theory previously described, the proxy is set up, but there are a few errors.
    
*   The proxy has a single storage variable implementation that stores the address of the logic contract.
    
*   By calling the `upgradeTo` function, the logic contract can be changed and upgraded. (Of course, as the authorization system is not set up now, anyone can call it, which is not desirable.)
    
*   The fallback function uses delegatecall to pass all calls to the logic contract. (Calls to the upgradeTo() function, which is processed by the proxy itself, are excluded) But how can we know which function the user intended to call?
    
*   The original origin calldata that triggered the fallback function can be accessed via msg.data.
    
*   Calldata includes both the function signature and the parameter values, so it is easy to pass [msg.data](http://msg.data/) to delegatecall and it is essential to handle exceptions to see if the call was successful.
    

    function testFallBackProxy() public {
        ImplFlagManagerV1 impl = new ImplFlagManagerV1();
        FallBackProxy proxy = new FallBackProxy();
    
        vm.expectEmit(false, false, false, true);
        proxy.upgradeTo(address(impl));
        vm.expectEmit(false, false, false, true);
        ImplFlagManagerV1(address(proxy)).setFlag(0x41414141);
    
        ImplFlagManagerV1(address(proxy)).getFlag();
    }
    

*   The `ImplFlagManagerV1` and `FallBackProxy` contracts are set up based on logic and proxy contracts, respectively. The `upgradeTo` function is used to update the address of the logic contract, `address public impl;`, in the proxy contract.
    
*   When the `ImplFlagManagerV1` is casted to the proxy contract address and the `setFlag(0x41414141)` function is called, the fallback function is executed because the `setFlag()` function is not defined. In the [msg.data](http://msg.data/), the following data is included.
    

*   The `setFlag(uint256)` function signature and the parameter 0x41414141.
    
*   Updating the proxy contract storage also updates the logic contract storage, as the storage slots are the same. This results in manipulating the value of the incorrect `address public impl;` state variable.
    
*   Finally, calling `ImplFlagManagerV1(address(proxy)).getFlag()` will result in an exception, causing the transaction to fail.
    

![ImplFlagManagerV1, FallBackProxy Contract Storage](https://storage.googleapis.com/papyrus_images/0a2e44ee1fd1e1e320733994c36b97a77ccf9964344dd6c7bbbe72bfb2f72e20.png)

ImplFlagManagerV1, FallBackProxy Contract Storage

*   When comparing the storage layout of the two contracts, it can be seen that different variables are stored in slot 0. This results in a unfortunate outcome where the `ImplFlagManagerV1.setFlag()` function modifies the 0th slot of the storage of the proxy contract when executed in order to update.
    
*   In the proxy contract, the 0th slot contains the address of the logic contract for upgrading and thus, when `ImplFlagManagerV1(address(proxy)).setFlag(0x41414141)` is executed, the actual logic contract address becomes the address + 0x41414141 and references a different address.
    
*   Specifically, it now points to an undeployed logic contract address, so a delegatecall call is made to a blank account but no data is returned.
    

### Solutions to Resolve Slot Collision in Upgradable Proxy Contracts

*   One solution to this issue is to use an unstructured storage pattern, which provides a solution to the problem without requiring changes to the logic contract.
    
*   The idea is for the proxy to store the logic contract address in a slot that is far enough away from the possible slot collision, so that the proxy can ignore the possibility of collision.
    
*   Other solutions include using design patterns like UUPS, transparent proxies, and diamond patterns, but these require changes to the logic contract.
    
*   However, these design patterns can be difficult to implement, especially if the logic contract is inherited from other contracts.
    
*   A simple but not optimal solution would be to insert dummy storage variables in the conflicting section, but this may be difficult to apply if the logic contract is inherited from other contracts.
    

    contract UpgradeProxy {
      bytes32 constant RAND_IMPL_SLOT =
          bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1);
    
      function upgradeTo(address _newImplementation) external {
          bytes32 slot = RAND_IMPL_SLOT;
          assembly {
              sstore(slot, _newImplementation)
          }
      }
    
      function implementation() public view returns (address _impl) {
          bytes32 slot = RAND_IMPL_SLOT;
          assembly {
              _impl := sload(slot)
          }
      }
    
      fallback() external payable {
          (bool _success, bytes memory _ret) = implementation().delegatecall(
              msg.data
          );
          require(_success, "[ERR-02] Delegate call failed");
    
          assembly {
              let data := add(_ret, 0x20)
              let size := mload(_ret)
              return(data, size)
          }
      }
    }
    

*   The main difference between this proxy contract and previous ones is that it does not declare any storage variables. Instead, the address of the logic contract is stored in the `RAND_IMPL_SLOT` slot, which is calculated by taking the keccak256 hash of the `eip1967.proxy.implementation` string and subtracting 1.
    
*   According to the EIP1967 standard, if a custom slot is used to store the logic contract, services like Etherscan can automatically detect the contract proxy and display information about both the proxy and logic contract.
    
*   To upgrade the contract, the `upgradeTo()` function must modify the address of the slot provided by `RAND_IMPL_SLOT` using the `sstore` command.
    
*   To prevent proxy storage conflicts, it is necessary to check that the layout is clearly compatible.
    
*   In fact, the proxy is completely independent of the specific logic contract used. It is enough to write a single proxy that can be used by everyone.
    
*   On the other hand, for types such as dynamic arrays that are matched and dynamically resized using keccak hash, the handling may be slightly different.
    
*   Slots calculated in this way can actually cause collisions with `RAND_IMPL_SLOT`.
    
*   The general consensus is that these occurrences are not considered a problem as the likelihood of this happening is low.
    

1.  Proxies are vulnerable to function signature collisions that can lead to unexpected behavior.
    
2.  `UpgradeTo` function allow anyone to upgrade the contract, and since the behavior of the contract can change significantly during an upgrade, this poses a significant security threat that needs to be addressed in all proxy implementations.
    

![ImplFlagManagerV1, UpgradeProxy Contract Storage](https://storage.googleapis.com/papyrus_images/3e1b0e776dee2797ca3db93803cb05937b9c40c8bf2805b48d5507ff75dd8245.png)

ImplFlagManagerV1, UpgradeProxy Contract Storage

### Upgradeable proxy pattern initialization process

*   In the previous contract `ImplFlagManagerV1`, it was used as a logic contract, but it was simple enough to not have a user-defined constructor. This allows important limitations that occur when using a proxy to be successfully ignored.
    
*   The constructor is not actually a function, so it cannot be called by delegatecall. Therefore, a separate initialization function is used.
    

    contract ImplFlagManagerV1 {
      bool public Initialized;
      uint256 public flag;
    
      function initialize(uint256 _flag) external {
          require(!Initialized, "[ERR-01] Already initialized");
          Initialized = true;
          flag = _flag;
      }
    
      function getFlag() external view returns (uint256) {
          return flag;
      }
    
      function setFlag(uint256 _flag) external {
          require(0 < _flag, "[ERR-01] Invalid flag");
          flag = _flag;
      }
    }
    

*   To make the proxy pattern upgradeable, we added an `Initialized` storage variable and `initialize(uint256)` function. Unlike the constructor, the `initialize(uint256)` function is a regular function and can be called multiple times.
    
*   Because security-sensitive parameters are often set during initialization, we use the `Initialized` variable to prevent re-initialization.
    

    contract ImplFlagManagerV2 {
      bool public Initialized;
      uint256 public flag;
    
      function initialize(uint256 _flag) external {
          require(!Initialized, "[ERR-01] Already initialized");
          Initialized = true;
          flag = _flag;
      }
    
      function getFlag() external view returns (uint256) {
          return flag;
      }
    
      function setFlag(uint256 _flag) external {
          require(0 < _flag, "[ERR-01] Invalid flag");
          flag = _flag >> 1;
      }
    }
    

    function testUpgradeProxy() public {
        ImplFlagManagerV1 logicV1 = new ImplFlagManagerV1();
        UpgradeProxy Uproxy = new UpgradeProxy();
        Uproxy.upgradeTo(address(logicV1));
    
        ImplFlagManagerV1 Uproxy_logicV1 = ImplFlagManagerV1(address(Uproxy));
        Uproxy_logicV1.initialize(0x41414141);
    
        assertEq(Uproxy_logicV1.getFlag(), 0x41414141);
    
        Uproxy_logicV1.setFlag(0x42424242);
        assertEq(Uproxy_logicV1.getFlag(), 0x42424242);
    
        ImplFlagManagerV2 logicV2 = new ImplFlagManagerV2();
        Uproxy.upgradeTo(address(logicV2));
    
        Uproxy_logicV1.setFlag(0x41414141);
        assertEq(Uproxy_logicV1.getFlag(), (0x41414141 >> 1));
    }
    

*   The previous steps involve deploying the logic and proxy contracts, and using the proxy contract to connect to the logic contract using the `upgradeTo` function (as per EIP1967).
    
*   After initializing the `ImplFlagManagerV1` logic contract through the proxy contract using the `initialize(0x41414141)` function, the `getFlag()` function is called to confirm that it returns the same value.
    
*   The `ImplFlagManagerV2` contract, which has new features, can then be upgraded to through the proxy contract and the verification conditions can be confirmed to have been passed.
    

### Solutions for Collision Vulnerabilities in Each Design Pattern

*   How to solve storage collisions between proxy and logic contracts?
    
    *   Eternal Storage
        
    *   Inherited Storage
        
    *   Unstructured Storage
        
    
    [https://blog.openzeppelin.com/proxy-patterns/](https://blog.openzeppelin.com/proxy-patterns/)
    
*   How to solve function signature collisions between proxy and logic contracts?
    
    *   Function signatures are derived from the keccak hash and internally identified by the 4-byte function signature. This can cause signature collisions when different functions are mapped to the same function signature.
        
    *   Single contract's two function signature collisions will cause a compiler error and stop, so this is generally not a problem. However, when two functions from different contracts collide, no exception occurs and a problem occurs.
        
    
    [https://medium.com/nomic-foundation-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357](https://medium.com/nomic-foundation-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357)
    
    *   To solve this, the recommended proxy design patterns are as follows:
        
        *   Transparent Proxy
            
        
        [https://blog.openzeppelin.com/the-transparent-proxy-pattern/](https://blog.openzeppelin.com/the-transparent-proxy-pattern/)
        
        *   UUPS (EIP1822)
            
        
        [https://eips.ethereum.org/EIPS/eip-1822](https://eips.ethereum.org/EIPS/eip-1822)
        
        *   Beacon Proxy
            
        
        [https://docs.openzeppelin.com/contracts/4.x/api/proxy#beacon](https://docs.openzeppelin.com/contracts/4.x/api/proxy#beacon)
        
        *   Diamond Proxy (EIP2535)
            
        
        [https://eips.ethereum.org/EIPS/eip-2535](https://eips.ethereum.org/EIPS/eip-2535)

---

*Originally published on [Zer0Luck](https://paragraph.com/@zer0luck-2/research-on-the-collisions-issues-between-evm-storage-layout-and-upgrade-proxy-pattern)*
