Finding Storage Slots

Translating account storage from Solidity to the EVM.

Smart contracts on Ethereum store their state in contract storage, which in the EVM is organized as a uint256 => uint256 mapping. Higher level languages like Solidity, Viper, and Yul map storage variables to key-value pairs in this mapping, known as storage slots, and the assignment of variables in each high level language to slots is determined by the compiler. In this page, we explain how this mapping works in Solidity.

Finding the Storage Layout

For a given smart contract, the storage layout can be determined using Foundry for storage variables or mappings using forge inspect or cast index:

# show the storage layout of a contract
forge inspect MyContract.sol:MyContract storage --pretty

# find the storage slot of a key in a mapping
# cast index [key_type] [key] [slot]
cast index string "hello" 1

Alternatively, you can run solc using the storageLayout option:

solc MyContract.sol --storage-layout

For any live contract on mainnet, the storage layout is shown at evm.storage.

Storage Layout Rules

The storage layout is determined according to rules specified in the Solidity documentation. To summarize these rules, they are determined by:

  • Storage variables are assigned slots by their order of appearance in a smart contract. If a contract uses inheritance, then variables are ordered starting from the most base-ward contract according to C3-linearization.

  • Each slot has size 32 bytes.

  • New variables are aligned to the start of a slot.

  • Multiple variables can be stored in a single slot, in which case they are concatenated in right-to-left order without separators.

  • Mappings, strings, and dynamic arrays are considered to occupy a single slot. If one of these contract variables lies in slot p, then:

    • For dynamic arrays:

      • Slot p stores the length.

      • Array data is stored in slots starting at keccak256(p) sequentially, sharing slots if elements are at most 16 bytes.

      • Nested arrays apply this recursively.

    • For mappings:

      • Slot p is empty.

      • The value of key k starts at keccak256(h(k) || p), where:

        • h(k) pads k to 32 bytes if k is a value type

        • h(k) is the unpadded keccak256 hash for strings or byte arrays

    • For bytes/string (string is viewed as bytes1[]):

      • If length is 32 or more bytes, slot p stores length * 2 + 1 and data is in keccak256(p).

      • If length is at most 31 bytes, the lowest order byte stores length * 2 and the highest order bytes store the raw data.

Example Layout for CryptoPunks

We give a worked example for the CryptoPunks smart contract deployed at:

0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb

The state variables and storage slots for this contract are shown below.

Variable
type
bytes
slot
data slot

imageHash

string

32

0

keccak(0)

owner

address

20

1

standard

string

32

2

name

string

32

3

symbol

string

32

4

decimals

uint8

1

5

totalSupply

uint256

32

6

nextPunkIndexToAssign

uint

32

7

allPunksAssigned

bool

1/8

8

punksRemainingToAssign

uint

32

9

punkIndexToAddress

mapping(uint => address)

32

10

keccak(index . 10)

balanceOf

mapping(address => uint256)

32

11

keccak(address . 0..0 . 11)

Last updated