Layout of State Variables in Storage and Transient Storage — Solidity 0.8.33-develop documentation

circle-info

Note

The rules described in this section apply for both storage and transient storage data locations. The layouts are completely independent and don’t interfere with each other’s variable locations. Thus storage and transient storage state variables can be safely interleaved without any side effects. Only value types are supported for transient storage.

Layout of State Variables in Storage and Transient Storage

State variables of contracts are stored in storage in a compact way such that multiple values sometimes use the same storage slot. Except for dynamically-sized arrays and mappings (see below), data is stored contiguously item after item starting with the first state variable, which is stored in slot 0. For each variable, a size in bytes is determined according to its type. Multiple, contiguous items that need less than 32 bytes are packed into a single storage slot if possible, according to the following rules:

  • The first item in a storage slot is stored lower-order aligned.

  • Value types use only as many bytes as are necessary to store them.

  • 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.

For contracts that use inheritance, the ordering of state variables is determined by the C3-linearized order of contracts starting with the most base-ward contract. If allowed by the above rules, state variables from different contracts do share the same storage slot.

The elements of structs and arrays are stored after each other, just as if they were given as individual values.

If a contract specifies a custom storage layout, the slots assigned to static storage variables are shifted according the value defined as the layout base. Locations of dynamic arrays and mappings are also indirectly affected by this due to shifting of the static slots they are based on. The custom layout is specified in the most derived contract and, following the order explained above, starting from the most base-ward contract’s variables, all storage slots are adjusted.

In the following example, contract C inherits from contracts A and B and also specifies a custom storage base slot. The result is that all storage variable slots of the inheritance tree are adjusted according to the value specified by C.

open in Remixarrow-up-right

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.29;

struct S {
    int32 x;
    bool y;
}

contract A {
    uint a;
    uint128 transient b;
    uint constant c = 10;
    uint immutable d = 12;
}

contract B {
    uint8[] e;
    mapping(uint => S) f;
    uint16 g;
    uint16 h;
    bytes16 transient i;
    S s;
    int8 k;
}

contract C is A, B layout at 42 {
    bytes21 l;
    uint8[10] m;
    bytes5[8] n;
    bytes5 o;
}

In the example, the storage layout starts with the inherited state variable a stored directly inside the base slot (slot 42). Transient, constant and immutable variables are stored in separate locations, and thus, b, i, c and d have no effect on the storage layout. Then we get to the dynamic array e and mapping f. They both reserve a whole slot whose address will be used to calculate the location where their data is actually stored. The slot cannot be shared with any other variable, because the resulting addresses must be unique.

The next two variables, g and h, need 2 bytes each and can be packed together into slot 45, at offsets 0 and 2 respectively. Since s is a struct, its two members are packed contiguously, each taking up 5 bytes. Even though they both would still fit in slot 45, structs and arrays always start a new slot. Therefore, s is placed in slot 46 and the next variable, k, in slot 47. Base contracts, on the other hand, can share slots with derived ones, so l does not require an new one. Then variable m, which is an array of 10 items, gets into slot 48 and takes up 10 bytes. n is an array as well, but due to the size of its items, cannot fill its first slot perfectly and spills over to the next one. Finally, variable o ends up in slot 51, even though it is of the same type as items of n. As explained before, variables after structs and arrays always start a new slot.

Putting it all together, the storage and transient storage layouts of contract C can be illustrated as follows:

  • Storage:

open in Remixarrow-up-right

  • Transient storage:

open in Remixarrow-up-right

Note that the storage specifier affects A and B only as a part of C’s inheritance hierarchy. When deployed independently, their storage starts at 0:

  • Storage layout of A:

open in Remixarrow-up-right

  • Storage layout of B:

open in Remixarrow-up-right

00 [eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee] 01 [ffffffffffffffffffffffffffffffff] 02 [ hhgg] 03 [ yxxxx] 04 [ k]

Let us compute the storage location of data[4][9].c. The position of the mapping itself is 1 (the variable x with 32 bytes precedes it). This means data[4] is stored at keccak256(uint256(4) . uint256(1)). The type of data[4] is again a mapping and the data for data[4][9] starts at slot keccak256(uint256(9) . keccak256(uint256(4) . uint256(1))). The slot offset of the member c inside the struct S is 1 because a and b are packed in a single slot. This means the slot for data[4][9].c is keccak256(uint256(9) . keccak256(uint256(4) . uint256(1))) + 1. The type of the value is uint256, so it uses a single slot.

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.

In particular: if the data is at most 31 bytes long, the elements are stored in the higher-order bytes (left aligned) and the lowest-order byte stores the value length * 2. For byte arrays that store data which is 32 or more bytes long, the main slot p stores length * 2 + 1 and the data is stored as usual in keccak256(p). This means that you can distinguish a short array from a long array by checking if the lowest bit is set: short (not set) and long (set).

Note: Handling invalidly encoded slots is currently not supported but may be added in the future. If you are compiling via IR, reading an invalidly encoded slot results in a Panic(0x22) error.

JSON Output

The storage (or transient storage) layout of a contract can be requested via the standard JSON interface. The output is a JSON object containing two keys, storage and types. The storage object is an array where each element has the following form:

Field meanings:

  • astId: id of the AST node of the state variable’s declaration.

  • contract: name of the contract including its path as prefix.

  • label: name of the state variable.

  • offset: offset in bytes within the storage slot according to the encoding.

  • slot: the storage slot where the state variable resides or starts (as a string since it may be very large).

  • type: identifier used as key to the variable’s type information (described next).

The given type, e.g. t_uint256, represents an element in types, which has the form:

Where:

  • encoding: how the data is encoded in storage. Possible values:

    • inplace: data is laid out contiguously in storage.

    • mapping: Keccak-256 hash-based method.

    • dynamic_array: Keccak-256 hash-based method.

    • bytes: single slot or Keccak-256 hash-based depending on the data size.

  • label: canonical type name.

  • numberOfBytes: number of used bytes (as a decimal string). If numberOfBytes > 32 this means more than one slot is used.

Some types have extra information: mappings contain key and value types (referencing entries in types), arrays have base type, and structs list their members in the same format as the top-level storage.

Note: The JSON output format of a contract’s storage layout is still considered experimental and is subject to change in non-breaking releases of Solidity.

The following example shows a contract and both its storage and transient storage layout, containing value and reference types, types that are encoded packed, and nested types.

open in Remixarrow-up-right

Storage Layout (JSON)

(omitted here for brevity—see the example in the original source for the full JSON structure)

Transient Storage Layout (JSON)

(omitted here for brevity—see the example in the original source for the full JSON structure)