State: storage¶
Warm/cold accounting (EIP-2929), persistent storage (SLOAD/SSTORE),
and transient storage (EIP-1153), over the host state stores.
function storage_key¶
function storage_key(a : address, s : word) -> StorageKey = struct { addr = a, slot = s }A fully qualified storage key: account address and 256-bit slot.
struct StorageKey = { addr : address, slot : word }A 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)The EVM 256-bit machine word (YP §9.1). A transparent range keeps the mathematical subtype relation visible: narrower non-negative ranges can be passed as words without a model-level conversion.
type word = range(0, 2 ^ 256 - 1)function k_account_is_warm¶
Returns the address's EIP-2929 warm bit without changing state. Active precompiles are warm independently of the BAL-derived account table.
function k_account_is_warm(a : address) -> bool = {
let precompile_id = precompile_id_for_address(a);
if precompile_id != NotPrecompile then {
true
} else {
account_is_warm(a)
}
}Returns whether the address is already warm without mutating the warm set.
val account_is_warm = impure { c: "account_is_warm" } : address -> boolMaps an address to its active precompile identifier; any other address,
including one whose precompile is not yet active at the current fork,
is NotPrecompile.
function precompile_id_for_address(bytes : address) -> precompile_id = {
match bytes {
_ if bytes == PRECOMPILE_ADDRESS_1 => precompile_id_if_active(Ecrecover),
_ if bytes == PRECOMPILE_ADDRESS_2 => precompile_id_if_active(Sha256),
_ if bytes == PRECOMPILE_ADDRESS_3 => precompile_id_if_active(Ripemd160),
_ if bytes == PRECOMPILE_ADDRESS_4 => precompile_id_if_active(Identity),
_ if bytes == PRECOMPILE_ADDRESS_5 => precompile_id_if_active(Modexp),
_ if bytes == PRECOMPILE_ADDRESS_6 => precompile_id_if_active(Bn254Add),
_ if bytes == PRECOMPILE_ADDRESS_7 => precompile_id_if_active(Bn254Mul),
_ if bytes == PRECOMPILE_ADDRESS_8 => precompile_id_if_active(Bn254Pairing),
_ if bytes == PRECOMPILE_ADDRESS_9 => precompile_id_if_active(Blake2f),
_ if bytes == PRECOMPILE_ADDRESS_10 => precompile_id_if_active(KzgPointEvaluation),
_ if bytes == PRECOMPILE_ADDRESS_11 => precompile_id_if_active(BlsG1Add),
_ if bytes == PRECOMPILE_ADDRESS_12 => precompile_id_if_active(BlsG1Msm),
_ if bytes == PRECOMPILE_ADDRESS_13 => precompile_id_if_active(BlsG2Add),
_ if bytes == PRECOMPILE_ADDRESS_14 => precompile_id_if_active(BlsG2Msm),
_ if bytes == PRECOMPILE_ADDRESS_15 => precompile_id_if_active(BlsPairing),
_ if bytes == PRECOMPILE_ADDRESS_16 => precompile_id_if_active(BlsMapFpToG1),
_ if bytes == PRECOMPILE_ADDRESS_17 => precompile_id_if_active(BlsMapFp2ToG2),
_ if bytes == PRECOMPILE_ADDRESS_256 => precompile_id_if_active(P256Verify),
_ => NotPrecompile,
}
}The closed first-order selector for the precompile catalog. Availability, gas pricing, and execution are separate interpreters of this identifier so their protocol equations remain explicit without function-valued records.
enum PrecompileId = {
/* the closed sentinel: the address is not a precompiled contract */
NotPrecompile,
/* addresses 0x01-0x04: the original Frontier precompiles */
Ecrecover,
Sha256,
Ripemd160,
Identity,
/* address 0x05: EIP-198 modular exponentiation */
Modexp,
/* addresses 0x06-0x08: the EIP-196/EIP-197 BN254 curve precompiles */
Bn254Add,
Bn254Mul,
Bn254Pairing,
/* address 0x09: EIP-152 BLAKE2 compression */
Blake2f,
/* address 0x0a: EIP-4844 KZG point evaluation */
KzgPointEvaluation,
/* addresses 0x0b-0x11: the EIP-2537 BLS12-381 precompiles */
BlsG1Add,
BlsG1Msm,
BlsG2Add,
BlsG2Msm,
BlsPairing,
BlsMapFpToG1,
BlsMapFp2ToG2,
/* address 0x100: EIP-7951 secp256r1 signature verification */
P256Verify,
}A 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)The quantity alias carried by the precompile interpreters for the closed selector above.
type precompile_id = PrecompileIdfunction k_account_mark_warm¶
Marks an address warm after the caller has established that its access gas is affordable. Active precompiles need no host-table entry.
function k_account_mark_warm(a : address) -> unit = {
let precompile_id = precompile_id_for_address(a);
if precompile_id != NotPrecompile then {
return ()
};
account_mark_warm(a)
}Marks the address warm after its access charge has been paid.
val account_mark_warm = impure { c: "account_mark_warm" } : address -> unitMaps an address to its active precompile identifier; any other address,
including one whose precompile is not yet active at the current fork,
is NotPrecompile.
function precompile_id_for_address(bytes : address) -> precompile_id = {
match bytes {
_ if bytes == PRECOMPILE_ADDRESS_1 => precompile_id_if_active(Ecrecover),
_ if bytes == PRECOMPILE_ADDRESS_2 => precompile_id_if_active(Sha256),
_ if bytes == PRECOMPILE_ADDRESS_3 => precompile_id_if_active(Ripemd160),
_ if bytes == PRECOMPILE_ADDRESS_4 => precompile_id_if_active(Identity),
_ if bytes == PRECOMPILE_ADDRESS_5 => precompile_id_if_active(Modexp),
_ if bytes == PRECOMPILE_ADDRESS_6 => precompile_id_if_active(Bn254Add),
_ if bytes == PRECOMPILE_ADDRESS_7 => precompile_id_if_active(Bn254Mul),
_ if bytes == PRECOMPILE_ADDRESS_8 => precompile_id_if_active(Bn254Pairing),
_ if bytes == PRECOMPILE_ADDRESS_9 => precompile_id_if_active(Blake2f),
_ if bytes == PRECOMPILE_ADDRESS_10 => precompile_id_if_active(KzgPointEvaluation),
_ if bytes == PRECOMPILE_ADDRESS_11 => precompile_id_if_active(BlsG1Add),
_ if bytes == PRECOMPILE_ADDRESS_12 => precompile_id_if_active(BlsG1Msm),
_ if bytes == PRECOMPILE_ADDRESS_13 => precompile_id_if_active(BlsG2Add),
_ if bytes == PRECOMPILE_ADDRESS_14 => precompile_id_if_active(BlsG2Msm),
_ if bytes == PRECOMPILE_ADDRESS_15 => precompile_id_if_active(BlsPairing),
_ if bytes == PRECOMPILE_ADDRESS_16 => precompile_id_if_active(BlsMapFpToG1),
_ if bytes == PRECOMPILE_ADDRESS_17 => precompile_id_if_active(BlsMapFp2ToG2),
_ if bytes == PRECOMPILE_ADDRESS_256 => precompile_id_if_active(P256Verify),
_ => NotPrecompile,
}
}The closed first-order selector for the precompile catalog. Availability, gas pricing, and execution are separate interpreters of this identifier so their protocol equations remain explicit without function-valued records.
enum PrecompileId = {
/* the closed sentinel: the address is not a precompiled contract */
NotPrecompile,
/* addresses 0x01-0x04: the original Frontier precompiles */
Ecrecover,
Sha256,
Ripemd160,
Identity,
/* address 0x05: EIP-198 modular exponentiation */
Modexp,
/* addresses 0x06-0x08: the EIP-196/EIP-197 BN254 curve precompiles */
Bn254Add,
Bn254Mul,
Bn254Pairing,
/* address 0x09: EIP-152 BLAKE2 compression */
Blake2f,
/* address 0x0a: EIP-4844 KZG point evaluation */
KzgPointEvaluation,
/* addresses 0x0b-0x11: the EIP-2537 BLS12-381 precompiles */
BlsG1Add,
BlsG1Msm,
BlsG2Add,
BlsG2Msm,
BlsPairing,
BlsMapFpToG1,
BlsMapFp2ToG2,
/* address 0x100: EIP-7951 secp256r1 signature verification */
P256Verify,
}A 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)The quantity alias carried by the precompile interpreters for the closed selector above.
type precompile_id = PrecompileIdfunction k_slot_is_warm¶
Returns a storage slot's EIP-2929 warm bit without changing state.
Returns whether (address, slot) is already warm without mutation.
val storage_is_warm = impure { c: "storage_is_warm" } : (address, word) -> boolA 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)The EVM 256-bit machine word (YP §9.1). A transparent range keeps the mathematical subtype relation visible: narrower non-negative ranges can be passed as words without a model-level conversion.
type word = range(0, 2 ^ 256 - 1)function k_slot_mark_warm¶
Marks a storage slot warm after the caller has paid its access charge.
Marks (address, slot) warm after its access charge has been paid.
val storage_mark_warm = impure { c: "storage_mark_warm" } : (address, word) -> unitA 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)The EVM 256-bit machine word (YP §9.1). A transparent range keeps the mathematical subtype relation visible: narrower non-negative ranges can be passed as words without a model-level conversion.
type word = range(0, 2 ^ 256 - 1)function k_prewarm_slot¶
Warms an explicitly addressed storage slot while preparing a transaction's access list, before any EVM frame owns the execution context.
Marks (address, slot) warm after its access charge has been paid.
val storage_mark_warm = impure { c: "storage_mark_warm" } : (address, word) -> unitA 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)The EVM 256-bit machine word (YP §9.1). A transparent range keeps the mathematical subtype relation visible: narrower non-negative ranges can be passed as words without a model-level conversion.
type word = range(0, 2 ^ 256 - 1)function k_sload¶
Resolves a slot to its live StorageValue: curr
is the value SLOAD pushes; orig is the EIP-2200 transaction-start
value the SSTORE gas policy compares against. The guarded
SLOAD/SSTORE opcode paths are the only callers, so reaching this
function consults the transaction overlay first. A real row hit was already
recorded when that row was established; misses record the EIP-7928 storage
read before consulting either a transaction-local clear generation or
block-scoped state. A clear generation makes an uncached slot known-zero,
but is not itself a slot-cache hit. BAL reads survive frame rollback (the
encoder removes slots that also have a storage change).
stateless_storage_by_key is the base primitive — an authenticated MPT
point-get, one walk for both the witness and the harness-built alloc trie;
everything above it (the overlay, the read-through, the journal) is common.
function k_sload(a : address, s : word) -> StorageValue = {
let key = storage_key(a, s);
let tx_value = storage_tx_get(key);
match tx_value {
StorageTxHit(value) => return value,
StorageTxCleared(_) => {
bal_storage_read(a, s);
return struct { curr = ZERO_WORD, orig = ZERO_WORD }
},
StorageTxMiss(_) => bal_storage_read(a, s),
};
let block_value = storage_block_get(key);
if block_value.found then {
return struct { curr = block_value.value.curr, orig = block_value.value.curr }
};
let acc = k_aload(a);
let slot_hash = keccak256_word(s);
let value =
if acc.storage_cleared then ZERO_WORD else stateless_storage_by_key(acc.info.storage_root, slot_hash);
storage_block_cache(key, slot_hash, value);
struct { curr = value, orig = value }
}Records a storage read of (address, slot); reads with no
subsequent change are listed slot-only in EIP-7928.
val bal_storage_read = impure { c: "bal_note_storage_read" } : (address, word) -> unitResolves an account through the transaction and block overlays before an authenticated witness read. A transaction-overlay hit was already touched when that row was established; misses record the EIP-7928 account touch before consulting block-scoped state.
function k_aload(a : address) -> Account = {
let tx_account = acct_tx_get(a);
if tx_account.found then {
return tx_account.account
};
bal_account_touch(a);
let block_account = acct_block_get(a);
if block_account.found then {
return block_account.account
};
let address_hash = keccak256_address(a);
let account = stateless_account_by_key(k_parent_state_root, address_hash);
acct_block_cache(a, address_hash, account);
account
}KECCAK-256 of one EVM word in canonical big-endian byte order.
val keccak256_word = impure { c: "host_keccak_word" } : word -> hashThe witnessed storage value of slot under a storage root, reading
the secure trie at keccak256(slot); absent slots are zero.
function stateless_storage_by_key(root : hash, slot_hash : hash) -> word = {
let path = path_new(slot_hash, 64);
let value = trie_lookup(root, path);
if value.len == 0 then {
ZERO_WORD
} else {
let encoded_value = rlp_single_ref(value);
rlp_decode_u256(encoded_value)
}
}Caches a witness-read value and its already-derived secure slot key in the block layer so later transactions skip both the trie walk and re-hashing.
val storage_block_cache = impure { c: "storage_block_cache" } : (StorageKey, hash, word) -> unitThe block-layer row for a storage key. found is false when no earlier
transaction in the block has read or written it.
val storage_block_get = impure { c: "storage_block_get" } : StorageKey -> StorageBlockRowfunction storage_key(a : address, s : word) -> StorageKey = struct { addr = a, slot = s }Resolves a transaction-layer storage key, distinguishing an actual cached row from an uncached slot made known-zero by an account storage clear.
val storage_tx_get = impure { c: "storage_tx_get" } : StorageKey -> StorageTxLookuplet ZERO_WORD : word = word_from_bits(0x0000000000000000000000000000000000000000000000000000000000000000)Result of consulting the transaction storage layer. A clear generation is not a slot-row hit: it resolves the value to zero, but the caller must still record the first EIP-7928 read of that slot.
union StorageTxLookup = {
/* the slot's transaction-layer current/original value pair */
StorageTxHit : StorageValue,
/* a clear generation covers the slot: it reads as zero */
StorageTxCleared : unit,
/* the transaction layer holds nothing for the slot */
StorageTxMiss : unit,
}A storage slot's current and original (transaction-start) values — the pair EIP-2200/EIP-3529 gas and refund rules compare.
struct StorageValue = { curr : word, orig : word }A 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)The EVM 256-bit machine word (YP §9.1). A transparent range keeps the mathematical subtype relation visible: narrower non-negative ranges can be passed as words without a model-level conversion.
type word = range(0, 2 ^ 256 - 1)function k_sstore¶
SSTORE: creates or updates the live transaction row. The preceding
k_sload supplies the transaction-original value; the host keeps
clear generations and frame undo history private.
function k_sstore(a : address, s : word, v : StorageValue) -> unit = {
let key = storage_key(a, s);
storage_tx_update(struct { key = key, value = v })
}function storage_key(a : address, s : word) -> StorageKey = struct { addr = a, slot = s }Inserts or overwrites a row in the transaction-layer storage overlay
(the raw store behind SSTORE).
val storage_tx_update = impure { c: "storage_tx_update" } : StorageEntry -> unitA storage slot's current and original (transaction-start) values — the pair EIP-2200/EIP-3529 gas and refund rules compare.
struct StorageValue = { curr : word, orig : word }A 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)The EVM 256-bit machine word (YP §9.1). A transparent range keeps the mathematical subtype relation visible: narrower non-negative ranges can be passed as words without a model-level conversion.
type word = range(0, 2 ^ 256 - 1)function k_tload¶
TLOAD (EIP-1153): reads per-transaction transient storage, which is
discarded at transaction end and is not part of the state trie.
Reads a transient storage slot; zero when never written (TLOAD).
val transient_load = impure { c: "transient_storage_read" } : (address, word) -> wordA 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)The EVM 256-bit machine word (YP §9.1). A transparent range keeps the mathematical subtype relation visible: narrower non-negative ranges can be passed as words without a model-level conversion.
type word = range(0, 2 ^ 256 - 1)function k_tstore¶
TSTORE (EIP-1153): writes transient storage. Frame rollback is part
of the host's semantic checkpoint contract.
Writes a transient storage slot and includes the mutation in the current semantic checkpoint history.
val transient_store = impure { c: "transient_storage_write" } : (address, word, word) -> unitA 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)The EVM 256-bit machine word (YP §9.1). A transparent range keeps the mathematical subtype relation visible: narrower non-negative ranges can be passed as words without a model-level conversion.
type word = range(0, 2 ^ 256 - 1)