State: accounts¶
Account reads and mutations over the two-layer overlay (transaction over block) with the witness as base, plus the EIP-161/EIP-684 predicates and value transfer.
function account_info_changed¶
Field-wise inequality of account tuples.
function account_info_changed(c : AccountInfo, o : AccountInfo) -> bool =
c.nonce != o.nonce | c.balance != o.balance | c.code_hash != o.code_hash | c.storage_root != o.storage_rootAccount state (Yellow Paper §4.1, the account tuple σ[a]).
storage_root is the account's pre-state storage root — the witness
anchor that stateless storage reads walk on a cached-state miss. It is
set when the account is materialized from the witness and is not kept
live during execution (storage mutates in the overlay maps; the
post-state root is computed on demand). Code bytes are not held here —
the account carries only code_hash, a content address into the code
store; the code hash is itself observable state (EXTCODEHASH,
EIP-1052) and the binding a stateless witness checks code against.
struct AccountInfo = {
/* sigma[a]_n: nonce (EIP-2681 caps it at 2^64-1) */
nonce : account_nonce,
/* sigma[a]_b: balance in wei */
balance : word,
/* sigma[a]_c: keccak256(code); KECCAK_EMPTY if codeless */
code_hash : hash,
/* sigma[a]_s: PRE-STATE storage root, the witness anchor */
storage_root : hash,
}function account_info_empty¶
The EIP-161 emptiness test: no code, zero nonce, zero balance.
function account_info_empty(info : AccountInfo) -> bool =
(info.code_hash == KECCAK_EMPTY) & info.nonce == 0 & word_is_zero(info.balance)function word_is_zero(w) = w == WORD_ZEROkeccak256 of the empty string: the codeHash of every codeless
account.
let KECCAK_EMPTY : hash = hash_from_bits(0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470)Account state (Yellow Paper §4.1, the account tuple σ[a]).
storage_root is the account's pre-state storage root — the witness
anchor that stateless storage reads walk on a cached-state miss. It is
set when the account is materialized from the witness and is not kept
live during execution (storage mutates in the overlay maps; the
post-state root is computed on demand). Code bytes are not held here —
the account carries only code_hash, a content address into the code
store; the code hash is itself observable state (EXTCODEHASH,
EIP-1052) and the binding a stateless witness checks code against.
struct AccountInfo = {
/* sigma[a]_n: nonce (EIP-2681 caps it at 2^64-1) */
nonce : account_nonce,
/* sigma[a]_b: balance in wei */
balance : word,
/* sigma[a]_c: keccak256(code); KECCAK_EMPTY if codeless */
code_hash : hash,
/* sigma[a]_s: PRE-STATE storage root, the witness anchor */
storage_root : hash,
}function account_changed¶
Whether an account differs from its original in any trie-observable way.
function account_changed(c : Account, o : Account) -> bool =
account_info_changed(c.info, o.info) | c.present != o.present | c.storage_cleared != o.storage_clearedField-wise inequality of account tuples.
function account_info_changed(c : AccountInfo, o : AccountInfo) -> bool =
c.nonce != o.nonce | c.balance != o.balance | c.code_hash != o.code_hash | c.storage_root != o.storage_rootAn account plus its lifecycle flags: existence, EIP-161 storage clearing, same-transaction creation (EIP-6780), and selfdestruction.
struct Account = {
info : AccountInfo,
present : bool,
storage_cleared: bool,
created : bool,
selfdestructed : bool,
}function account_set_info¶
Installs a new tuple, collapsing to the non-existent form when it is EIP-161-empty.
function account_set_info(acc : Account, info : AccountInfo) -> Account = {
let empty = account_info_empty(info);
if empty then {
{
acc with
info = { EMPTY_ACCOUNT_INFO with storage_root = acc.info.storage_root },
present = false,
storage_cleared = true,
}
} else {
{ acc with info = info, present = true }
}
}The EIP-161 emptiness test: no code, zero nonce, zero balance.
function account_info_empty(info : AccountInfo) -> bool =
(info.code_hash == KECCAK_EMPTY) & info.nonce == 0 & word_is_zero(info.balance)The empty account tuple: zero nonce and balance, KECCAK_EMPTY code
hash, empty-trie storage root (YP §4.1).
let EMPTY_ACCOUNT_INFO : AccountInfo = struct {
nonce = 0,
balance = ZERO_WORD,
code_hash = KECCAK_EMPTY,
storage_root = EMPTY_TRIE_ROOT,
}An account plus its lifecycle flags: existence, EIP-161 storage clearing, same-transaction creation (EIP-6780), and selfdestruction.
struct Account = {
info : AccountInfo,
present : bool,
storage_cleared: bool,
created : bool,
selfdestructed : bool,
}Account state (Yellow Paper §4.1, the account tuple σ[a]).
storage_root is the account's pre-state storage root — the witness
anchor that stateless storage reads walk on a cached-state miss. It is
set when the account is materialized from the witness and is not kept
live during execution (storage mutates in the overlay maps; the
post-state root is computed on demand). Code bytes are not held here —
the account carries only code_hash, a content address into the code
store; the code hash is itself observable state (EXTCODEHASH,
EIP-1052) and the binding a stateless witness checks code against.
struct AccountInfo = {
/* sigma[a]_n: nonce (EIP-2681 caps it at 2^64-1) */
nonce : account_nonce,
/* sigma[a]_b: balance in wei */
balance : word,
/* sigma[a]_c: keccak256(code); KECCAK_EMPTY if codeless */
code_hash : hash,
/* sigma[a]_s: PRE-STATE storage root, the witness anchor */
storage_root : hash,
}function account_clear_storage¶
Marks the account's storage cleared (fresh storage generation).
An account plus its lifecycle flags: existence, EIP-161 storage clearing, same-transaction creation (EIP-6780), and selfdestruction.
struct Account = {
info : AccountInfo,
present : bool,
storage_cleared: bool,
created : bool,
selfdestructed : bool,
}function account_delete¶
The deleted form of an account: empty tuple, non-existent, storage cleared.
function account_delete(acc : Account) -> Account =
{
acc with
info = { EMPTY_ACCOUNT_INFO with storage_root = acc.info.storage_root },
present = false,
storage_cleared = true,
}The empty account tuple: zero nonce and balance, KECCAK_EMPTY code
hash, empty-trie storage root (YP §4.1).
let EMPTY_ACCOUNT_INFO : AccountInfo = struct {
nonce = 0,
balance = ZERO_WORD,
code_hash = KECCAK_EMPTY,
storage_root = EMPTY_TRIE_ROOT,
}An account plus its lifecycle flags: existence, EIP-161 storage clearing, same-transaction creation (EIP-6780), and selfdestruction.
struct Account = {
info : AccountInfo,
present : bool,
storage_cleared: bool,
created : bool,
selfdestructed : bool,
}function account_clear_preserving_balance¶
Clears nonce, code, and storage while preserving a nonzero balance. Amsterdam applies this form to an account created and selfdestructed in the same transaction (EIP-8246).
function account_clear_preserving_balance(acc : Account) -> Account = {
let cleared_storage = account_clear_storage(acc);
account_set_info(cleared_storage, { acc.info with nonce = 0, code_hash = KECCAK_EMPTY })
}Marks the account's storage cleared (fresh storage generation).
function account_clear_storage(acc : Account) -> Account =
{
acc with
storage_cleared = true,
}Installs a new tuple, collapsing to the non-existent form when it is EIP-161-empty.
function account_set_info(acc : Account, info : AccountInfo) -> Account = {
let empty = account_info_empty(info);
if empty then {
{
acc with
info = { EMPTY_ACCOUNT_INFO with storage_root = acc.info.storage_root },
present = false,
storage_cleared = true,
}
} else {
{ acc with info = info, present = true }
}
}keccak256 of the empty string: the codeHash of every codeless
account.
let KECCAK_EMPTY : hash = hash_from_bits(0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470)An account plus its lifecycle flags: existence, EIP-161 storage clearing, same-transaction creation (EIP-6780), and selfdestruction.
struct Account = {
info : AccountInfo,
present : bool,
storage_cleared: bool,
created : bool,
selfdestructed : bool,
}function store_account¶
Writes a whole-account row to the transaction overlay.
Inserts or overwrites the transaction-layer account row for an address.
val acct_tx_update = impure { c: "acct_tx_update" } : (address, Account) -> unitAn account plus its lifecycle flags: existence, EIP-161 storage clearing, same-transaction creation (EIP-6780), and selfdestruction.
struct Account = {
info : AccountInfo,
present : bool,
storage_cleared: bool,
created : bool,
selfdestructed : bool,
}A 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)function store_account_info¶
Applies a tuple change with minimal overlay traffic: scalar-field fast paths when existence/storage state is unchanged, and a storage clear when the tuple collapses to empty.
function store_account_info(a : address, acc : Account, info : AccountInfo) -> unit = {
let empty = account_info_empty(info);
if empty then {
storage_tx_clear(a)
};
let next = account_set_info(acc, info);
if (next.info.storage_root != acc.info.storage_root)
| (next.present != acc.present)
| (next.storage_cleared != acc.storage_cleared) then {
store_account(a, next)
} else {
if next.info.balance != acc.info.balance then {
acct_tx_set_balance(a, next.info.balance)
};
if next.info.nonce != acc.info.nonce then {
acct_tx_set_nonce(a, next.info.nonce)
};
if next.info.code_hash != acc.info.code_hash then {
acct_tx_set_code_hash(a, next.info.code_hash)
}
}
}The EIP-161 emptiness test: no code, zero nonce, zero balance.
function account_info_empty(info : AccountInfo) -> bool =
(info.code_hash == KECCAK_EMPTY) & info.nonce == 0 & word_is_zero(info.balance)Installs a new tuple, collapsing to the non-existent form when it is EIP-161-empty.
function account_set_info(acc : Account, info : AccountInfo) -> Account = {
let empty = account_info_empty(info);
if empty then {
{
acc with
info = { EMPTY_ACCOUNT_INFO with storage_root = acc.info.storage_root },
present = false,
storage_cleared = true,
}
} else {
{ acc with info = info, present = true }
}
}Balance-only fast path into the transaction-layer row, avoiding a whole-account rewrite.
val acct_tx_set_balance = impure { c: "acct_tx_set_balance" } : (address, word) -> unitCode-hash-only fast path into the transaction-layer row.
val acct_tx_set_code_hash = impure { c: "acct_tx_set_code_hash" } : (address, hash) -> unitNonce-only fast path into the transaction-layer row.
val acct_tx_set_nonce = impure { c: "acct_tx_set_nonce" } : (address, account_nonce) -> unitDrops every transaction-layer storage row of an address — the storage side of an account collapsing to empty or clearing its storage.
val storage_tx_clear = impure { c: "storage_tx_clear" } : address -> unitWrites a whole-account row to the transaction overlay.
function store_account(a : address, v : Account) -> unit =
acct_tx_update(a, v)An account plus its lifecycle flags: existence, EIP-161 storage clearing, same-transaction creation (EIP-6780), and selfdestruction.
struct Account = {
info : AccountInfo,
present : bool,
storage_cleared: bool,
created : bool,
selfdestructed : bool,
}Account state (Yellow Paper §4.1, the account tuple σ[a]).
storage_root is the account's pre-state storage root — the witness
anchor that stateless storage reads walk on a cached-state miss. It is
set when the account is materialized from the witness and is not kept
live during execution (storage mutates in the overlay maps; the
post-state root is computed on demand). Code bytes are not held here —
the account carries only code_hash, a content address into the code
store; the code hash is itself observable state (EXTCODEHASH,
EIP-1052) and the binding a stateless witness checks code against.
struct AccountInfo = {
/* sigma[a]_n: nonce (EIP-2681 caps it at 2^64-1) */
nonce : account_nonce,
/* sigma[a]_b: balance in wei */
balance : word,
/* sigma[a]_c: keccak256(code); KECCAK_EMPTY if codeless */
code_hash : hash,
/* sigma[a]_s: PRE-STATE storage root, the witness anchor */
storage_root : hash,
}A 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)function k_aload¶
Resolves 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
}Caches a witness-read account and its already-derived secure address key in the block layer so later transactions skip both the trie walk and re-hashing.
val acct_block_cache = impure { c: "acct_block_cache" } : (address, hash, Account) -> unitThe block-layer account row for an address. found is false when no
earlier transaction in the block has loaded it.
val acct_block_get = impure { c: "acct_block_get" } : address -> AccountRowThe transaction-layer account row for an address. found is false when
this transaction has not touched it.
val acct_tx_get = impure { c: "acct_tx_get" } : address -> AccountRowRecords that the address was accessed, so it appears in the block access list even without a change (EIP-7928).
val bal_account_touch = impure { c: "bal_note_account_touch" } : address -> 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 a 20-byte address (secure-trie account keys).
val keccak256_address = impure { c: "host_keccak_address" } : address -> hashThe witnessed account at address a under state root root,
reading the secure trie at keccak256(a); a walk that proves absence
yields [EMPTY_ACCOUNT], whose present field is the absence witness.
function stateless_account_by_key(root : hash, address_hash : hash) -> Account = {
let path = path_new(address_hash, 64);
let value = trie_lookup(root, path);
if value.len == 0 then {
EMPTY_ACCOUNT
} else {
let account_info = decode_state_account(value);
account_from_info(account_info)
}
}The authenticated parent state root, the anchor of every stateless read.
register k_parent_state_root : hash = ZERO_HASHAn account plus its lifecycle flags: existence, EIP-161 storage clearing, same-transaction creation (EIP-6780), and selfdestruction.
struct Account = {
info : AccountInfo,
present : bool,
storage_cleared: bool,
created : bool,
selfdestructed : bool,
}A 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)function k_get_balance¶
The account balance (BALANCE, SELFBALANCE).
Resolves 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
}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_get_nonce¶
The account nonce.
function k_get_nonce(a : address) -> account_nonce = {
k_aload(a).info.nonce
}Resolves 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
}An account transaction-count nonce (EIP-2681).
type account_nonce = range(0, account_nonce_bound)A 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)function k_account_exists¶
Whether the account exists (post-EIP-161 sense).
Resolves 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
}A 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)function k_account_is_empty¶
The EIP-161 "empty" test on the live account: zero nonce, zero balance, no code.
function k_account_is_empty(a : address) -> bool = {
let account = k_aload(a);
account_info_empty(account.info)
}The EIP-161 emptiness test: no code, zero nonce, zero balance.
function account_info_empty(info : AccountInfo) -> bool =
(info.code_hash == KECCAK_EMPTY) & info.nonce == 0 & word_is_zero(info.balance)Resolves 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
}A 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)function k_account_occupied¶
The CREATE/CREATE2/create-transaction address-collision test
(EIP-684/EIP-7610): the target is occupied if it has code, a nonzero
nonce, or any storage.
function k_account_occupied(a : address) -> bool = {
let acc = k_aload(a);
let info = acc.info;
var anchored_storage : bool = false;
if not_bool(acc.storage_cleared) then {
anchored_storage = info.storage_root != EMPTY_TRIE_ROOT
};
let has_code = info.code_hash != KECCAK_EMPTY;
let has_nonce = info.nonce != 0;
if has_code | has_nonce | anchored_storage then {
true
} else {
storage_has_writes(a)
}
}Resolves 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
}val not_bool = pure {coq: "negb", lean: "_lean_not", _: "not"}: forall ('p : Bool). bool('p) -> bool(not('p))Whether the transaction layer holds any storage write for the address — the overlay side of the EIP-684/EIP-7610 occupancy test.
val storage_has_writes = impure { c: "storage_has_writes" } : address -> boolkeccak256(rlp("")) — the root of an empty Merkle-Patricia trie: the
storage root of every account with no storage (EMPTY_ACCOUNT, freshly
created).
let EMPTY_TRIE_ROOT : hash = hash_from_bits(0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421)keccak256 of the empty string: the codeHash of every codeless
account.
let KECCAK_EMPTY : hash = hash_from_bits(0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470)A 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)function k_transfer¶
Moves v wei from src to dst (both updates recorded for frame
rollback; the EVM checks sufficiency before calling) and emits the
EIP-7708 transfer log.
function k_transfer(src : address, dst : address, v : word) -> unit = {
let src_acc = k_aload(src);
let dst_acc = k_aload(dst);
let value_is_zero = word_is_zero(v);
if value_is_zero | (src == dst) then {
return ()
};
let source_balance = alu_sub(src_acc.info.balance, v);
store_account_info(src, src_acc, { src_acc.info with balance = source_balance });
let destination_balance = alu_add(dst_acc.info.balance, v);
store_account_info(dst, dst_acc, { dst_acc.info with balance = destination_balance });
k_emit_transfer_log(src, dst, v)
}function alu_add(a, b) = word_add(a, b)function alu_sub(a : word, b : word) -> word = word_sub(a, b)Resolves 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
}Emits the EIP-7708 transfer log for a nonzero, non-self value transfer (Amsterdam onward).
function k_emit_transfer_log(src : address, dst : address, v : word) -> unit = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
let value_is_zero = word_is_zero(v);
if (profile.fork < Amsterdam) | value_is_zero | (src == dst) then {
return ()
};
let source = address_to_word(src);
let destination = address_to_word(dst);
let topics = LogTopics3((EIP7708_TRANSFER_TOPIC, source, destination));
let data = LogDataWord(v);
k_log(EIP7708_SYSTEM_ADDRESS, topics, data)
}Applies a tuple change with minimal overlay traffic: scalar-field fast paths when existence/storage state is unchanged, and a storage clear when the tuple collapses to empty.
function store_account_info(a : address, acc : Account, info : AccountInfo) -> unit = {
let empty = account_info_empty(info);
if empty then {
storage_tx_clear(a)
};
let next = account_set_info(acc, info);
if (next.info.storage_root != acc.info.storage_root)
| (next.present != acc.present)
| (next.storage_cleared != acc.storage_cleared) then {
store_account(a, next)
} else {
if next.info.balance != acc.info.balance then {
acct_tx_set_balance(a, next.info.balance)
};
if next.info.nonce != acc.info.nonce then {
acct_tx_set_nonce(a, next.info.nonce)
};
if next.info.code_hash != acc.info.code_hash then {
acct_tx_set_code_hash(a, next.info.code_hash)
}
}
}function word_is_zero(w) = w == WORD_ZEROA 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_bump_nonce¶
Increments the account nonce. The u64 increment cannot wrap:
EIP-2681 guards every path that reaches a bump.
function k_bump_nonce(a : address) -> unit = {
let cur = k_aload(a);
let nonce = cur.info.nonce;
if nonce < sizeof(account_nonce_bound) then {
store_account_info(a, cur, { cur.info with nonce = nonce + 1 })
} else {
fatal_error(ExecutionInvalid)
}
}function fatal_error(_reason) = exit(())Resolves 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
}Applies a tuple change with minimal overlay traffic: scalar-field fast paths when existence/storage state is unchanged, and a storage clear when the tuple collapses to empty.
function store_account_info(a : address, acc : Account, info : AccountInfo) -> unit = {
let empty = account_info_empty(info);
if empty then {
storage_tx_clear(a)
};
let next = account_set_info(acc, info);
if (next.info.storage_root != acc.info.storage_root)
| (next.present != acc.present)
| (next.storage_cleared != acc.storage_cleared) then {
store_account(a, next)
} else {
if next.info.balance != acc.info.balance then {
acct_tx_set_balance(a, next.info.balance)
};
if next.info.nonce != acc.info.nonce then {
acct_tx_set_nonce(a, next.info.nonce)
};
if next.info.code_hash != acc.info.code_hash then {
acct_tx_set_code_hash(a, next.info.code_hash)
}
}
}The reason a block fails validation; one variant per violated block-validity rule.
enum FatalError = {
/* chain config: wrong fork / inactive activation */
InvalidConfig,
/* witness ancestor headers not contiguous */
HeaderChainBroken,
/* a transaction failed to RLP-decode */
RlpDecode,
/* a tx signature did not authenticate its sender */
InvalidSignature,
/* header.gas_limit is outside the consensus domain */
InvalidGasLimit,
/* EIP-7778: a tx exceeds the block's remaining gas */
GasUsedExceedsLimit,
/* a tx exceeds the block's remaining blob gas */
BlobGasLimitExceeded,
/* an invalid tx or a failed block-end system call */
ExecutionInvalid,
/* recomputed cumulative gas != header.gas_used */
InvalidGasUsed,
/* recomputed blob gas != header.blob_gas_used */
InvalidBlobGasUsed,
/* header.excess_blob_gas != expected */
InvalidExcessBlobGas,
/* recomputed post-state root != header.state_root */
InvalidStateRoot,
/* recomputed receipts root != header.receipts_root */
InvalidReceiptsRoot,
/* recomputed logs bloom != header.logs_bloom */
InvalidLogsBloom,
/* recomputed block hash != payload expected hash */
InvalidBlockHash,
/* header.parent_hash != authenticated parent */
InvalidParentHash,
/* EIP-7928: BAL item count > gas_limit / 2000 */
BlockAccessListTooLarge,
/* reconstructed EIP-7928 BAL bytes mismatch */
InvalidBlockAccessList,
/* reconstructed EIP-7685 request bytes mismatch */
InvalidExecutionRequests,
/* a missing/inconsistent proof node (thrown at use) */
WitnessDeficient,
/* an exact protocol integer exceeds its bounded execution representation */
NumericOverflow,
}The largest account nonce admitted by EIP-2681.
type account_nonce_bound : Int = 2 ^ 64 - 1A 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)function k_add_balance¶
function k_add_balance(a, v) = {
let cur = k_aload(a);
let value_is_zero = word_is_zero(v);
let value_is_nonzero = not_bool(value_is_zero);
if value_is_nonzero then {
let balance = alu_add(cur.info.balance, v);
store_account_info(a, cur, { cur.info with balance = balance })
}
}function alu_add(a, b) = word_add(a, b)function k_add_balance(a, v) = {
let cur = k_aload(a);
let value_is_zero = word_is_zero(v);
let value_is_nonzero = not_bool(value_is_zero);
if value_is_nonzero then {
let balance = alu_add(cur.info.balance, v);
store_account_info(a, cur, { cur.info with balance = balance })
}
}Resolves 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
}val not_bool = pure {coq: "negb", lean: "_lean_not", _: "not"}: forall ('p : Bool). bool('p) -> bool(not('p))Applies a tuple change with minimal overlay traffic: scalar-field fast paths when existence/storage state is unchanged, and a storage clear when the tuple collapses to empty.
function store_account_info(a : address, acc : Account, info : AccountInfo) -> unit = {
let empty = account_info_empty(info);
if empty then {
storage_tx_clear(a)
};
let next = account_set_info(acc, info);
if (next.info.storage_root != acc.info.storage_root)
| (next.present != acc.present)
| (next.storage_cleared != acc.storage_cleared) then {
store_account(a, next)
} else {
if next.info.balance != acc.info.balance then {
acct_tx_set_balance(a, next.info.balance)
};
if next.info.nonce != acc.info.nonce then {
acct_tx_set_nonce(a, next.info.nonce)
};
if next.info.code_hash != acc.info.code_hash then {
acct_tx_set_code_hash(a, next.info.code_hash)
}
}
}function word_is_zero(w) = w == WORD_ZEROfunction k_sub_balance¶
Debits v wei (no-op when zero; caller guarantees sufficiency).
function k_sub_balance(a : address, v : word) -> unit = {
let cur = k_aload(a);
let value_is_zero = word_is_zero(v);
let value_is_nonzero = not_bool(value_is_zero);
if value_is_nonzero then {
let balance = alu_sub(cur.info.balance, v);
store_account_info(a, cur, { cur.info with balance = balance })
}
}function alu_sub(a : word, b : word) -> word = word_sub(a, b)Resolves 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
}val not_bool = pure {coq: "negb", lean: "_lean_not", _: "not"}: forall ('p : Bool). bool('p) -> bool(not('p))Applies a tuple change with minimal overlay traffic: scalar-field fast paths when existence/storage state is unchanged, and a storage clear when the tuple collapses to empty.
function store_account_info(a : address, acc : Account, info : AccountInfo) -> unit = {
let empty = account_info_empty(info);
if empty then {
storage_tx_clear(a)
};
let next = account_set_info(acc, info);
if (next.info.storage_root != acc.info.storage_root)
| (next.present != acc.present)
| (next.storage_cleared != acc.storage_cleared) then {
store_account(a, next)
} else {
if next.info.balance != acc.info.balance then {
acct_tx_set_balance(a, next.info.balance)
};
if next.info.nonce != acc.info.nonce then {
acct_tx_set_nonce(a, next.info.nonce)
};
if next.info.code_hash != acc.info.code_hash then {
acct_tx_set_code_hash(a, next.info.code_hash)
}
}
}function word_is_zero(w) = w == WORD_ZEROA 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_clear_storage¶
Clears the account's storage (create-time collision cleanup).
function k_clear_storage(a : address) -> unit = {
let cur = k_aload(a);
storage_tx_clear(a);
let cleared = account_clear_storage(cur);
store_account(a, cleared)
}Marks the account's storage cleared (fresh storage generation).
function account_clear_storage(acc : Account) -> Account =
{
acc with
storage_cleared = true,
}Resolves 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
}Drops every transaction-layer storage row of an address — the storage side of an account collapsing to empty or clearing its storage.
val storage_tx_clear = impure { c: "storage_tx_clear" } : address -> unitWrites a whole-account row to the transaction overlay.
function store_account(a : address, v : Account) -> unit =
acct_tx_update(a, v)A 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)