Understand Solidity Storage in depth

Introduction

The slots in storage are stored as key-value pairs with a length of 256 bits (32 bytes). The default value of each slot is always 0, so we do not need to assign a value to 0 when the new declaration.

Basically, we can barely understand that variables in a contract will be stored in storage as follows:

  • Storage only stores variables, not constant

  • Each slot can store up to 256 bits (32 bytes)

  • The variables in turn are in slot in the order of lower-order (ie from right to left).

  • If the size of the variable exceeds the remaining size of the slot, this variable will be passed to the new slot.

  • Struct creates a new slot, the struct elements are put into the slot in the same way as above.

  • Fixed size array creates a new slot, struct elements are put into the slot in the same way as above.

  • Dynamic size array creates a new slot, this slot only stores the length of the array, while the values in the array will be stored at other locations (we will talk more in detail later)

  • Mapping always creates a new slot to hold a place, the values ​​in the array will be stored in other locations, we will talk more in detail later.

  • String creates a new slot, this slot stores both data & data length, we will talk more in detail later.

Going into practice, we will see that the combination of data types becomes much more complicated.

You may not know:

  • All variables in the contract are stored in storage and are accessible

  • Regardless of whether you have declared the variable privateor internalnot, it only works within the scope of the contract only, on the blockchain are all public. Of course, depending on the data type, there will be different encryption, but basically nothing is private at all.

Fixed size variables

The fixed size variables are single variables, using the basic data types such as uint8, uint16, uint24, uint32, uint64, uint128, uint256, bool, address we have a sample contract:

contract StorageTest {
  uint256 a;
  uint256[2] b;

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

The variables will be stored in storage as follows:

  • ais stored in slot 0 and it occupies this slot due to the size of a256 bits (uint256)

  • boccupies a new slot (slot 1) which contains 2 elements. Because the data type of b is uint256, each element will occupy a slot. This means that b [0] will occupy slot 1, and b [1] will occupy slot 2

  • Struct Entry has just been declared, it does not count

  • cwill occupy a new slot (slot 3) and then put 2 elements in the struct in. Just like bthe above, every element is uint256so each will occupy a slot. The result is c.idoccupied slot 3 and c.valueoccupied slot 4

Another example:

pragma solidity ^0.5.9;

contract StorageTest {
  uint8 public a = 7; //0
  uint16 public b =10; //0
  address public d = 0xbE03bCA4D65A86114668697867982Ecfc76f15F8; //0
  bool public bb = true; //0
  uint64 public c = 15; //0
  uint256 public e = 200; //1
  uint8 public f = 40; //2
  uint256 public g = 789; //3
}

In this contract the variable is not full-slot size as before, so how is it arranged in storage?

  • a 8 bit size, put into slot 0

  • b 16 bit size, put into slot 0

  • d is the address. The address has 40 hexadecimal characters, so 160 bits

  • bb is bool type, only need 1 bit to store, but in solidity, the smallest data type is 8 bits, so bool will also occupy 8 bits; we put it into slot 0

  • c 64 bit, we still can put it into slot 0 because (a+b+d+bb+c = 256 bit)

  • e 256 bit, because slot 0 has been filled by (a,b,d,bb,c) with 256 bit, so e will be stored in slot 1

  • f 8 bit, f has to be stored in slot 2 because of a full of 256 bit of e

  • g 256 bit, put in slot 3

How do we verify this? We will deploy to a testnet network (here I use Ropsten) and then get the data to try.

Slot 0:

truffle(ropsten)> web3.eth.getStorageAt('0x9129970dce1274D3D6FB94B705F21eaAe8fABF1F', 0)
'0x000000000000000f01be03bca4d65a86114668697867982ecfc76f15f8000a07

So, as I've described earlier. It's stored from right to left in Hexadecimal, let's dive right into the analysis

0x000000000000000f01be03bca4d65a86114668697867982ecfc76f15f8000a070x000000000000000f01be03bca4d65a86114668697867982ecfc76f15f8000a07

a is 7 = 07 (Hex to Dec)

b is 10 = 0a (10 hex to Dec is a)

c is 0xbE03bCA4D65A86114668697867982Ecfc76f15F8

d is True = 01

bb is 15 = 0f

truffle(ropsten)> web3.utils.fromDecimal("0xbe03bca4d65a86114668697867982ecfc76f15f800")
'0xbe03bca4d65a86114668697867982ecfc76f15f800'

Slot 1:

e 200 = c8 (Hex to Dec)

truffle(ropsten)> web3.eth.getStorageAt('0x9129970dce1274D3D6FB94B705F21eaAe8fABF1F', 1)
'0x00000000000000000000000000000000000000000000000000000000000000c8'

Slot :2

f 40 = 28 (Hex to Dec)

truffle(ropsten)> web3.eth.getStorageAt('0x9129970dce1274D3D6FB94B705F21eaAe8fABF1F', 2)
'0x0000000000000000000000000000000000000000000000000000000000000028'

Slot 3:

g 789 = 315 (Hex to Dec)

truffle(ropsten)> web3.eth.getStorageAt('0x9129970dce1274D3D6FB94B705F21eaAe8fABF1F', 3)
'0x0000000000000000000000000000000000000000000000000000000000000315'

Dynamic Size variables

Dynamic arrays always occupies a new slot, and this slot will store the length of the array.

Assuming there is an X array, the element in the dynamic array will be stored sequentially from keccak256(X), one important note X is the string of 64 hexa (256 bit) characters, not the uint values.

pragma solidity ^0.5.9;

contract StorageTest {
  uint256 a;     // slot 0
  uint256[3] b;  // slots 1-2-3

  struct Entry {
    uint256 id;
    uint256 value;
  }
  
  Entry c;       // slots 4-5
  uint256[] d;
  Entry[] e;

  constructor () public {
    d.push(1);
    d.push(2);
    d.push(3);
    e.push(Entry(4, 5));
  }
}

Length of d

truffle(ropsten)> web3.eth.getStorageAt("0x22a77C59ab77B431E5c7236A531D032551cc0aD5",6)
'0x0000000000000000000000000000000000000000000000000000000000000003'

Value of `d[0]`

lengthof6="0x0000000000000000000000000000000000000000000000000000000000000006"
d0=web3.utils.soliditySha3(lengthof6)
'0xf652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f'
truffle(ropsten)> web3.eth.getStorageAt("0x22a77C59ab77B431E5c7236A531D032551cc0aD5",d0)
'0x0000000000000000000000000000000000000000000000000000000000000001'

Value of d[1]= d0 + 1

Convert d0 to Decimal to make a summation operation

111414077815863400510004064629973595961579173665589224203503662149373724986687 + 1 = 111414077815863400510004064629973595961579173665589224203503662149373724986688

Decimal of d[1] is 111414077815863400510004064629973595961579173665589224203503662149373724986688

The address of d[1] should be presented in Hexadecimal

d1 = "0xF652222313E28459528D920B65115C16C04F3EFC82AAEDC97BE59F3F377C0D40"
truffle(ropsten)> web3.eth.getStorageAt("0x22a77C59ab77B431E5c7236A531D032551cc0aD5",d1)
'0x0000000000000000000000000000000000000000000000000000000000000002'

Similar to d[2], d[2] = d[1] + 1

111414077815863400510004064629973595961579173665589224203503662149373724986688 + 1 = 111414077815863400510004064629973595961579173665589224203503662149373724986689

Decimal of d[1] is 111414077815863400510004064629973595961579173665589224203503662149373724986689

d2="0xF652222313E28459528D920B65115C16C04F3EFC82AAEDC97BE59F3F377C0D41"
truffle(ropsten)> web3.eth.getStorageAt("0x22a77C59ab77B431E5c7236A531D032551cc0aD5",d2)
'0x0000000000000000000000000000000000000000000000000000000000000003'

Check length of e

truffle(ropsten)> web3.eth.getStorageAt("0x22a77C59ab77B431E5c7236A531D032551cc0aD5",7)
'0x0000000000000000000000000000000000000000000000000000000000000001'

Value of e[0].id

truffle(ropsten)> lengthof7="0000000000000000000000000000000000000000000000000000000000000007"
'0000000000000000000000000000000000000000000000000000000000000007'
truffle(ropsten)> e0=web3.utils.soliditySha3(lengthof7)
'0xa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c688'
truffle(ropsten)> web3.eth.getStorageAt("0x22a77C59ab77B431E5c7236A531D032551cc0aD5",e0)
'0x0000000000000000000000000000000000000000000000000000000000000004'

Value of e[0].value

e[0].value = e[0].id + 1

0xa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c688 + 1 = 0xA66CC928B5EDB82AF9BD49922954155AB7B0942694BEA4CE44661D9A8736C689

truffle(ropsten)> e1="0xA66CC928B5EDB82AF9BD49922954155AB7B0942694BEA4CE44661D9A8736C689"
'0xA66CC928B5EDB82AF9BD49922954155AB7B0942694BEA4CE44661D9A8736C689'
truffle(ropsten)> web3.eth.getStorageAt("0x22a77C59ab77B431E5c7236A531D032551cc0aD5",e1)
'0x0000000000000000000000000000000000000000000000000000000000000005'

Eg:

bool public contact;
bytes32[] public codex;

Mapping

Mapping always takes up a new slot, but this slot doesn't store any value at all!

We have a contract which comprises the mapping f

pragma solidity ^0.5.9;

contract StorageTest {
  uint256 a;     // slot 0
  uint256[2] b;  // slots 1-2

  mapping (uint256 => uint256) f; //slot 3

  constructor () public {
    f[20] = 8;
    f[567] = 133;
  }
}

As we can straightforwardly realize that the mapping f allocated in slot 3

  • f will occupy slot 3

  • f[20] will occupy slot keccak256(hex(20) + hex(7))

  • f[567] will occupy slot keccak256(hex(567) + hex(7))

After an above contract deployed, we check the information of the contract as follows

Last updated