The execution environment¶
The block- and transaction-level environment registers, and the
environment reads the evm layer serves opcodes from.
register k_parent_state_root¶
The authenticated parent state root, the anchor of every stateless read.
let ZERO_HASH : hash = hash_from_bits(0x0000000000000000000000000000000000000000000000000000000000000000)The common digest type used by trie, code, and block hashes.
type hash = b256register k_n_headers¶
The number of witnessed ancestor headers (k_blockhash range).
register k_n_headers : ancestor_hash_count = 0The number of authenticated ancestor hashes available to BLOCKHASH.
type ancestor_hash_count = range(0, 256)register k_chain_id¶
The chain id (CHAINID, EIP-155 domains).
register k_chain_id : chain_identifier = 1A chain identifier. Typed-transaction chain identifiers and the stateless chain configuration are decoded as unsigned 64-bit integers.
type chain_identifier = range(0, chain_identifier_bound)register k_execution_profile¶
The active protocol policy and all gas limits derived from the executing header, selected together while decoding the stateless input.
register k_execution_profile : ExecutionProfile = DEFAULT_EXECUTION_PROFILElet DEFAULT_EXECUTION_PROFILE : ExecutionProfile = execution_profile_for(DEFAULT_PROTOCOL_PROFILE, 0)An execution profile with its thirteen indices packed existentially.
type ExecutionProfile = {
'fork
'target
'maximum
'denominator
'code_limit
'initcode_limit
'profile_total_limit
'profile_regular_limit
'transaction_blob_limit
'refund_divisor
'block_limit
'transaction_total_limit
'transaction_regular_limit,
execution_profile_parameters(
'fork,
'target,
'maximum,
'denominator,
'code_limit,
'initcode_limit,
'profile_total_limit,
'profile_regular_limit,
'transaction_blob_limit,
'refund_divisor,
'block_limit,
'transaction_total_limit,
'transaction_regular_limit,
).
ExecutionProfileFields(
'fork,
'target,
'maximum,
'denominator,
'code_limit,
'initcode_limit,
'profile_total_limit,
'profile_regular_limit,
'transaction_blob_limit,
'refund_divisor,
'block_limit,
'transaction_total_limit,
'transaction_regular_limit,
)
}register k_header¶
The executing payload's header.
register k_header : BlockHeader =
struct {
number = 0,
timestamp = 0,
extra_data = EMPTY_STATELESS_INPUT_SLICE,
gas_limit = 0,
gas_used = 0,
prev_randao = ZERO_WORD,
base_fee = ZERO_WORD,
blob_gas_used = 0,
excess_blob_gas = 0,
state_root = ZERO_HASH,
receipts_root = ZERO_HASH,
logs_bloom = stateless_input_slice(0, 256),
fee_recipient = ZERO_ADDRESS,
parent_hash = ZERO_HASH,
parent_beacon_block_root = ZERO_HASH,
slot_number = 0,
}function stateless_input_slice(off, len) =
struct { bytes = off, len = len }let EMPTY_STATELESS_INPUT_SLICE : StatelessInputSliceFields(0, 0) = stateless_input_slice(0, 0)let ZERO_ADDRESS : address = address_from_bits(0x0000000000000000000000000000000000000000000000000000000000000000)let ZERO_HASH : hash = hash_from_bits(0x0000000000000000000000000000000000000000000000000000000000000000)let ZERO_WORD : word = word_from_bits(0x0000000000000000000000000000000000000000000000000000000000000000)The execution-payload header fields the model reads and validates
(YP §4.4). Scalar wire bounds come from the consensus/Amsterdam SSZ
ExecutionPayload schema. gas_used <= gas_limit and the active
blob-schedule rules are execution-protocol constraints checked when the
payload is admitted. extra_data retains the schema's
ByteList[MAX_EXTRA_DATA_BYTES] bound while staying source-backed; it is
RLP-encoded whole for the header hash and never inspected. The fixed
logs_bloom commitment likewise stays source-backed until a semantic
consumer explicitly decodes it.
struct BlockHeader = {
number : block_number,
timestamp : block_timestamp,
gas_limit : block_gas_limit,
gas_used : block_gas,
prev_randao : word,
base_fee : word,
/* EIP-4844: a multiple of GAS_PER_BLOB within the active schedule. */
blob_gas_used : blob_gas_used,
/* EIP-4844 uint64 on the wire; narrowed to the documented reachable-chain
invariant at the authenticated input boundary. */
excess_blob_gas : excess_blob_gas,
state_root : hash,
receipts_root : hash,
logs_bloom : LogsBloomRef,
fee_recipient : address,
parent_hash : hash,
parent_beacon_block_root : hash,
/* uint64 (EIP-7843 and the Amsterdam stateless SSZ schema). */
slot_number : slot_number,
extra_data : StatelessInputSliceAtMost(extra_data_length_bound),
}Blob gas used by one supported block. The existential count retains that
every value is exactly a multiple of GAS_PER_BLOB; profile-indexed
decoding applies the selected schedule's tighter range before values enter
this heterogeneous header domain.
type blob_gas_used = {
'count,
0 <= 'count
& 'count <= bpo2_blob_max_count.
int(gas_per_blob_value * 'count)
}The accumulated excess blob gas carried between headers (EIP-4844).
type excess_blob_gas = range(0, excess_blob_gas_bound)A beacon-chain slot number. Provenance: EIP-7843 and Amsterdam's
stateless SSZ payload declare this field as uint64.
type slot_number = range(0, ssz_uint_bound)register k_tx¶
The per-transaction environment (ORIGIN, GASPRICE,
BLOBHASH).
register k_tx : TxEnv =
struct { origin = ZERO_ADDRESS, gas_price = ZERO_WORD, blob_hashes = EMPTY_BLOB_HASHES } :
TxEnvFields(blob_schedule_inactive_count)let EMPTY_BLOB_HASHES : BlobHashesFields(blob_schedule_inactive_count) = struct {
bytes = EMPTY_STATELESS_INPUT_SLICE,
count = 0,
} :
BlobHashesFields(blob_schedule_inactive_count)let ZERO_ADDRESS : address = address_from_bits(0x0000000000000000000000000000000000000000000000000000000000000000)let ZERO_WORD : word = word_from_bits(0x0000000000000000000000000000000000000000000000000000000000000000)A transaction environment packing its fork-selected blob-count limit existentially.
type TxEnv = {
'blob_limit,
transaction_blob_limit_value('blob_limit).
TxEnvFields('blob_limit)
}The per-transaction environment: ORIGIN/GASPRICE (YP I_o, I_p)
plus the EIP-4844 blob versioned hashes the BLOBHASH opcode reads.
struct TxEnvFields('blob_limit : Int), transaction_blob_limit_value('blob_limit) = {
origin : address,
gas_price : word,
blob_hashes : BlobHashesFields('blob_limit),
}Named blob-schedule constants retained by the profile domains below.
type blob_schedule_inactive_count : Int = 0register k_current_transaction_epoch¶
The current execution epoch: zero for pre-execution effects, transaction index plus one during transaction execution, and transaction count plus one for post-execution effects. EIP-2929 warmth and EIP-7928 BAL changes share this transaction-scoped identity.
register k_current_transaction_epoch : block_access_index = 0EIP-7928 change position: pre-execution system calls use zero,
transactions use their one-based position, and post-execution system calls
use transaction_count + 1.
type block_access_index = range(0, transaction_count_bound + 1)type EnvField¶
The closed environment-projection algebra interpreted by k_env. Each opcode supplies one constant member, so the shared projection remains explicit and first-order for executable and proof backends.
enum EnvField = {
/* block number (`NUMBER`) */
F_Number,
/* block timestamp (`TIMESTAMP`) */
F_Timestamp,
/* beneficiary address (`COINBASE`) */
F_Coinbase,
/* block base fee (`BASEFEE`, EIP-3198) */
F_BaseFee,
/* chain identifier (`CHAINID`, EIP-1344) */
F_ChainId,
/* block gas limit (`GASLIMIT`) */
F_GasLimit,
/* randomness beacon (`PREVRANDAO`, EIP-4399) */
F_PrevRandao,
/* transaction sender (`ORIGIN`) */
F_Origin,
/* effective gas price (`GASPRICE`) */
F_GasPrice,
/* consensus slot number (`SLOTNUM`, EIP-7843) */
F_SlotNumber,
}function k_env¶
An environment field as the word its opcode pushes.
function k_env(f : EnvField) -> word = {
let active_tx = k_tx;
match f {
F_Number => {
let number = word_of_block_number(k_header.number);
u256(number)
},
F_Timestamp => {
let timestamp = word_of_block_timestamp(k_header.timestamp);
u256(timestamp)
},
F_Coinbase => address_to_word(k_header.fee_recipient),
F_BaseFee => k_header.base_fee,
F_ChainId => {
let chain_id = word_of_chain_identifier(k_chain_id);
u256(chain_id)
},
F_GasLimit => u256(k_header.gas_limit),
F_PrevRandao => k_header.prev_randao,
F_Origin => address_to_word(active_tx.origin),
F_GasPrice => active_tx.gas_price,
F_SlotNumber => {
let slot_number = word_of_slot_number(k_header.slot_number);
u256(slot_number)
},
}
}Embeds a canonical-order address into the low 160 bits of an EVM word.
function address_to_word(bytes : address) -> word =
unsigned(
bytes[0]
@ bytes[1]
@ bytes[2]
@ bytes[3]
@ bytes[4]
@ bytes[5]
@ bytes[6]
@ bytes[7]
@ bytes[8]
@ bytes[9]
@ bytes[10]
@ bytes[11]
@ bytes[12]
@ bytes[13]
@ bytes[14]
@ bytes[15]
@ bytes[16]
@ bytes[17]
@ bytes[18]
@ bytes[19],
)function u256(value) = valueEmbeds the SSZ-bounded block number in the EVM word domain. The uint64 schema proof makes a runtime 256-bit range check unnecessary.
function word_of_block_number(value : block_number) -> block_number = valueEmbeds the SSZ-bounded block timestamp in the EVM word domain. The uint64 schema proof makes a runtime 256-bit range check unnecessary.
function word_of_block_timestamp(value : block_timestamp) -> block_timestamp = valueConverts a chain identifier to the value exposed by CHAINID.
function word_of_chain_identifier(value : chain_identifier) -> chain_identifier = valueEmbeds an EIP-7843 slot number in the EVM word domain.
function word_of_slot_number(value : slot_number) -> slot_number = valueThe chain id (CHAINID, EIP-155 domains).
register k_chain_id : chain_identifier = 1The executing payload's header.
register k_header : BlockHeader =
struct {
number = 0,
timestamp = 0,
extra_data = EMPTY_STATELESS_INPUT_SLICE,
gas_limit = 0,
gas_used = 0,
prev_randao = ZERO_WORD,
base_fee = ZERO_WORD,
blob_gas_used = 0,
excess_blob_gas = 0,
state_root = ZERO_HASH,
receipts_root = ZERO_HASH,
logs_bloom = stateless_input_slice(0, 256),
fee_recipient = ZERO_ADDRESS,
parent_hash = ZERO_HASH,
parent_beacon_block_root = ZERO_HASH,
slot_number = 0,
}The per-transaction environment (ORIGIN, GASPRICE,
BLOBHASH).
register k_tx : TxEnv =
struct { origin = ZERO_ADDRESS, gas_price = ZERO_WORD, blob_hashes = EMPTY_BLOB_HASHES } :
TxEnvFields(blob_schedule_inactive_count)The closed environment-projection algebra interpreted by k_env. Each opcode supplies one constant member, so the shared projection remains explicit and first-order for executable and proof backends.
enum EnvField = {
/* block number (`NUMBER`) */
F_Number,
/* block timestamp (`TIMESTAMP`) */
F_Timestamp,
/* beneficiary address (`COINBASE`) */
F_Coinbase,
/* block base fee (`BASEFEE`, EIP-3198) */
F_BaseFee,
/* chain identifier (`CHAINID`, EIP-1344) */
F_ChainId,
/* block gas limit (`GASLIMIT`) */
F_GasLimit,
/* randomness beacon (`PREVRANDAO`, EIP-4399) */
F_PrevRandao,
/* transaction sender (`ORIGIN`) */
F_Origin,
/* effective gas price (`GASPRICE`) */
F_GasPrice,
/* consensus slot number (`SLOTNUM`, EIP-7843) */
F_SlotNumber,
}A beacon-chain slot number. Provenance: EIP-7843 and Amsterdam's
stateless SSZ payload declare this field as uint64.
type slot_number = range(0, ssz_uint_bound)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_coinbase¶
The block's fee recipient (COINBASE).
The executing payload's header.
register k_header : BlockHeader =
struct {
number = 0,
timestamp = 0,
extra_data = EMPTY_STATELESS_INPUT_SLICE,
gas_limit = 0,
gas_used = 0,
prev_randao = ZERO_WORD,
base_fee = ZERO_WORD,
blob_gas_used = 0,
excess_blob_gas = 0,
state_root = ZERO_HASH,
receipts_root = ZERO_HASH,
logs_bloom = stateless_input_slice(0, 256),
fee_recipient = ZERO_ADDRESS,
parent_hash = ZERO_HASH,
parent_beacon_block_root = ZERO_HASH,
slot_number = 0,
}A 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)function blockhash_word_distance¶
function blockhash_word_distance(current, number) = current - numberfunction blockhash_word_distance(current, number) = current - numberfunction k_blockhash¶
BLOCKHASH: the hash of ancestor number, zero outside the 256-block
window; an in-window ancestor missing from the witness is a deficient
witness.
function k_blockhash(number_word : word) -> hash = {
let current = k_header.number;
let current_number = word_of_block_number(current);
let current_word : word = u256(current_number);
if number_word < current_word then {
let distance_word = blockhash_word_distance(current_word, number_word);
if distance_word <= 256 then {
let distance : range(1, 256) = distance_word;
if k_n_headers < distance then {
fatal_error(WitnessDeficient)
} else {
let index : ancestor_index = distance - 1;
ancestor_hash_read(index)
}
} else {
ZERO_HASH
}
} else {
ZERO_HASH
}
}The recorded ancestor hash at a distance index (the BLOCKHASH
source).
val ancestor_hash_read = impure { c: "ancestor_hash_read" } : ancestor_index -> hashfunction blockhash_word_distance(current, number) = current - numberfunction fatal_error(_reason) = exit(())function u256(value) = valueEmbeds the SSZ-bounded block number in the EVM word domain. The uint64 schema proof makes a runtime 256-bit range check unnecessary.
function word_of_block_number(value : block_number) -> block_number = valuelet ZERO_HASH : hash = hash_from_bits(0x0000000000000000000000000000000000000000000000000000000000000000)The executing payload's header.
register k_header : BlockHeader =
struct {
number = 0,
timestamp = 0,
extra_data = EMPTY_STATELESS_INPUT_SLICE,
gas_limit = 0,
gas_used = 0,
prev_randao = ZERO_WORD,
base_fee = ZERO_WORD,
blob_gas_used = 0,
excess_blob_gas = 0,
state_root = ZERO_HASH,
receipts_root = ZERO_HASH,
logs_bloom = stateless_input_slice(0, 256),
fee_recipient = ZERO_ADDRESS,
parent_hash = ZERO_HASH,
parent_beacon_block_root = ZERO_HASH,
slot_number = 0,
}The number of witnessed ancestor headers (k_blockhash range).
register k_n_headers : ancestor_hash_count = 0The 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,
}An index into the 256 most recent ancestor block hashes.
type ancestor_index = range(0, 255)The common digest type used by trie, code, and block hashes.
type hash = b256The 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_blobhash¶
BLOBHASH (EIP-4844): the i-th versioned hash, zero out of
range.
function k_blobhash(index_word : word) -> word = {
let active_tx = k_tx;
let count = active_tx.blob_hashes.count;
if index_word < count then {
let index = index_word;
let offset : source_pointer = 33 * index + 1;
slice_load_n(active_tx.blob_hashes.bytes, offset, WORD_BYTE_LENGTH)
} else {
ZERO_WORD
}
}let WORD_BYTE_LENGTH : int(32) = 32let ZERO_WORD : word = word_from_bits(0x0000000000000000000000000000000000000000000000000000000000000000)The per-transaction environment (ORIGIN, GASPRICE,
BLOBHASH).
register k_tx : TxEnv =
struct { origin = ZERO_ADDRESS, gas_price = ZERO_WORD, blob_hashes = EMPTY_BLOB_HASHES } :
TxEnvFields(blob_schedule_inactive_count)An absolute byte position in a named source region.
type source_pointer = range(0, default_host_region_bound)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_create_addr¶
The CREATE address rule, in kernel form.
function k_create_addr(a : address, nonce : account_nonce) -> address = create_address(a, nonce)The CREATE address (YP §7): the low 20 bytes of
keccak256(rlp([sender, nonce])).
function create_address(sender : address, nonce : account_nonce) -> address = {
let address_length = rlp_addr_size();
let nonce_length = rlp_uint_word_size(nonce);
let content_len = address_length + nonce_length;
let encoded_len = rlp_list_size(content_len);
let encoder = rlp_encoder_begin(encoded_len);
rlp_write_list_prefix(content_len);
rlp_write_addr(sender);
rlp_write_uint(nonce);
let encoded = rlp_encoder_finish(encoder);
let digest = keccak256(encoded);
let digest_word = hash_to_word(digest);
let address = word_to_address(digest_word);
rlp_encoder_rewind(encoder);
address
}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_create2_addr¶
The CREATE2 address rule, in kernel form.
function k_create2_addr(a : address, salt : word, inithash : hash) -> address =
create2_address(a, salt, inithash)The CREATE2 address (EIP-1014): the low 20 bytes of
keccak256(0xff ++ sender ++ salt ++ keccak256(initcode)).
function create2_address(sender : address, salt : word, init_hash : hash) -> address = {
let mark = scratch_reserve(85);
scratch_push_byte(0xff);
scratch_push_address(sender);
let salt_hash = word_to_hash(salt);
scratch_push_b256(salt_hash, WORD_BYTE_LENGTH);
scratch_push_b256(init_hash, WORD_BYTE_LENGTH);
let preimage = scratch_finish(mark);
let digest = keccak256(preimage);
scratch_rewind(mark);
let digest_word = hash_to_word(digest);
word_to_address(digest_word)
}A 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)The common digest type used by trie, code, and block hashes.
type hash = b256The 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)