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
private
orinternal
not, 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:
The variables will be stored in storage as follows:
a
is stored in slot 0 and it occupies this slot due to the size ofa
256 bits (uint256)b
occupies a new slot (slot 1) which contains 2 elements. Because the data type of b isuint256
, each element will occupy a slot. This means that b [0] will occupy slot 1, and b [1] will occupy slot 2Struct Entry has just been declared, it does not count
c
will occupy a new slot (slot 3) and then put 2 elements in the struct in. Just likeb
the above, every element isuint256
so each will occupy a slot. The result isc.id
occupied slot 3 andc.value
occupied slot 4
Another example:
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 0b
16 bit size, put into slot 0d
is the address. The address has 40 hexadecimal characters, so 160 bitsbb
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 0c
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 1f
8 bit, f has to be stored in slot 2 because of a full of 256 bit ofe
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:
So, as I've described earlier. It's stored from right to left in Hexadecimal, let's dive right into the analysis
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
Slot 1:
e
200 = c8 (Hex to Dec)
Slot :2
f
40 = 28 (Hex to Dec)
Slot 3:
g
789 = 315 (Hex to Dec)
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.
Length of d
Value of `d[0]`
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
Similar to d[2]
, d[2] = d[1] + 1
111414077815863400510004064629973595961579173665589224203503662149373724986688 + 1 = 111414077815863400510004064629973595961579173665589224203503662149373724986689
Decimal of d[1]
is 111414077815863400510004064629973595961579173665589224203503662149373724986689
Check length of e
Value of e[0].id
Value of e[0].value
e[0].value = e[0].id + 1
0xa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c688 + 1 = 0xA66CC928B5EDB82AF9BD49922954155AB7B0942694BEA4CE44661D9A8736C689
Eg:
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
As we can straightforwardly realize that the mapping f
allocated in slot 3
f
will occupy slot 3f[20]
will occupy slotkeccak256(hex(20) + hex(7))
f[567]
will occupy slotkeccak256(hex(567) + hex(7))
After an above contract deployed, we check the information of the contract as follows
Last updated