Definition of a JSON format for describing the “storage layout” of an EVM contract.
The storage layout represents the mapping from the smart contracts’ state variables as defined in the source code to their EVM-level storage locations.
The format is extensible and is therefore not tied to any single source language or compiler. It can accommodate layouts produced by any EVM-targeting compiler, as well as more advanced and manually controlled use-cases like ERC-7201, ERC-8042, and hand-written EVM assembly storage.
Consumers of these descriptors — block explorers, debuggers, source-verification services, security tooling, and wallets performing clear signing, transaction simulations, and transaction assertions — can use a single, predictable schema regardless of how a contract was written.
Motivation
Tools that need to make sense of raw storage slots, such as block explorers, debuggers, wallets that show users the outcome of a transaction before they sign, and other similar projects, currently have no common and trusted layout description format and have to rely on their own custom storage layout interpretation.
Solidity’s storageLayout JSON output often serves as a de facto standard, but it has some structural problems making it unfit to be used in scenarios where a finalized ERC-level standard would be required:
It is Solidity specific. For example, type identifiers such as t_uint256 are Solidity-internal naming. As of this writing, no other EVM language emits the storage layout descriptors in a compatible format.
It is not stable. The format documentation states the storage layout is experimental and the format is subject to change in non-breaking releases.
It does not support any common alternative layouts. For example, namespaced storage (ERC-7201) and diamond storage (ERC-8042) place structs at a keccak256-derived slot reached through an inline-assembly pointer. Solidity compiler is not aware of these standards and is unable to generate their layout’s descriptor.
The proposed storage layout format is intended for cross-chain, cross-tool, and cross-language wallets that need to translate raw (contract, slot, old_value, new_value) tuples from transaction simulation or EIP-7906 transaction assertions into a meaningful description of which variable changed as the result of this transaction.
Therefore, this format must satisfy the following requirements:
Use a type and encoding vocabulary that is not coupled to any specific compiler’s internal naming.
Verifiably describe the slot derivation algorithms rather than providing opaque trusted constants for complex types.
Allow storage regions to declare how their base slots were derived. This includes but is not limited to namespaced and diamond schemes.
Allow entries explicitly marked as either compiler-derived or hand-authored, since for a significant share of production contracts, such as upgradeable proxies, diamonds, EIP-7702 delegates, etc., there is no compiler output to derive them from.
Carry optional display annotations that wallets may use to present storage changes in human-readable form, following the ERC-7730 display vocabulary.
Specification
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119 and RFC 8174.
Source language. This ERC defines support for "Solidity", "Vyper", and "manual".
version
No
Compiler version string (e.g. "0.8.24").
settings
No
Arbitrary compiler settings object.
A single file MAY mix entries whose slots were assigned by the declared compiler with entries that were defined manually.
"manual" MUST be used as language when no compiler produced any part of the layout (e.g. Huff, raw Yul, hand-written assembly).
Storage entry
Each element of the top-level storage array, and each element of a struct type’s members array, is a storage entry:
Field
Required
Description
label
Yes
Source-level variable name.
slot
Yes
Decimal string of the storage slot where this entry resides or starts.
offset
Yes
Byte offset within the slot where this entry begins. 0 for any type occupying a full slot or more.
type
Yes
Identifier referencing an entry in the file’s types object.
path
No
Stable dot-separated identifier for this entry (e.g. "vaults.lockedAmount"). If absent, consumers MUST compute it by joining label values from the top-level entry down through struct members.
source
No
"compiler" (default) or "manual". "compiler" means the slot and offset were emitted by the declared compiler from verifiable source. "manual" means the author declared them directly.
baseSlotDerivation
No
For entries whose base slot was computed via an alternative scheme rather than simple sequential compiler assignment. See Base Slot Derivation.
Each value in the top-level types object describes one type referenced by type fields elsewhere in the file:
Field
Required
Description
encoding
Yes
Storage encoding: inplace, inplace_struct, mapping, dynamic_array, or bytes. See Slot Derivation Formulas.
label
Yes
Human-readable type name as it appears in the source language (e.g. "uint256", "mapping(address => uint256)").
numberOfBytes
Yes
Decimal string: number of bytes this type occupies. Used with the entry’s offset to extract a packed value.
key
mapping only
Type identifier of the mapping key.
value
mapping and dynamic_array only
Type identifier of the mapping value or array element.
base
dynamic_array only
Alias of value, kept for literal compatibility with Solidity compiler output.
members
inplace_struct only
Ordered array of storage entries describing the struct’s members.
Base Slot Derivation
An entry’s baseSlotDerivation object names the scheme used to compute its slot, so that consumers can independently recompute and verify it:
Field
Required
Description
scheme
Yes
One of supported base slot calculation algorithms. Currently "erc7201", "erc8042", or "custom".
namespaceId
erc7201, erc8042 only
The namespace id string the scheme hashes.
formula
custom only
Free-text, non-machine-verified description of how slot was computed.
For scheme: "erc7201", consumers MAY verify slot == keccak256(keccak256(namespaceId) - 1) & ~0xff per ERC-7201.
For scheme: "erc8042", consumers MAY verify slot == keccak256(namespaceId) per ERC-8042.
"custom" exists for schemes not yet standardized by an ERC; its slot is hand-authored.
baseSlotDerivation MUST NOT be used for sequential, compiler-assigned slots, including slots shifted by Solidity’s native layout at base-slot syntax — in both cases the compiler emits the final, absolute slot directly, and no separate re-derivation is meaningful.
Packed Variables
Solidity and Vyper may pack multiple small variables into a single 32-byte slot. When a packed slot is modified, consumers MUST identify which variables changed by inspecting each entry whose slot matches, using offset and numberOfBytes from the entry’s type to extract the specific bytes.
To extract the value of a packed variable from a raw 32-byte slot word: read numberOfBytes bytes starting at byte offset within the slot.
When a packed slot changes, wallets MAY omit variables whose extracted old and new values are identical.
Slot Derivation Formulas
inplace types (primitives and fixed-size value types such as uint256, address, bytes32) are stored directly at their declared slot and offset; no further derivation is needed.
Complex types are not stored directly at their entry’s slot; child values are stored at a position computed from the entry’s slot (acting as a base slot), the type’s encoding, and the declared compiler.language. Wallets and other consumers that need to map a raw modified slot back to a storage entry MUST implement the formulas below for every compiler.language value they support:
Language
encoding
Derivation formula
Solidity
inplace_struct
baseSlot + member.slot
Solidity
mapping
keccak256(abi.encode(key, baseSlot))
Solidity
dynamic_array
keccak256(baseSlot) + index * elementSlots
Solidity
bytes
Short form (numberOfBytes(value) <= 31): inline in baseSlot. Long form: data at keccak256(baseSlot), sequential. Length is stored in the low-order byte of baseSlot.
Vyper
inplace_struct
baseSlot + member.slot
Vyper
mapping
keccak256(abi.encode(key, baseSlot)) (Vyper ≥ 0.3.0) or keccak256(abi.encode(baseSlot, key)) (Vyper < 0.3.0) — note the reversed key/slot order in legacy Vyper.
Vyper
dynamic_array
baseSlot + 1 + index * elementSlots
Vyper
bytes
Stored inline starting at baseSlot, spanning ceil(length / 32) slots sequentially. Unlike Solidity, Vyper never relocates bytes/string data behind a keccak256 indirection.
Where elementSlots = ceil(elementSize / 32), derived from the element type’s numberOfBytes. For nested types, derivation is applied recursively at each level.
This table is the canonical, normative source of derivation formulas defined by this ERC. Support for additional values of compiler.language MUST be added via an update to this ERC.
Nested and Complex Types
Storage can involve complex nesting of mappings, arrays, and structs. Consumers resolve nested types recursively:
Mappings: Each nesting level adds a key. mapping(address => mapping(uint256 => uint256)) uses {key[0]} for the outer key and {key[1]} for the inner key.
Arrays: Each nesting level adds an index. uint256[][] uses {index[0]} for the outer dimension and {index[1]} for the inner.
Structs: Members are defined in the types object (with encoding: "inplace_struct"), each with their own optional display annotation. When a struct member changes, the wallet displays the parent variable’s resolved label alongside the member’s own label.
Display Annotations
Each storage entry and each struct member in types may carry an optional display object. These annotations follow the ERC-7730 display vocabulary and are consumed by wallets to present storage changes in human-readable form during transaction simulation and clear signing.
Field
Required
Description
label
No
User-facing name for the variable. Supports {key[N]} and {index[N]} placeholders. See Labels and Placeholders.
description
No
Human-readable explanation of the variable’s semantic meaning. Informative only — MUST NOT be relied upon for security decisions without additional authenticity assurances.
keyFormats
No
Array of formatters applied to mapping keys or array indices before they are embedded in label. See Key Formats.
format
No
The ERC-7730 formatter (tokenAmount, addressName, raw, date, etc.) applied to the storage value itself. Same vocabulary as display.formats[*].fields[*].format in ERC-7730.
params
No
Formatter parameters for format. Same structure as display.formats[*].fields[*].params in ERC-7730.
The label string may embed placeholder tokens that are resolved at display time:
{key[N]} — the Nth mapping key (outermost = {key[0]}), formatted by keyFormats[N] if provided.
{index[N]} — the Nth array element index (outermost = {index[0]}), formatted by keyFormats[N] if provided.
Key Formats
keyFormats is an ordered array of formatter objects. Each entry has the same format and params structure as ERC-7730 field formatters and is applied to the corresponding key or index level before it is embedded in label.
keyFormats[0] applies to {key[0]} or {index[0]}, keyFormats[1] to {key[1]} or {index[1]}, and so on.
Templates and JSON Logic
template is either a plain string or an ordered array of conditional entries:
Plain string: used unconditionally.
Array: each entry has a value string and an optional condition. The wallet evaluates entries in order and uses the first entry whose condition is satisfied. An entry without condition matches unconditionally and SHOULD appear last as the fallback.
Template strings support the following placeholders:
{label} — the resolved label of this entry.
{oldValue} — the storage value before the transaction, after applying format/params.
{newValue} — the storage value after the transaction, after applying format/params.
Conditions are JSON Logic expressions as defined in JSON Logic.
{"label":"_balances","slot":"1","type":"t_mapping(t_address,t_uint256)","display":{"label":"Balance of {key[0]}","description":"Token balance held by each account.","keyFormats":[{"format":"addressName"}],"format":"tokenAmount","params":{"tokenPath":"@.to"},"template":[{"condition":{">":[{"var":"newValue"},{"var":"oldValue"}]},"value":"Balance of {key[0]} increased from {oldValue} to {newValue}"},{"condition":{"<":[{"var":"newValue"},{"var":"oldValue"}]},"value":"Balance of {key[0]} decreased from {oldValue} to {newValue}"},{"value":"Balance of {key[0]} changed from {oldValue} to {newValue}"}]}}
{"storage":[{"label":"vaults","slot":"10","type":"t_mapping(t_address,t_struct(VaultInfo))","display":{"label":"Vault belonging to {key[0]}","keyFormats":[{"format":"addressName"}]}}],"types":{"t_struct(VaultInfo)":{"encoding":"inplace_struct","label":"struct VaultInfo","numberOfBytes":"32","members":[{"label":"lockedAmount","slot":"0","offset":0,"type":"t_uint256","display":{"label":"Locked Amount","format":"tokenAmount","params":{"tokenPath":"@.to"},"template":[{"condition":{">":[{"var":"newValue"},{"var":"oldValue"}]},"value":"Locked Amount increased from {oldValue} to {newValue}"},{"condition":{"<":[{"var":"newValue"},{"var":"oldValue"}]},"value":"Locked Amount decreased from {oldValue} to {newValue}"}]}}]}}}
When vaults[0xAlice].lockedAmount changes, the wallet presents the parent label "Vault belonging to Alice.eth" alongside the member label "Locked Amount".
JSON Logic
Conditions are JSON Logic expressions. This ERC normatively defines the subset of JSON Logic used by conditions and does not depend on the external JSON Logic specification.
A JSON Logic expression is a JSON object with exactly one key naming the operator and an array of operands as its value:
{"<operator>":[<operand>,<operand>,...]}
Operands are themselves JSON Logic expressions or literal JSON values. A condition passes when its top-level expression evaluates to a truthy value.
Expressions are evaluated against the following context:
Variable
Type
Description
oldValue
value
Storage value before the transaction, after applying format.
newValue
value
Storage value after the transaction, after applying format.
key
array
Raw mapping keys at each nesting level, outermost first.
index
array
Array element indices at each nesting level, outermost first.
The var operator returns the value of a context variable: {"var": "oldValue"} evaluates to the oldValue variable. Elements of key and index are addressed with dot-separated paths: {"var": "key.0"} returns the outermost mapping key, {"var": "index.1"} returns the second array index, and so on.
The following operators MUST be supported:
Operator
Example
Returns
==
{"==": [a, b]}
true if a equals b.
!=
{"!=": [a, b]}
true if a does not equal b.
>
{">": [a, b]}
true if a is greater than b.
>=
{">=": [a, b]}
true if a is greater than or equal to b.
<
{"<": [a, b]}
true if a is less than b.
<=
{"<=": [a, b]}
true if a is less than or equal to b.
!
{"!": [a]}
true if a is falsy.
and
{"and": [a, b]}
true if both a and b are truthy.
or
{"or": [a, b]}
true if either a or b is truthy.
var
{"var": "<path>"}
Value of the context variable at <path>.
The comparison operators accept two operands and return a boolean. ! accepts a single operand. and and or accept one or more operands. var accepts a single string operand. The values false, 0, the empty string, the empty array, and null are falsy; all other values are truthy.
Wallets MUST NOT rely on any other JSON Logic operators or features being available.
Rationale
Transient storage is out of scope
While any change persisted in the contract’s storage may be extremely consequential for the user, the transient storage is cleared after the transaction completes and bears no effect on the transaction outcome.
There is no use-case that would require knowledge of the transient storage variable layout in the same way as the persistent one.
Backwards Compatibility
This ERC defines a new file format and introduces no backwards compatibility concerns.
Security Considerations
A storage layout file that misrepresents a slot’s type or path could cause a consumer to display a misleading variable name or formatted value for a real storage change.
This ERC does not define a trust or distribution mechanism for storage layout files. Standards that reference files conforming to this ERC, such as ERC-7730, are responsible for defining how a layout file’s authenticity is established before it is relied upon for any security-relevant display.