3 min read

Unboxing EVM storage

Unboxing EVM storage

Ethereum smart contracts use a rather abnormal storage model inside EVM. It is not necessarily useful to know and understand how data types are represented at a low level in a typical programming language. Solidity and other EVM-type languages are different as this knowledge is crucial as storage access is expensive in Ethereum.

Gas refers to the unit that measures the amount of computational effort required to execute specific operations on the Ethereum network.

Since each Ethereum transaction requires computational resources to execute, each transaction requires a fee. Gas refers to the fee needed to conduct a transaction on Ethereum successfully.

The most expensive operations in EVM are the ones that deal with storage, writes to storage, and read from it. That’s why it’s essential to understand how storage in EVM looks like.

Smart Contract’s storage

Each smart contract running in EVM has permanent storage that is part of the Ethereum state. This storage can be considered an array, with each item being 256 bits (32 bytes). Why 256 bits, you ask? It’s mainly to facilitate native hashing and elliptic curve operations, but we won’t be going into this.

The storage of each smart contract is 2^256 large, and smart contract can read from that storage and write to it at any location. BUT! It’s a big but… The storage is initially blank, defaulting to zero. It doesn’t cost you anything to have such a large array.

Fixes-sized values

Let’s look at the example contract and understand what is happening with each variable and where it resides in the storage.

contract Test {
    uint256 a;
    uint256[3] b;

    struct Data {
        uint256 id;
        uint256 value;
    }
    Data c;
}

The layout in storage is simple.

  • The variable a is in position 0x0
  • The variable b is in position 0x1
  • As it is fixed-length array, it takes 3 spaces to it goes from 0x1, 0x2, 0x3. One for each element of the array.
  • The variable c starts at position 0x4 and takes two spaces as the Data struct have two variables 32 bytes long.

Storage variable declaration doesn’t cost anything as we don’t store anything in these variables yet. Solidity reserves a position for each of the variables, and you only pay gas when you store anything in it.

Dynamically-sized values

Simple variables and fixed-size state variables are laid out one after another in storage. The thing is a bit different with dynamic arrays and mappings.

Due to unpredictability in the size of said dynamic types, we cannot store them one after another. All items in mappings and arrays are stored at a different location in storage. As I said before, the storage of each smart contract is 2^256 large. That means there are 2^256 locations to chose from to store our dynamic types. For comparison, this is also an approximate number of atoms in the known, observable universe.

We can choose our storage slots at random and don’t worry about the collision. The locations would be so apart from that we can store as much data as we want at each location without worrying about the next one.

Items of the dynamic types are stored starting at a different storage slot that is computed using a Keccak-256 hash. The slot of the dynamic type holds the location of these items. Let’s see below.

contract Test {
    uint256 a; // 0x0
    uint256[3] b; // 0x1-0x3

    struct Data {
        uint256 id;
        uint256 value;
    }
    Data c; // 0x4-0x5
	  Data[] d; // 0x6
    
    mapping(uint256 => uint256) e; // 0x7
}

We extended our previous contract by dynamic array d. The array is at the slot number 0x6, but the only thing that’s store there is the size of the array. Array data is located starting at the hash of the array d and is store in the same way as statically-sized array data would: One element after the other.

Now let’s look at the mapping.

Storage slot of mapping e is at position 0x7, but nothing is actually stored in this location. Location of the actual items are calculated using keccack hash of mapping slot position alongside the key of the mapping.

This also ensures that two mapping will not collide with each other.

Future reading

There are still a lot to cover in terms of EVM storage but if you would like to learn more, here are interesting resources:

I will create another post explaining more complex data structures and how to determine their slots location.


Thanks for reading, and if you like my writing, you can subscribe to my blog to receive the daily newsletter as I’m currently in the middle of 100 days of blogging challenge. Subscription box below 👇

If the newsletter is not your thing, check out my Twitter @adrianhetman, where I post and share exciting news from the Blockchain world and security.

See you tomorrow!