Skip to content

Layout of State Variables in Storage and Transient Storage

Statically-sized variables (everything except mapping and dynamically-sized array types) are laid out in storage consecutively starting from position 0 in Ethereum.

💡 NeoVM Difference: Key-Value Architecture

EVM uses a linear, sequential 256-bit slot model for storage where variables occupy contiguous 32-byte slots. NeoVM uses a dynamic Key-Value storage model. Keys are arbitrary-length byte arrays. Neo DevPack for Solidity computes a unique, deterministic hash for every state variable using SHA256 rather than assigning sequential slots.

For a split reader guide to mappings, arrays, structs, and key derivation, see Storage and Mappings.

Transient Storage

💡 NeoVM Difference: Transient Keyword

Ethereum Solidity v0.8.24 introduced the transient data location keyword (EIP-1153) to store data that is discarded after every transaction. NeoVM does not currently have a native transient storage mechanism.

Using the transient keyword in Neo DevPack for Solidity is currently parsed for compatibility but will emit a compilation warning or error depending on the use-case, as the runtime does not support reverting state without a transaction failure.

Deterministic Key Derivation

The Neo DevPack for Solidity compiler derives storage keys by utilizing the SHA256 hashing algorithm.

For simple, non-dynamic variables, the storage key is the hash of the variable name:

storage_key = SHA256(variable_name)

Because keys are derived by name, renaming a state variable between contract upgrades will cause the contract to lose access to the previously stored data.

Mappings and Dynamic Arrays

Mappings and arrays on Neo use iterative hashing to produce deterministic, collision-free keys, similar in concept to EVM's keccak256(key . slot), but adapted for Neo's key-value infrastructure.

For mappings, keys are derived iteratively:

// Simple mapping: mapping(address => uint256) balances;
slot_hash = SHA256("balances")
storage_key = SHA256(serialize(key) || slot_hash)

// Nested mapping: mapping(address => mapping(uint256 => bool)) approvals;
slot_hash   = SHA256("approvals")
level_1     = SHA256(serialize(outer_key) || slot_hash)
level_2     = SHA256(serialize(inner_key) || level_1)
// level_2 is the final storage key

Array Storage Layout

Storage arrays use a length key and per-element keys:

length_key = SHA256(variable_name)
element_key(i) = SHA256(serialize(i) || SHA256(variable_name))

The .push() operation retrieves the length, increments it, and writes the new element to element_key(i).

Struct Storage Layout

Structs stored in state variables are serialized as a single blob using StdLib.serialize(). The entire struct is read and written atomically.

solidity
struct Config {
    address admin;
    uint256 fee;
    bool paused;
}

Config public config;
// In storage: mapped to a single key: SHA256("config")
// Value: the binary serialized output of the struct

Note: Because struct storage is atomic, reading a single field requires deserializing the entire struct. For frequently accessed individual fields, consider using separate state variables.

Storage Serialization Formats

When encoding values to create storage keys or persisting them to storage, Neo DevPack for Solidity uses specific binary formats:

TypeSerialization Strategy
IntegersBig-endian byte array, padded to declared bit width. Signed integers are sign-extended; unsigned are zero-extended.
BooleansSingle byte: 0x00 (false) or 0x01 (true).
AddressRaw 20 bytes (UInt160), no padding.
Bytes/StringRaw UTF-8 bytes, no length prefix.
StructsStdLib.serialize() binary payload.

Because NeoVM handles the underlying Get and Put syscalls using these arbitrary byte arrays, you do not have to worry about EVM tight-packing optimizations (like packing two uint128 variables into a single 256-bit slot). The NeoVM bills storage byte-by-byte based on the actual size of the payload.

MIT Licensed