Solidity Storage Layout

Introduction to Ethereum storage

Each Ethereum account has its storage, excluding EOA which doesn't have any storage.

The storage layout is divided into slots, starting from slot 0 . There are 2^256 available slots in the storage, each slot is 32 bytes of data. Cause there are a limited number of slots, data collision may happens, we will talk about this later.

State variables are stored differently in the storage, based on rules:

  • Multiple contiguous items that need less than 32 bytes are packed into a single storage slot if possible.
  • The variables in turn are in slot in the order of lower-order (ie from right to left).
  • If a value type does not fit the remaining part of a storage slot, it is stored in the next storage slot.
  • Structs and array data always start a new slot and their items are packed tightly according to these rules.
  • Items following struct or array data always start a new storage slot.

You may not know

  • All state variables in the storage are accessible, whether they are defined public or private. It's recommended not to storage any sensitive informations in the contract storage.
  • When working with variables that are smaller than 32 bytes, your contract's gas usage must be higher. The reason is EVM operations on 32 bytes, so if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.
  • It's better to pack variables fit into one slot if the combination of the data size is smaller than 32 bytes. For example, the order uint128, uint128, uint256 is better than uint128, uint256, uint128 cause the former takes two slots while the latter takes three slots.

Dynamic arrays and mapping

Dynamic arrays

Dynamic array takes one slot to store the length of the array. Assume the slot p stores the length of the array, the slot location for element index i in the array is calculated equal to uint256(keccak256(p)) + i.

Mapping

Mapping takes one slot but nothing is stored at that slot. It is needed to ensure that even if there are two mappings next to each other, their content ends up at different storage locations. Assume mapping is located at slot p, so the slot location for value of key k is calculated equal to keccak256(h(k) + p) where h is function applied to key k depending on its type:

  • For value types, h pads the value to 32 bytes in the same way as when storing the value in memory.
  • For strings and byte arrays, h(k) is just the unpadded data.

bytes and string

bytes and string are encoded identically. In general, the encoding is similar to bytes1[], in the sense that there is a slot for the array itself and a data area that is computed using a keccak256 hash of that slot’s position. However, for short values (shorter than 32 bytes) the array elements are stored together with the length in the same slot.

Example

You can find my example in this repository Solidity Storage

References

Solidity documentation