EVM Storage Layout: Slots, Packing & Cold vs Warm Access
Table of Contents
Table of Contents
Share

Audit EVM storage slots, packing patterns, mapping layout, and cold vs warm SLOAD/SSTORE costs to build gas-efficient Solidity smart contracts in 2024.
Frequently Asked Questions
- For a mapping declared at storage slot `p`, the value associated with key `k` is stored at the slot computed by `keccak256(abi.encode(k, p))`. Both `k` and `p` are left-padded to 32 bytes before concatenation and hashing. For nested mappings, the formula is applied recursively: the inner mapping's slot for key `k1` is `keccak256(abi.encode(k1, p))`, and the value for key `k2` within that inner mapping is stored at `keccak256(abi.encode(k2, keccak256(abi.encode(k1, p))))`. This derivation is specified in the Solidity documentation and has been stable since Solidity 0.5.x (Solidity Storage Layout, docs.soliditylang.org, 2024).
- EIP-2929 (State Access Gas Cost Repricing), implemented in the Berlin hardfork on April 15, 2021, introduced the cold/warm access cost distinction. Before Berlin, SLOAD cost a flat 800 gas. After Berlin, first access to any slot in a transaction costs 2,100 gas (cold), and subsequent accesses to the same slot cost 100 gas (warm). EIP-2930 (Optional Access Lists) was activated in the same hardfork, allowing senders to pre-declare storage slots for pre-warming at 2,400 gas per slot. Full specification is at EIP-2929, eips.ethereum.org, 2021.
- Yes. The most common packing-related bug is assuming that a write to one packed field does not affect adjacent fields in the same slot. The Solidity compiler handles this correctly using bitmask operations, but developers using inline assembly (`sstore`/`sload` directly) bypass these protections and must manually manage bitmasks. A second common issue is that packed storage reads are always 32-byte reads: the full slot is loaded, then the target bytes are extracted. If two threads (simulated via re-entrancy) both read and modify the same slot independently, one will overwrite the other's changes. This is a storage-layer re-entrancy variant and is not prevented by standard OpenZeppelin re-entrancy guards if the guard itself is in a different slot than the data being protected.
- Regular Solidity `memory` is allocated per-call and is inaccessible to sub-calls (it does not persist across `CALL` boundaries by default). Transient storage (TLOAD/TSTORE) persists for the entire transaction duration, including across all internal and external sub-calls made during that transaction, but is cleared when the transaction completes. This makes transient storage behave like a transaction-scoped global: a top-level function can write a value via TSTORE, and a deeply nested sub-call can read it via TLOAD, without any ABI encoding overhead. Memory cannot do this across call boundaries without explicit passing as function arguments.
- A proxy contract stores the address of its implementation contract in a state variable. If that variable is in slot 0 (sequential assignment default), and the implementation contract also declares a state variable in slot 0, every write to the implementation's first variable overwrites the proxy's implementation pointer, bricking the contract or enabling an upgrade hijack. EIP-1967 prevents this by specifying that proxy administrative variables must be stored at `keccak256("eip1967.proxy.implementation") - 1`, which evaluates to a slot far outside the sequential range any implementation contract would reach with normal variable declarations (EIP-1967, eips.ethereum.org, 2019).
Don't Miss What's Next
Subscribe to newsletter
EVM
Solidity
Gas Optimization
Storage Layout
Smart Contracts
EIP-2929
EIP-1153
Get in Touch
Our team will get back to you within 24 hours.
















