Storage Layout & Packing
Storage is a map of 2^256 slots, each 32 bytes.
26.1 Slots
Storage is a map of 2^256 slots, each 32 bytes. State variables are laid out in declaration order starting at slot 0 (including inherited variables, in linearization order, most-base first).
Value types < 32 bytes packed into the same slot if they fit, right-aligned (lower-order) Structs and fixed arrays always start a new slot; members packed by the same rules Item after a struct/array starts a new slot Dynamic array at slot p length at p; element i at keccak256(p) + i (packed if small) Mapping at slot p slot p unused; value for key k at keccak256(abi.encode(k, p)) bytes/string < 32 bytes data and length*2 in the same slot bytes/string ≥ 32 bytes slot holds length*2+1; data at keccak256(p) constant / immutable no storage slot separate transient address space with its own layout
26.2 Packing example
Packing saves gas when variables are read or written together; reading a packed small type alone costs extra masking operations. Order struct members and state variables by size and access pattern.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Unpacked { // 3 slots
uint128 a; // slot 0
uint256 b; // slot 1
uint128 c; // slot 2
}
contract Packed { // 2 slots
uint128 a; // slot 0 (bytes 0-15)
uint128 c; // slot 0 (bytes 16-31)
uint256 b; // slot 1
}
contract PackedStruct {
struct Position { // fits in ONE slot: 20 + 8 + 4 = 32 bytes
address owner; // 20 bytes
uint64 openedAt; // 8 bytes
uint32 leverage; // 4 bytes
}
mapping(uint256 => Position) public positions;
}26.3 Inspecting storage
forge inspect Packed storageLayout # slot, offset, type of each variable
cast storage <address> 0 --rpc-url $RPC # raw value of slot 0 on a live contract
cast index address <key> <mappingSlot> # compute mapping slot for key// Reading/writing arbitrary slots in assembly
function readSlot(uint256 slot) external view returns (bytes32 v) { assembly { v := sload(slot) } }26.4 Namespaced storage (ERC-7201) and custom layouts
To avoid collisions in upgradeable and modular contracts, group state in a struct stored at a pseudo-random slot:
OpenZeppelin Contracts 5.x upgradeable contracts use ERC-7201 namespaces. Since 0.8.29 you can also move a contract's whole storage base: contract C layout at 0x1234 { ... } (the expression must be a compile-time constant).
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract NamespacedStorage {
/// @custom:storage-location erc7201:example.main
struct MainStorage {
uint256 value;
mapping(address => uint256) balances;
}
// keccak256(abi.encode(uint256(keccak256("example.main")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant MAIN_STORAGE_LOCATION =
keccak256(abi.encode(uint256(keccak256("example.main")) - 1)) & ~bytes32(uint256(0xff));
function _getMainStorage() private pure returns (MainStorage storage $) {
bytes32 slot = MAIN_STORAGE_LOCATION;
assembly { $.slot := slot }
}
function setValue(uint256 v) external { _getMainStorage().value = v; }
function value() external view returns (uint256) { return _getMainStorage().value; }
}