The transaction state transition¶
The per-transaction step of the Ethereum state transition (Yellow Paper §6): validate the transaction, charge upfront gas, run it as the top-level message call, then settle gas, refunds, and the coinbase fee.
Validity and intrinsic gas are user-space policy; every world effect is a
kernel syscall, and a transaction-level snapshot/commit/revert bounds the
whole transaction's world state. The typed-envelope rules covered here:
EIP-2718 (typed transactions), EIP-1559 (fee market: base fee + priority
tip caps), EIP-2930 (access lists), EIP-3860 (initcode size/gas),
EIP-4844 (blob transactions + blob-gas burn), EIP-7702 (set-code
authorizations), EIP-7623 (calldata floor), EIP-3607 (no transactions
from an account with code), EIP-7825 (gas cap). Gas refunds (SSTORE
clears) are capped per EIP-3529.
| Name | Value | Description |
|---|---|---|
G_transaction |
21000 |
Per-transaction base cost |
G_txcreate |
32000 |
Create-transaction surcharge |
G_txdatazero |
4 |
Per zero calldata byte (EIP-2028) |
G_txdatanonzero |
16 |
Per nonzero calldata byte (EIP-2028) |
G_access_list_address |
2400 |
Per access-list address (EIP-2930) |
G_access_list_storage_key |
1900 |
Per access-list storage key (EIP-2930) |
PER_AUTH_BASE |
12500 |
Per authorization (EIP-7702) |
PER_EMPTY_ACCOUNT |
25000 |
Per authorization of a new account (EIP-7702) |
let G_transaction¶
let G_transaction : int(21000) = 21000let G_txcreate¶
let G_txcreate : int(32000) = 32000let G_txdatazero¶
let G_txdatazero : int(4) = 4let G_txdatanonzero¶
let G_txdatanonzero : int(16) = 16let G_access_list_address¶
let G_access_list_address : int(2400) = 2400let G_access_list_storage_key¶
let G_access_list_storage_key : int(1900) = 1900let PER_AUTH_BASE¶
let PER_AUTH_BASE : int(12500) = 12500let PER_EMPTY_ACCOUNT¶
let PER_EMPTY_ACCOUNT : int(25000) = 25000type authorization_refund_per_item¶
The refund available when one EIP-7702 authorization targets an existing account.
type authorization_refund_per_item : Int = 12500type authorization_item_refund¶
The bounded refund contributed by one EIP-7702 authorization.
type authorization_item_refund = range(0, authorization_refund_per_item)The refund available when one EIP-7702 authorization targets an existing account.
type authorization_refund_per_item : Int = 12500type authorization_refund¶
The aggregate EIP-7702 refund admitted by one transaction.
type authorization_refund = range(
0,
authorization_refund_per_item * transaction_length_bound,
)The refund available when one EIP-7702 authorization targets an existing account.
type authorization_refund_per_item : Int = 12500Maximum byte length of one encoded transaction envelope. Provenance:
Amsterdam MAX_BYTES_PER_TRANSACTION in
SszExecutionPayload.transactions.
type transaction_length_bound : Int = 2 ^ 30let AMSTERDAM_TX_BASE¶
let AMSTERDAM_TX_BASE : int(12000) = 12000let AMSTERDAM_CREATE_ACCESS¶
let AMSTERDAM_CREATE_ACCESS : int(11000) = 11000let AMSTERDAM_COLD_ACCOUNT_ACCESS¶
let AMSTERDAM_COLD_ACCOUNT_ACCESS : int(3000) = 3000let AMSTERDAM_TX_VALUE_COST¶
let AMSTERDAM_TX_VALUE_COST : int(4244) = 4244let AMSTERDAM_TRANSFER_LOG_COST¶
let AMSTERDAM_TRANSFER_LOG_COST : int(1756) = 1756let AMSTERDAM_ACCESS_LIST_ADDRESS¶
let AMSTERDAM_ACCESS_LIST_ADDRESS : int(3000) = 3000let AMSTERDAM_ACCESS_LIST_SLOT¶
let AMSTERDAM_ACCESS_LIST_SLOT : int(3000) = 3000let AMSTERDAM_ACCESS_LIST_ADDRESS_FLOOR¶
let AMSTERDAM_ACCESS_LIST_ADDRESS_FLOOR : int(1280) = 1280let AMSTERDAM_ACCESS_LIST_SLOT_FLOOR¶
let AMSTERDAM_ACCESS_LIST_SLOT_FLOOR : int(2048) = 2048let AMSTERDAM_AUTH_BASE¶
let AMSTERDAM_AUTH_BASE : int(7816) = 7816let AMSTERDAM_CALLDATA_FLOOR_BYTE¶
let AMSTERDAM_CALLDATA_FLOOR_BYTE : int(64) = 64type IntrinsicGasCost¶
Intrinsic transaction charges split into Amsterdam execution gas, state gas, and the calldata floor.
A transient computed charge after its affordability or structural bound
has been established. Unaffordable larger computations are represented by
GasCharge.affordable = false rather than materialized as a cost.
type gas_cost = range(0, 2 ^ 64 - 1)type TransactionCosts¶
The intrinsic, blob, and upfront costs established during validation.
struct TransactionCosts = {
intrinsic_execution : gas_cost,
intrinsic_state : gas_cost,
calldata_floor : gas_cost,
blob_gas : transaction_blob_gas,
blob_fee : word,
upfront : word,
}A transient computed charge after its affordability or structural bound
has been established. Unaffordable larger computations are represented by
GasCharge.affordable = false rather than materialized as a cost.
type gas_cost = range(0, 2 ^ 64 - 1)Blob gas contributed by one transaction. Profile-indexed blob-hash decoding establishes the selected limit before this derived quantity is widened to the common transaction-cost domain.
type transaction_blob_gas = {
'count,
0 <= 'count
& 'count <= prague_blob_max_count.
int(gas_per_blob_value * 'count)
}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 transaction_initcode_slice¶
Reclassifies transaction initcode as executable after re-establishing the enclosing SSZ transaction-envelope bound. This bound is structural and is deliberately independent of the active protocol deployment limit.
function transaction_initcode_slice(input : TransactionInputSlice) -> CodeSlice = {
let input_slice = stateless_input_slice(input.bytes, input.len);
code_db_intern_input(input_slice)
}Normalizes stateless-input code into the code arena before analysis.
function code_db_intern_input(bytes : StatelessInputSlice) -> CodeSlice = {
let region = code_region_from_input(bytes);
validated_code_slice(region)
}function stateless_input_slice(off, len) =
struct { bytes = off, len = len }A source-backed executable byte span. Its length carries the separate representation invariant needed by program-counter arithmetic; this is not a protocol deployment-size limit.
type CodeSlice = {
'off 'len,
code_region_valid_range('off, 'len) & code_valid_length('len).
CodeRegionSliceFields('off, 'len)
}A byte span contained by one SSZ transaction envelope. This structural bound is independent of fork-specific calldata and initcode limits.
type TransactionInputSlice = {
'off 'len,
source_valid_range('off, 'len) & 0 <= 'len & 'len <= transaction_length_bound.
StatelessInputSliceFields('off, 'len)
}type TxUpfrontResult¶
Values established before entering the top-level transaction frame.
struct TxUpfrontResult = {
authorization_refund : authorization_refund,
create_target_prestate_empty : bool,
}The aggregate EIP-7702 refund admitted by one transaction.
type authorization_refund = range(
0,
authorization_refund_per_item * transaction_length_bound,
)function calldata_cost¶
The EIP-2028 calldata cost: 4 gas per zero byte, 16 per nonzero. One native pass counts the nonzero bytes; zero bytes are the remainder.
function calldata_cost(input : TransactionInputSlice) -> transaction_calldata_cost = {
let nonzeroes = slice_count_nonzero(input);
let input_len = input.len;
if nonzeroes <= input_len then {
let zeroes = input_len - nonzeroes;
G_txdatazero * zeroes + G_txdatanonzero * nonzeroes
} else {
fatal_error(ExecutionInvalid)
}
}function fatal_error(_reason) = exit(())function slice_count_nonzero(s : StatelessInputSlice) -> stateless_input_length =
stateless_input_count_nonzero(s)let G_txdatanonzero : int(16) = 16let G_txdatazero : int(4) = 4The 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,
}A byte span contained by one SSZ transaction envelope. This structural bound is independent of fork-specific calldata and initcode limits.
type TransactionInputSlice = {
'off 'len,
source_valid_range('off, 'len) & 0 <= 'len & 'len <= transaction_length_bound.
StatelessInputSliceFields('off, 'len)
}The greatest EIP-2028 calldata charge admitted by an SSZ transaction.
type transaction_calldata_cost = range(0, 16 * transaction_length_bound)function legacy_intrinsic_gas¶
The intrinsic gas of a transaction (YP §6.2, g_0): the 21000 base,
calldata cost, access-list cost (EIP-2930), authorization cost
(EIP-7702), and for creates the G_txcreate base plus EIP-3860
initcode words.
function legacy_intrinsic_gas(tx : Transaction) -> gas_cost = {
let data_cost = calldata_cost(tx.input_src);
let input = tx.input_src;
let input_len = input.len;
let address_cost = G_access_list_address * tx.access_list.address_count;
let slot_cost = G_access_list_storage_key * tx.access_list.slot_count;
let authorizations = tx.authorizations;
let auth_cost = PER_EMPTY_ACCOUNT * authorizations.count;
let common = data_cost + G_transaction + address_cost + slot_cost + auth_cost;
if tx.is_create then {
common + G_txcreate + transaction_initcode_gas(input_len)
} else {
common
}
}The EIP-2028 calldata cost: 4 gas per zero byte, 16 per nonzero. One native pass counts the nonzero bytes; zero bytes are the remainder.
function calldata_cost(input : TransactionInputSlice) -> transaction_calldata_cost = {
let nonzeroes = slice_count_nonzero(input);
let input_len = input.len;
if nonzeroes <= input_len then {
let zeroes = input_len - nonzeroes;
G_txdatazero * zeroes + G_txdatanonzero * nonzeroes
} else {
fatal_error(ExecutionInvalid)
}
}The EIP-3860 charge for transaction-backed initcode, retaining the SSZ transaction-envelope ceiling for native cost aggregation.
function transaction_initcode_gas(byte_len : transaction_byte_length) -> transaction_initcode_cost = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
if profile.fork >= Shanghai then {
let words = memory_word_count(byte_len);
words + words
} else {
0
}
}let G_access_list_address : int(2400) = 2400let G_access_list_storage_key : int(1900) = 1900let G_transaction : int(21000) = 21000let G_txcreate : int(32000) = 32000let PER_EMPTY_ACCOUNT : int(25000) = 25000A decoded transaction retains the fork-selected blob-count limit that was applied while decoding its envelope. Non-blob transactions carry the inactive zero limit.
type Transaction = {
'blob_limit,
transaction_blob_limit_value('blob_limit).
TransactionFields('blob_limit)
}A transient computed charge after its affordability or structural bound
has been established. Unaffordable larger computations are represented by
GasCharge.affordable = false rather than materialized as a cost.
type gas_cost = range(0, 2 ^ 64 - 1)function legacy_calldata_floor¶
Computes the pre-Amsterdam EIP-7623 calldata floor cost.
function legacy_calldata_floor(input : TransactionInputSlice) -> transaction_calldata_floor_cost = {
let nonzeroes = slice_count_nonzero(input);
let input_len = input.len;
if nonzeroes <= input_len then {
let zeroes = input_len - nonzeroes;
10 * zeroes + 40 * nonzeroes + G_transaction
} else {
fatal_error(ExecutionInvalid)
}
}function fatal_error(_reason) = exit(())function slice_count_nonzero(s : StatelessInputSlice) -> stateless_input_length =
stateless_input_count_nonzero(s)let G_transaction : int(21000) = 21000The 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,
}A byte span contained by one SSZ transaction envelope. This structural bound is independent of fork-specific calldata and initcode limits.
type TransactionInputSlice = {
'off 'len,
source_valid_range('off, 'len) & 0 <= 'len & 'len <= transaction_length_bound.
StatelessInputSliceFields('off, 'len)
}The greatest EIP-7623 calldata floor admitted by an SSZ transaction.
type transaction_calldata_floor_cost = range(0, 40 * transaction_length_bound + 21000)type amsterdam_recipient_cost¶
The maximum Amsterdam recipient-side intrinsic execution charge.
type amsterdam_recipient_cost = range(0, 12756)function amsterdam_recipient_execution_cost¶
Computes Amsterdam execution-gas charges for recipient access, value transfer, and contract creation.
function amsterdam_recipient_execution_cost(tx : Transaction) -> amsterdam_recipient_cost = {
let transfers_value = word_nonzero(tx.value);
if tx.is_create then {
if transfers_value then {
AMSTERDAM_CREATE_ACCESS + AMSTERDAM_TRANSFER_LOG_COST
} else {
AMSTERDAM_CREATE_ACCESS
}
} else if tx.recipient != tx.sender then {
if transfers_value then {
AMSTERDAM_COLD_ACCOUNT_ACCESS + AMSTERDAM_TX_VALUE_COST + AMSTERDAM_TRANSFER_LOG_COST
} else {
AMSTERDAM_COLD_ACCOUNT_ACCESS
}
} else {
0
}
}function word_nonzero(w) = {
let is_zero = word_is_zero(w);
not_bool(is_zero)
}let AMSTERDAM_COLD_ACCOUNT_ACCESS : int(3000) = 3000let AMSTERDAM_CREATE_ACCESS : int(11000) = 11000let AMSTERDAM_TRANSFER_LOG_COST : int(1756) = 1756let AMSTERDAM_TX_VALUE_COST : int(4244) = 4244A decoded transaction retains the fork-selected blob-count limit that was applied while decoding its envelope. Non-blob transactions carry the inactive zero limit.
type Transaction = {
'blob_limit,
transaction_blob_limit_value('blob_limit).
TransactionFields('blob_limit)
}The maximum Amsterdam recipient-side intrinsic execution charge.
type amsterdam_recipient_cost = range(0, 12756)function intrinsic_gas¶
Computes the fork-specific execution/state intrinsic costs and calldata floor. Amsterdam decomposes the transaction charge into the two gas dimensions introduced by EIP-2780.
function intrinsic_gas(tx : Transaction) -> IntrinsicGasCost = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
if profile.fork < Amsterdam then {
let execution = legacy_intrinsic_gas(tx);
let calldata_floor = legacy_calldata_floor(tx.input_src);
struct { execution = execution, state = 0, calldata_floor = calldata_floor }
} else {
let input = tx.input_src;
let recipient = amsterdam_recipient_execution_cost(tx);
let address_count = tx.access_list.address_count;
let slot_count = tx.access_list.slot_count;
let access_execution = AMSTERDAM_ACCESS_LIST_ADDRESS
* address_count
+ AMSTERDAM_ACCESS_LIST_SLOT
* slot_count
+ AMSTERDAM_ACCESS_LIST_ADDRESS_FLOOR
* address_count
+ AMSTERDAM_ACCESS_LIST_SLOT_FLOOR
* slot_count;
let authorizations = tx.authorizations;
let authorization_execution = AMSTERDAM_AUTH_BASE * authorizations.count;
let create_execution =
if tx.is_create then transaction_initcode_gas(input.len) else 0;
let execution = calldata_cost(tx.input_src)
+ AMSTERDAM_TX_BASE
+ recipient
+ access_execution
+ authorization_execution
+ create_execution;
let input_length = input.len;
let floor = AMSTERDAM_CALLDATA_FLOOR_BYTE
* input_length
+ AMSTERDAM_TX_BASE
+ recipient
+ AMSTERDAM_ACCESS_LIST_ADDRESS_FLOOR
* address_count
+ AMSTERDAM_ACCESS_LIST_SLOT_FLOOR
* slot_count;
struct { execution = execution, state = 0, calldata_floor = floor }
}
}Computes Amsterdam execution-gas charges for recipient access, value transfer, and contract creation.
function amsterdam_recipient_execution_cost(tx : Transaction) -> amsterdam_recipient_cost = {
let transfers_value = word_nonzero(tx.value);
if tx.is_create then {
if transfers_value then {
AMSTERDAM_CREATE_ACCESS + AMSTERDAM_TRANSFER_LOG_COST
} else {
AMSTERDAM_CREATE_ACCESS
}
} else if tx.recipient != tx.sender then {
if transfers_value then {
AMSTERDAM_COLD_ACCOUNT_ACCESS + AMSTERDAM_TX_VALUE_COST + AMSTERDAM_TRANSFER_LOG_COST
} else {
AMSTERDAM_COLD_ACCOUNT_ACCESS
}
} else {
0
}
}The EIP-2028 calldata cost: 4 gas per zero byte, 16 per nonzero. One native pass counts the nonzero bytes; zero bytes are the remainder.
function calldata_cost(input : TransactionInputSlice) -> transaction_calldata_cost = {
let nonzeroes = slice_count_nonzero(input);
let input_len = input.len;
if nonzeroes <= input_len then {
let zeroes = input_len - nonzeroes;
G_txdatazero * zeroes + G_txdatanonzero * nonzeroes
} else {
fatal_error(ExecutionInvalid)
}
}Computes the pre-Amsterdam EIP-7623 calldata floor cost.
function legacy_calldata_floor(input : TransactionInputSlice) -> transaction_calldata_floor_cost = {
let nonzeroes = slice_count_nonzero(input);
let input_len = input.len;
if nonzeroes <= input_len then {
let zeroes = input_len - nonzeroes;
10 * zeroes + 40 * nonzeroes + G_transaction
} else {
fatal_error(ExecutionInvalid)
}
}The intrinsic gas of a transaction (YP §6.2, g_0): the 21000 base,
calldata cost, access-list cost (EIP-2930), authorization cost
(EIP-7702), and for creates the G_txcreate base plus EIP-3860
initcode words.
function legacy_intrinsic_gas(tx : Transaction) -> gas_cost = {
let data_cost = calldata_cost(tx.input_src);
let input = tx.input_src;
let input_len = input.len;
let address_cost = G_access_list_address * tx.access_list.address_count;
let slot_cost = G_access_list_storage_key * tx.access_list.slot_count;
let authorizations = tx.authorizations;
let auth_cost = PER_EMPTY_ACCOUNT * authorizations.count;
let common = data_cost + G_transaction + address_cost + slot_cost + auth_cost;
if tx.is_create then {
common + G_txcreate + transaction_initcode_gas(input_len)
} else {
common
}
}The EIP-3860 charge for transaction-backed initcode, retaining the SSZ transaction-envelope ceiling for native cost aggregation.
function transaction_initcode_gas(byte_len : transaction_byte_length) -> transaction_initcode_cost = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
if profile.fork >= Shanghai then {
let words = memory_word_count(byte_len);
words + words
} else {
0
}
}let AMSTERDAM_ACCESS_LIST_ADDRESS : int(3000) = 3000let AMSTERDAM_ACCESS_LIST_ADDRESS_FLOOR : int(1280) = 1280let AMSTERDAM_ACCESS_LIST_SLOT : int(3000) = 3000let AMSTERDAM_ACCESS_LIST_SLOT_FLOOR : int(2048) = 2048let AMSTERDAM_AUTH_BASE : int(7816) = 7816let AMSTERDAM_CALLDATA_FLOOR_BYTE : int(64) = 64let AMSTERDAM_TX_BASE : int(12000) = 12000EIP-7954 code/initcode size bump (65536/131072).
let Amsterdam : int(amsterdam_fork_value) = sizeof(amsterdam_fork_value)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_PROFILEIntrinsic transaction charges split into Amsterdam execution gas, state gas, and the calldata floor.
struct IntrinsicGasCost = {
execution : gas_cost,
state : gas_cost,
calldata_floor : gas_cost,
}A decoded transaction retains the fork-selected blob-count limit that was applied while decoding its envelope. Non-blob transactions carry the inactive zero limit.
type Transaction = {
'blob_limit,
transaction_blob_limit_value('blob_limit).
TransactionFields('blob_limit)
}function transaction_blob_fee¶
function transaction_blob_fee(blob_price, blob_gas) =
blob_price * blob_gasfunction transaction_blob_fee(blob_price, blob_gas) =
blob_price * blob_gasfunction transaction_upfront_cost¶
function transaction_upfront_cost(max_fee, gas_limit, value, max_blob_fee, blob_gas) =
max_fee * gas_limit + value + max_blob_fee * blob_gasfunction transaction_upfront_cost(max_fee, gas_limit, value, max_blob_fee, blob_gas) =
max_fee * gas_limit + value + max_blob_fee * blob_gasfunction transaction_costs¶
Computes transaction costs as mathematical naturals, narrowing only the externally observable word-valued results.
function transaction_costs(
profile : ProtocolProfile,
tx : Transaction,
gas_limit : block_gas_limit,
excess_blob_gas : excess_blob_gas,
) -> (
TransactionCosts
) = {
let intrinsic = intrinsic_gas(tx);
let blob_gas : transaction_blob_gas = sizeof(gas_per_blob_value) * tx.blob_hashes.count;
let blob_fee_value : nat =
if blob_gas == 0 then {
0
} else {
let blob_price = blob_base_fee(
profile.fork,
profile.blob_schedule,
profile.excess_blob_gas_limit,
excess_blob_gas,
);
if blob_price <= tx.max_blob_fee then {
transaction_blob_fee(blob_price, blob_gas)
} else {
fatal_error(ExecutionInvalid)
}
};
let upfront_value = transaction_upfront_cost(tx.max_fee, gas_limit, tx.value, tx.max_blob_fee, blob_gas);
if (blob_fee_value < sizeof(word_modulus)) & (upfront_value < sizeof(word_modulus)) then {
struct {
intrinsic_execution = intrinsic.execution,
intrinsic_state = intrinsic.state,
calldata_floor = intrinsic.calldata_floor,
blob_gas = blob_gas,
blob_fee = protocol_word(blob_fee_value),
upfront = protocol_word(upfront_value),
}
} else {
fatal_error(ExecutionInvalid)
}
}function blob_base_fee(fork, schedule, limit, excess_blob_gas) = {
if (fork >= Cancun) & (excess_blob_gas <= limit) then {
fake_exponential_word(schedule, excess_blob_gas)
} else {
fatal_error(InvalidConfig)
}
}function blob_schedule(target, maximum, denominator) =
struct { target = target, max = maximum, base_fee_update_fraction = denominator }function fatal_error(_reason) = exit(())Computes the fork-specific execution/state intrinsic costs and calldata floor. Amsterdam decomposes the transaction charge into the two gas dimensions introduced by EIP-2780.
function intrinsic_gas(tx : Transaction) -> IntrinsicGasCost = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
if profile.fork < Amsterdam then {
let execution = legacy_intrinsic_gas(tx);
let calldata_floor = legacy_calldata_floor(tx.input_src);
struct { execution = execution, state = 0, calldata_floor = calldata_floor }
} else {
let input = tx.input_src;
let recipient = amsterdam_recipient_execution_cost(tx);
let address_count = tx.access_list.address_count;
let slot_count = tx.access_list.slot_count;
let access_execution = AMSTERDAM_ACCESS_LIST_ADDRESS
* address_count
+ AMSTERDAM_ACCESS_LIST_SLOT
* slot_count
+ AMSTERDAM_ACCESS_LIST_ADDRESS_FLOOR
* address_count
+ AMSTERDAM_ACCESS_LIST_SLOT_FLOOR
* slot_count;
let authorizations = tx.authorizations;
let authorization_execution = AMSTERDAM_AUTH_BASE * authorizations.count;
let create_execution =
if tx.is_create then transaction_initcode_gas(input.len) else 0;
let execution = calldata_cost(tx.input_src)
+ AMSTERDAM_TX_BASE
+ recipient
+ access_execution
+ authorization_execution
+ create_execution;
let input_length = input.len;
let floor = AMSTERDAM_CALLDATA_FLOOR_BYTE
* input_length
+ AMSTERDAM_TX_BASE
+ recipient
+ AMSTERDAM_ACCESS_LIST_ADDRESS_FLOOR
* address_count
+ AMSTERDAM_ACCESS_LIST_SLOT_FLOOR
* slot_count;
struct { execution = execution, state = 0, calldata_floor = floor }
}
}function protocol_word(value) = u256(value)function transaction_blob_fee(blob_price, blob_gas) =
blob_price * blob_gasfunction transaction_upfront_cost(max_fee, gas_limit, value, max_blob_fee, blob_gas) =
max_fee * gas_limit + value + max_blob_fee * blob_gasThe 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,
}A protocol profile with its parameter tuple packed existentially; unpacking recovers the admitted combination's equations.
type ProtocolProfile = {
'fork
'target
'maximum
'denominator
'code_limit
'initcode_limit
'transaction_total_gas_limit
'transaction_regular_gas_limit
'transaction_blob_limit
'refund_divisor,
protocol_profile_parameters(
'fork,
'target,
'maximum,
'denominator,
'code_limit,
'initcode_limit,
'transaction_total_gas_limit,
'transaction_regular_gas_limit,
'transaction_blob_limit,
'refund_divisor,
).
ProtocolProfileFields(
'fork,
'target,
'maximum,
'denominator,
'code_limit,
'initcode_limit,
'transaction_total_gas_limit,
'transaction_regular_gas_limit,
'transaction_blob_limit,
'refund_divisor,
)
}A decoded transaction retains the fork-selected blob-count limit that was applied while decoding its envelope. Non-blob transactions carry the inactive zero limit.
type Transaction = {
'blob_limit,
transaction_blob_limit_value('blob_limit).
TransactionFields('blob_limit)
}The intrinsic, blob, and upfront costs established during validation.
struct TransactionCosts = {
intrinsic_execution : gas_cost,
intrinsic_state : gas_cost,
calldata_floor : gas_cost,
blob_gas : transaction_blob_gas,
blob_fee : word,
upfront : word,
}A block header's gas limit. Its SSZ execution-payload field is uint64;
GASLIMIT widens this bounded natural into an EVM word when it pushes the
value onto the stack (Yellow Paper equation 147).
type block_gas_limit = range(0, block_gas_limit_bound)The accumulated excess blob gas carried between headers (EIP-4844).
type excess_blob_gas = range(0, excess_blob_gas_bound)Blob gas charged per blob, 2^17 (EIP-4844).
type gas_per_blob_value : Int = 2 ^ 17Blob gas contributed by one transaction. Profile-indexed blob-hash decoding establishes the selected limit before this derived quantity is widened to the common transaction-cost domain.
type transaction_blob_gas = {
'count,
0 <= 'count
& 'count <= prague_blob_max_count.
int(gas_per_blob_value * 'count)
}The type-level modulus of EVM-word arithmetic.
type word_modulus : Int = 2 ^ 256function validated_word_product¶
function validated_word_product(value, factor) = {
let product = value * factor;
if product < sizeof(word_modulus) then {
protocol_word(product)
} else {
fatal_error(ExecutionInvalid)
}
}function fatal_error(_reason) = exit(())function protocol_word(value) = u256(value)function validated_word_product(value, factor) = {
let product = value * factor;
if product < sizeof(word_modulus) then {
protocol_word(product)
} else {
fatal_error(ExecutionInvalid)
}
}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 type-level modulus of EVM-word arithmetic.
type word_modulus : Int = 2 ^ 256function tx_frame_gas_snapshot¶
function tx_frame_gas_snapshot(initial, execution, state, state_delta) = {
let limit = initial.admitted_limit;
let regular = initial.regular_limit;
if execution <= limit then {
let room = limit - execution;
if state <= room then {
let (remaining as 'remaining) = execution + state;
let spent = limit - remaining;
let raw_state_used : transaction_state_gas_delta = initial.intrinsic_state + state_delta;
if raw_state_used <= 0 then {
if spent <= regular then {
tx_frame_gas_snapshot_fields(limit, regular, initial.calldata_floor, remaining, 0)
} else {
fatal_error(ExecutionInvalid)
}
} else {
let positive_state_used : transaction_state_gas_used = raw_state_used;
if positive_state_used <= spent then {
/* The comparison is the semantic boundary that narrows the
signed transaction delta back into the admitted reservoir.
Name that refinement explicitly so optimized lowering can
convert directly to the concrete gas width instead of first
materializing the wider positive range of the signed sum. */
let (bounded_state_used as 'bounded_state_used) : range(0, 'limit - 'remaining) = positive_state_used;
if spent - bounded_state_used <= regular then {
tx_frame_gas_snapshot_fields(
limit,
regular,
initial.calldata_floor,
remaining,
bounded_state_used,
)
} else {
fatal_error(ExecutionInvalid)
}
} else {
fatal_error(ExecutionInvalid)
}
}
} else {
fatal_error(ExecutionInvalid)
}
} else {
fatal_error(ExecutionInvalid)
}
}function fatal_error(_reason) = exit(())function tx_frame_gas_snapshot(initial, execution, state, state_delta) = {
let limit = initial.admitted_limit;
let regular = initial.regular_limit;
if execution <= limit then {
let room = limit - execution;
if state <= room then {
let (remaining as 'remaining) = execution + state;
let spent = limit - remaining;
let raw_state_used : transaction_state_gas_delta = initial.intrinsic_state + state_delta;
if raw_state_used <= 0 then {
if spent <= regular then {
tx_frame_gas_snapshot_fields(limit, regular, initial.calldata_floor, remaining, 0)
} else {
fatal_error(ExecutionInvalid)
}
} else {
let positive_state_used : transaction_state_gas_used = raw_state_used;
if positive_state_used <= spent then {
/* The comparison is the semantic boundary that narrows the
signed transaction delta back into the admitted reservoir.
Name that refinement explicitly so optimized lowering can
convert directly to the concrete gas width instead of first
materializing the wider positive range of the signed sum. */
let (bounded_state_used as 'bounded_state_used) : range(0, 'limit - 'remaining) = positive_state_used;
if spent - bounded_state_used <= regular then {
tx_frame_gas_snapshot_fields(
limit,
regular,
initial.calldata_floor,
remaining,
bounded_state_used,
)
} else {
fatal_error(ExecutionInvalid)
}
} else {
fatal_error(ExecutionInvalid)
}
}
} else {
fatal_error(ExecutionInvalid)
}
} else {
fatal_error(ExecutionInvalid)
}
}function tx_frame_gas_snapshot_fields(limit, regular, calldata_floor, remaining, state_used) =
struct {
admitted_limit = limit,
regular_limit = regular,
calldata_floor = calldata_floor,
remaining = remaining,
state_used = state_used,
}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,
}State gas after adding the intrinsic transaction charge and before the block-level non-negative/u64 checks.
type transaction_state_gas_delta = range(
-(2 * (2 ^ 64 - 1)),
3 * (2 ^ 64 - 1) + 2 * transaction_execution_gas_limit_value,
)A non-negative transaction state-gas total after settlement has rejected a negative signed delta.
type transaction_state_gas_used = range(
0,
3 * (2 ^ 64 - 1) + 2 * transaction_execution_gas_limit_value,
)function transaction_gas_allowance_fields¶
function transaction_gas_allowance_fields(value, _total_limit, regular_limit) = {
let regular =
if value < regular_limit then value else regular_limit;
struct { total = value, regular = regular }
}function transaction_gas_allowance_fields(value, _total_limit, regular_limit) = {
let regular =
if value < regular_limit then value else regular_limit;
struct { total = value, regular = regular }
}function transaction_gas_allowance¶
function transaction_gas_allowance(value, total_limit, regular_limit) =
if total_limit < value then {
fatal_error(ExecutionInvalid)
} else {
transaction_gas_allowance_fields(value, total_limit, regular_limit)
}function fatal_error(_reason) = exit(())function transaction_gas_allowance(value, total_limit, regular_limit) =
if total_limit < value then {
fatal_error(ExecutionInvalid)
} else {
transaction_gas_allowance_fields(value, total_limit, regular_limit)
}function transaction_gas_allowance_fields(value, _total_limit, regular_limit) = {
let regular =
if value < regular_limit then value else regular_limit;
struct { total = value, regular = regular }
}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,
}function transaction_initial_gas¶
function transaction_initial_gas(allowance, intrinsic_execution, intrinsic_state, calldata_floor) = {
if allowance.total < intrinsic_execution then {
fatal_error(ExecutionInvalid)
} else {
let after_execution = allowance.total - intrinsic_execution;
if after_execution < intrinsic_state then {
fatal_error(ExecutionInvalid)
} else if (allowance.regular < intrinsic_execution) | (allowance.regular < calldata_floor) then {
fatal_error(ExecutionInvalid)
} else {
let available = after_execution - intrinsic_state;
let regular_budget = allowance.regular - intrinsic_execution;
if available < regular_budget then {
transaction_initial_gas_fields(
allowance.total,
allowance.regular,
intrinsic_execution,
intrinsic_state,
calldata_floor,
available,
0,
)
} else {
transaction_initial_gas_fields(
allowance.total,
allowance.regular,
intrinsic_execution,
intrinsic_state,
calldata_floor,
regular_budget,
available - regular_budget,
)
}
}
}
}function fatal_error(_reason) = exit(())function transaction_initial_gas(allowance, intrinsic_execution, intrinsic_state, calldata_floor) = {
if allowance.total < intrinsic_execution then {
fatal_error(ExecutionInvalid)
} else {
let after_execution = allowance.total - intrinsic_execution;
if after_execution < intrinsic_state then {
fatal_error(ExecutionInvalid)
} else if (allowance.regular < intrinsic_execution) | (allowance.regular < calldata_floor) then {
fatal_error(ExecutionInvalid)
} else {
let available = after_execution - intrinsic_state;
let regular_budget = allowance.regular - intrinsic_execution;
if available < regular_budget then {
transaction_initial_gas_fields(
allowance.total,
allowance.regular,
intrinsic_execution,
intrinsic_state,
calldata_floor,
available,
0,
)
} else {
transaction_initial_gas_fields(
allowance.total,
allowance.regular,
intrinsic_execution,
intrinsic_state,
calldata_floor,
regular_budget,
available - regular_budget,
)
}
}
}
}function transaction_initial_gas_fields(
total,
regular,
intrinsic_execution,
intrinsic_state,
calldata_floor,
execution,
state,
) =
struct {
admitted_limit = total,
regular_limit = regular,
intrinsic_execution = intrinsic_execution,
intrinsic_state = intrinsic_state,
calldata_floor = calldata_floor,
execution_remaining = execution,
state_remaining = state,
}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,
}function process_auth¶
Applies one EIP-7702 authorization: validates it against current state, sets or clears the delegation, bumps the authority nonce, and refunds if the authority already existed. The signature and chain id are validated from the tuple alone before the authority's account is read — a tuple rejected there touches no state, so its authority need not be witnessed; every authority-state read is gated on those checks. The authority is warmed before the code/nonce checks, so a tuple later skipped still warms it.
function process_auth(au : Authorization) -> authorization_item_refund = {
var refund : authorization_item_refund = 0;
let authority = au.authority;
/* Sig + chain id are validated from the tuple ALONE before reading the
* authority's account; a tuple rejected there (e.g. wrong chain id) touches
* NO state, so its authority need not be witnessed -- reading it eagerly would
* over-access the witness. Gate every authority-state read on signature and
* chain-id acceptance. */
let chain_id_is_zero = word_is_zero(au.chain_id);
let expected_chain_id = word_of_chain_identifier(k_chain_id);
let chain_id_matches = au.chain_id == expected_chain_id;
if au.valid_sig & (chain_id_is_zero | chain_id_matches) then {
/* step 5: warm the authority BEFORE the code/nonce checks -- a tuple later
* skipped for code/nonce still warms its authority. */
k_account_mark_warm(authority);
let (is_deleg, _) = k_deleg_target(authority);
let code_key = k_code_key(authority);
let nonce = k_get_nonce(authority);
if ((code_key == KECCAK_EMPTY) | is_deleg) & (nonce == au.nonce) then {
let existed = k_account_exists(authority);
if au.address == ZERO_ADDRESS then {
k_clear_code(authority)
/* clear delegation */
} else {
k_set_delegation(authority, au.address)
};
k_bump_nonce(authority);
if existed then {
refund = PER_EMPTY_ACCOUNT - PER_AUTH_BASE
}
}
};
refund
}Whether the account exists (post-EIP-161 sense).
function k_account_exists(a : address) -> bool = k_aload(a).presentMarks 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)
}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)
}
}Resets an account's code to empty (EIP-7702 clearing).
function k_clear_code(a : address) -> unit = {
let cur = k_aload(a);
store_account_info(a, cur, { cur.info with code_hash = KECCAK_EMPTY })
}The account's code hash — the code-store key.
function k_code_key(a : address) -> hash = k_aload(a).info.code_hashThe delegation target of an account's code, with a validity flag — false when the code is not a designator.
function k_deleg_target(a : address) -> (bool, address) = {
let h : hash = k_code_key(a);
let r : AddressResult = code_db_read_delegation(h);
(r.success, r.address)
}The account nonce.
function k_get_nonce(a : address) -> account_nonce = {
k_aload(a).info.nonce
}Installs an EIP-7702 delegation designator
(0xef0100 ‖ target) as the account's code.
function k_set_delegation(a : address, target : address) -> unit = {
let cur = k_aload(a);
let execution_profile = k_execution_profile;
let code_region = code_region_from_delegation(target);
let code = validated_code_slice(code_region);
let h : hash = code_db_insert(code, execution_profile.protocol.fork);
store_account_info(a, cur, { cur.info with code_hash = h })
}function word_is_zero(w) = w == WORD_ZEROConverts a chain identifier to the value exposed by CHAINID.
function word_of_chain_identifier(value : chain_identifier) -> chain_identifier = valuekeccak256 of the empty string: the codeHash of every codeless
account.
let KECCAK_EMPTY : hash = hash_from_bits(0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470)let PER_AUTH_BASE : int(12500) = 12500let PER_EMPTY_ACCOUNT : int(25000) = 25000let ZERO_ADDRESS : address = address_from_bits(0x0000000000000000000000000000000000000000000000000000000000000000)The chain id (CHAINID, EIP-155 domains).
register k_chain_id : chain_identifier = 1An EIP-7702 set-code authorization tuple. RLP decoding recovers the
authority and validates the signature (s <= n/2, y_parity, r);
valid_sig records that result, and process_auth applies the
chain-id/nonce/code checks.
struct Authorization = {
valid_sig : bool, authority : address, address : address,
nonce : account_nonce, chain_id : word,
}A 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)The bounded refund contributed by one EIP-7702 authorization.
type authorization_item_refund = range(0, authorization_refund_per_item)function authorization_refund_add¶
Adds one authorization refund to the transaction-wide accumulator. The decoded authorization count proves this guard unreachable in valid input; spelling it at the narrowing boundary keeps proof extraction independent of the Rocq backend's treatment of existential range indices.
function authorization_refund_add(
item : authorization_item_refund,
accumulated : authorization_refund,
) -> (
authorization_refund
) = {
let bound = sizeof(authorization_refund_per_item) * sizeof(transaction_length_bound);
if accumulated <= bound - item then {
item + accumulated
} else {
fatal_error(ExecutionInvalid)
}
}function fatal_error(_reason) = exit(())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 bounded refund contributed by one EIP-7702 authorization.
type authorization_item_refund = range(0, authorization_refund_per_item)The aggregate EIP-7702 refund admitted by one transaction.
type authorization_refund = range(
0,
authorization_refund_per_item * transaction_length_bound,
)The refund available when one EIP-7702 authorization targets an existing account.
type authorization_refund_per_item : Int = 12500Maximum byte length of one encoded transaction envelope. Provenance:
Amsterdam MAX_BYTES_PER_TRANSACTION in
SszExecutionPayload.transactions.
type transaction_length_bound : Int = 2 ^ 30function process_auth_cursor¶
function process_auth_cursor(authorizations, count) =
if count == 0 then {
0
} else {
let authorization = prepared_authorization_head(authorizations);
let remaining = prepared_authorization_tail(authorizations, count);
let item_refund = process_auth(authorization);
let remaining_refund = process_auth_cursor(remaining, count - 1);
authorization_refund_add(item_refund, remaining_refund)
}Adds one authorization refund to the transaction-wide accumulator. The decoded authorization count proves this guard unreachable in valid input; spelling it at the narrowing boundary keeps proof extraction independent of the Rocq backend's treatment of existential range indices.
function authorization_refund_add(
item : authorization_item_refund,
accumulated : authorization_refund,
) -> (
authorization_refund
) = {
let bound = sizeof(authorization_refund_per_item) * sizeof(transaction_length_bound);
if accumulated <= bound - item then {
item + accumulated
} else {
fatal_error(ExecutionInvalid)
}
}Reads the current prepared entry. Callers carry the decreasing count that proves this operation is not applied to the empty collection.
function prepared_authorization_head(authorizations : PreparedAuthorizationList) -> Authorization =
match authorizations.entries {
authorization :: _ => authorization,
[||] => fatal_error(ExecutionInvalid),
}function prepared_authorization_tail(authorizations, count) =
match authorizations.entries {
_ :: entries => struct { entries = entries, count = count - 1 },
[||] => fatal_error(ExecutionInvalid),
}Applies one EIP-7702 authorization: validates it against current state, sets or clears the delegation, bumps the authority nonce, and refunds if the authority already existed. The signature and chain id are validated from the tuple alone before the authority's account is read — a tuple rejected there touches no state, so its authority need not be witnessed; every authority-state read is gated on those checks. The authority is warmed before the code/nonce checks, so a tuple later skipped still warms it.
function process_auth(au : Authorization) -> authorization_item_refund = {
var refund : authorization_item_refund = 0;
let authority = au.authority;
/* Sig + chain id are validated from the tuple ALONE before reading the
* authority's account; a tuple rejected there (e.g. wrong chain id) touches
* NO state, so its authority need not be witnessed -- reading it eagerly would
* over-access the witness. Gate every authority-state read on signature and
* chain-id acceptance. */
let chain_id_is_zero = word_is_zero(au.chain_id);
let expected_chain_id = word_of_chain_identifier(k_chain_id);
let chain_id_matches = au.chain_id == expected_chain_id;
if au.valid_sig & (chain_id_is_zero | chain_id_matches) then {
/* step 5: warm the authority BEFORE the code/nonce checks -- a tuple later
* skipped for code/nonce still warms its authority. */
k_account_mark_warm(authority);
let (is_deleg, _) = k_deleg_target(authority);
let code_key = k_code_key(authority);
let nonce = k_get_nonce(authority);
if ((code_key == KECCAK_EMPTY) | is_deleg) & (nonce == au.nonce) then {
let existed = k_account_exists(authority);
if au.address == ZERO_ADDRESS then {
k_clear_code(authority)
/* clear delegation */
} else {
k_set_delegation(authority, au.address)
};
k_bump_nonce(authority);
if existed then {
refund = PER_EMPTY_ACCOUNT - PER_AUTH_BASE
}
}
};
refund
}function process_auth_cursor(authorizations, count) =
if count == 0 then {
0
} else {
let authorization = prepared_authorization_head(authorizations);
let remaining = prepared_authorization_tail(authorizations, count);
let item_refund = process_auth(authorization);
let remaining_refund = process_auth_cursor(remaining, count - 1);
authorization_refund_add(item_refund, remaining_refund)
}function process_auth_list¶
Applies a prepared authorization collection in order.
function process_auth_list(authorizations : PreparedAuthorizationList) -> authorization_refund =
process_auth_cursor(authorizations, authorizations.count)function process_auth_cursor(authorizations, count) =
if count == 0 then {
0
} else {
let authorization = prepared_authorization_head(authorizations);
let remaining = prepared_authorization_tail(authorizations, count);
let item_refund = process_auth(authorization);
let remaining_refund = process_auth_cursor(remaining, count - 1);
authorization_refund_add(item_refund, remaining_refund)
}Authorizations decoded and signature-recovered after transaction validity but before any world-state mutation. The semantic model retains an immutable list. Optimized C represents the same ordered collection as a cursor into a fixed-capacity transaction workspace.
struct PreparedAuthorizationList = {
entries : list(Authorization),
count : prepared_authorization_count,
}The aggregate EIP-7702 refund admitted by one transaction.
type authorization_refund = range(
0,
authorization_refund_per_item * transaction_length_bound,
)function process_amsterdam_auth¶
Applies one Amsterdam authorization and charges its state-dependent execution-gas and state-gas components. Tuple-local signature and chain checks precede all authority-state reads; a valid tuple warms its authority before checking code and nonce, as required by EIP-7702.
function process_amsterdam_auth(
au : Authorization,
sender : address,
current_target : address,
transfers_value : bool,
carried_gas : gas,
carried_state_gas : state_gas,
carried_state_spill : state_gas_spill,
) -> (
(bool, gas, state_gas, state_gas_spill)
) = {
var gas_after : gas = carried_gas;
var state_gas_after : state_gas = carried_state_gas;
var state_spill_after : state_gas_spill = carried_state_spill;
let authority = au.authority;
let chain_id_is_zero = word_is_zero(au.chain_id);
let expected_chain_id = word_of_chain_identifier(k_chain_id);
let chain_id_matches = au.chain_id == expected_chain_id;
if au.valid_sig & (chain_id_is_zero | chain_id_matches) then {
k_account_mark_warm(authority);
let (currently_delegated, _) = k_deleg_target(authority);
let code_key = k_code_key(authority);
let nonce = k_get_nonce(authority);
if ((code_key == KECCAK_EMPTY) | currently_delegated) & (nonce == au.nonce) then {
let seen = authorization_tracker_seen(authority);
let delegated_before_tx =
if seen then authorization_tracker_originally_delegated(authority) else currently_delegated;
let already_written = seen | (authority == sender) | (transfers_value & (authority == current_target));
let account_exists = k_account_exists(authority);
let account_missing = not_bool(account_exists);
if account_missing then {
let (state_gas_halt, next_gas, next_state_gas, next_state_spill) = charge_state_gas(
gas_after,
state_gas_after,
state_spill_after,
G_amsterdam_state_new_account,
);
gas_after = next_gas;
state_gas_after = next_state_gas;
state_spill_after = next_state_spill;
if state_gas_halt then {
return (false, gas_after, state_gas_after, state_spill_after)
}
};
let requires_account_write = not_bool(already_written);
if requires_account_write then {
if gas_after < G_amsterdam_account_write then {
return (false, GAS_ZERO, state_gas_after, state_spill_after)
};
gas_after = gas_sub(gas_after, G_amsterdam_account_write)
};
let not_delegated_before_tx = not_bool(delegated_before_tx);
let delegation_set = authorization_tracker_delegation_set(authority);
let delegation_not_set = not_bool(delegation_set);
let creates_delegation = au.address != ZERO_ADDRESS;
if creates_delegation & not_delegated_before_tx & delegation_not_set then {
let (auth_state_gas_halt, auth_gas, auth_state_gas, auth_state_spill) = charge_state_gas(
gas_after,
state_gas_after,
state_spill_after,
G_amsterdam_state_auth_base,
);
gas_after = auth_gas;
state_gas_after = auth_state_gas;
state_spill_after = auth_state_spill;
if auth_state_gas_halt then {
return (false, gas_after, state_gas_after, state_spill_after)
}
};
if au.address == ZERO_ADDRESS then {
k_clear_code(authority)
} else {
k_set_delegation(authority, au.address)
};
k_bump_nonce(authority);
let unseen = not_bool(seen);
let originally_delegated = unseen & currently_delegated;
authorization_tracker_commit(authority, originally_delegated, creates_delegation)
}
};
(true, gas_after, state_gas_after, state_spill_after)
}Records one successfully applied authorization.
val authorization_tracker_commit = impure { c: "authorization_tracker_commit" } : (address, bool, bool) -> unitWhether this transaction has successfully set a delegation for the authority.
val authorization_tracker_delegation_set = impure { c: "authorization_tracker_delegation_set" } : address -> boolWhether the authority was delegated before its first successful tuple.
val authorization_tracker_originally_delegated = impure { c: "authorization_tracker_originally_delegated" } : address -> boolWhether a successfully applied authorization for this authority was seen.
val authorization_tracker_seen = impure { c: "authorization_tracker_seen" } : address -> boolfunction charge_state_gas(g, state_gas_remaining, state_gas_spilled, amount) = {
if amount == 0 then {
return (false, g, state_gas_remaining, state_gas_spilled)
};
let state_left = state_gas_remaining;
if amount <= state_left then {
(false, g, state_left - amount, state_gas_spilled)
} else {
let remainder = amount - state_left;
if remainder <= g then {
let spilled = state_gas_spilled;
(false, g - remainder, STATE_GAS_ZERO, state_gas_spill_add(spilled, remainder))
} else {
(true, g, state_gas_remaining, state_gas_spilled)
}
}
}Total gas subtraction. Protocol callers establish affordability first; the saturated arm keeps the primitive representation-safe by construction.
function gas_sub(left : gas, right : gas_cost) -> gas =
if right <= left then {
left - right
} else {
GAS_ZERO
}Whether the account exists (post-EIP-161 sense).
function k_account_exists(a : address) -> bool = k_aload(a).presentMarks 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)
}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)
}
}Resets an account's code to empty (EIP-7702 clearing).
function k_clear_code(a : address) -> unit = {
let cur = k_aload(a);
store_account_info(a, cur, { cur.info with code_hash = KECCAK_EMPTY })
}The account's code hash — the code-store key.
function k_code_key(a : address) -> hash = k_aload(a).info.code_hashThe delegation target of an account's code, with a validity flag — false when the code is not a designator.
function k_deleg_target(a : address) -> (bool, address) = {
let h : hash = k_code_key(a);
let r : AddressResult = code_db_read_delegation(h);
(r.success, r.address)
}The account nonce.
function k_get_nonce(a : address) -> account_nonce = {
k_aload(a).info.nonce
}Installs an EIP-7702 delegation designator
(0xef0100 ‖ target) as the account's code.
function k_set_delegation(a : address, target : address) -> unit = {
let cur = k_aload(a);
let execution_profile = k_execution_profile;
let code_region = code_region_from_delegation(target);
let code = validated_code_slice(code_region);
let h : hash = code_db_insert(code, execution_profile.protocol.fork);
store_account_info(a, cur, { cur.info with code_hash = h })
}val not_bool = pure {coq: "negb", lean: "_lean_not", _: "not"}: forall ('p : Bool). bool('p) -> bool(not('p))function word_is_zero(w) = w == WORD_ZEROConverts a chain identifier to the value exposed by CHAINID.
function word_of_chain_identifier(value : chain_identifier) -> chain_identifier = valuelet GAS_ZERO : int(0) = 0let G_amsterdam_account_write : gas_constant = 8000let G_amsterdam_state_auth_base : state_gas_spill = 35190let G_amsterdam_state_new_account : state_gas_spill = 183600keccak256 of the empty string: the codeHash of every codeless
account.
let KECCAK_EMPTY : hash = hash_from_bits(0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470)let ZERO_ADDRESS : address = address_from_bits(0x0000000000000000000000000000000000000000000000000000000000000000)The chain id (CHAINID, EIP-155 domains).
register k_chain_id : chain_identifier = 1An EIP-7702 set-code authorization tuple. RLP decoding recovers the
authority and validates the signature (s <= n/2, y_parity, r);
valid_sig records that result, and process_auth applies the
chain-id/nonce/code checks.
struct Authorization = {
valid_sig : bool, authority : address, address : address,
nonce : account_nonce, chain_id : word,
}A 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)Available gas in a running EVM frame. Every admitted transaction gas
limit originates in the execution payload's SSZ uint64 gas-limit
domain, and child frames can only receive gas from their parent.
type gas = range(0, 2 ^ 64 - 1)Amsterdam's per-frame state-gas reservoir. The transaction's total gas
allowance remains in the execution payload's uint64 domain; only the
regular-gas portion and state-gas spill into that portion are capped by
EIP-7825.
type state_gas = range(0, 2 ^ 64 - 1)Execution gas temporarily consumed by Amsterdam state charges. EIP-8037 draws spill only from the regular-gas pool, which is capped by EIP-7825.
type state_gas_spill = range(0, transaction_execution_gas_limit_value)function process_amsterdam_auth_cursor¶
function process_amsterdam_auth_cursor(
authorizations,
count,
sender,
current_target,
transfers_value,
gas,
state_gas,
state_spill,
) =
if count == 0 then {
(true, gas, state_gas, state_spill)
} else {
let authorization = prepared_authorization_head(authorizations);
let remaining = prepared_authorization_tail(authorizations, count);
let (processed, gas_after, state_gas_after, state_spill_after) = process_amsterdam_auth(
authorization,
sender,
current_target,
transfers_value,
gas,
state_gas,
state_spill,
);
if processed then {
process_amsterdam_auth_cursor(
remaining,
count - 1,
sender,
current_target,
transfers_value,
gas_after,
state_gas_after,
state_spill_after,
)
} else {
(false, gas_after, state_gas_after, state_spill_after)
}
}Reads the current prepared entry. Callers carry the decreasing count that proves this operation is not applied to the empty collection.
function prepared_authorization_head(authorizations : PreparedAuthorizationList) -> Authorization =
match authorizations.entries {
authorization :: _ => authorization,
[||] => fatal_error(ExecutionInvalid),
}function prepared_authorization_tail(authorizations, count) =
match authorizations.entries {
_ :: entries => struct { entries = entries, count = count - 1 },
[||] => fatal_error(ExecutionInvalid),
}Applies one Amsterdam authorization and charges its state-dependent execution-gas and state-gas components. Tuple-local signature and chain checks precede all authority-state reads; a valid tuple warms its authority before checking code and nonce, as required by EIP-7702.
function process_amsterdam_auth(
au : Authorization,
sender : address,
current_target : address,
transfers_value : bool,
carried_gas : gas,
carried_state_gas : state_gas,
carried_state_spill : state_gas_spill,
) -> (
(bool, gas, state_gas, state_gas_spill)
) = {
var gas_after : gas = carried_gas;
var state_gas_after : state_gas = carried_state_gas;
var state_spill_after : state_gas_spill = carried_state_spill;
let authority = au.authority;
let chain_id_is_zero = word_is_zero(au.chain_id);
let expected_chain_id = word_of_chain_identifier(k_chain_id);
let chain_id_matches = au.chain_id == expected_chain_id;
if au.valid_sig & (chain_id_is_zero | chain_id_matches) then {
k_account_mark_warm(authority);
let (currently_delegated, _) = k_deleg_target(authority);
let code_key = k_code_key(authority);
let nonce = k_get_nonce(authority);
if ((code_key == KECCAK_EMPTY) | currently_delegated) & (nonce == au.nonce) then {
let seen = authorization_tracker_seen(authority);
let delegated_before_tx =
if seen then authorization_tracker_originally_delegated(authority) else currently_delegated;
let already_written = seen | (authority == sender) | (transfers_value & (authority == current_target));
let account_exists = k_account_exists(authority);
let account_missing = not_bool(account_exists);
if account_missing then {
let (state_gas_halt, next_gas, next_state_gas, next_state_spill) = charge_state_gas(
gas_after,
state_gas_after,
state_spill_after,
G_amsterdam_state_new_account,
);
gas_after = next_gas;
state_gas_after = next_state_gas;
state_spill_after = next_state_spill;
if state_gas_halt then {
return (false, gas_after, state_gas_after, state_spill_after)
}
};
let requires_account_write = not_bool(already_written);
if requires_account_write then {
if gas_after < G_amsterdam_account_write then {
return (false, GAS_ZERO, state_gas_after, state_spill_after)
};
gas_after = gas_sub(gas_after, G_amsterdam_account_write)
};
let not_delegated_before_tx = not_bool(delegated_before_tx);
let delegation_set = authorization_tracker_delegation_set(authority);
let delegation_not_set = not_bool(delegation_set);
let creates_delegation = au.address != ZERO_ADDRESS;
if creates_delegation & not_delegated_before_tx & delegation_not_set then {
let (auth_state_gas_halt, auth_gas, auth_state_gas, auth_state_spill) = charge_state_gas(
gas_after,
state_gas_after,
state_spill_after,
G_amsterdam_state_auth_base,
);
gas_after = auth_gas;
state_gas_after = auth_state_gas;
state_spill_after = auth_state_spill;
if auth_state_gas_halt then {
return (false, gas_after, state_gas_after, state_spill_after)
}
};
if au.address == ZERO_ADDRESS then {
k_clear_code(authority)
} else {
k_set_delegation(authority, au.address)
};
k_bump_nonce(authority);
let unseen = not_bool(seen);
let originally_delegated = unseen & currently_delegated;
authorization_tracker_commit(authority, originally_delegated, creates_delegation)
}
};
(true, gas_after, state_gas_after, state_spill_after)
}function process_amsterdam_auth_cursor(
authorizations,
count,
sender,
current_target,
transfers_value,
gas,
state_gas,
state_spill,
) =
if count == 0 then {
(true, gas, state_gas, state_spill)
} else {
let authorization = prepared_authorization_head(authorizations);
let remaining = prepared_authorization_tail(authorizations, count);
let (processed, gas_after, state_gas_after, state_spill_after) = process_amsterdam_auth(
authorization,
sender,
current_target,
transfers_value,
gas,
state_gas,
state_spill,
);
if processed then {
process_amsterdam_auth_cursor(
remaining,
count - 1,
sender,
current_target,
transfers_value,
gas_after,
state_gas_after,
state_spill_after,
)
} else {
(false, gas_after, state_gas_after, state_spill_after)
}
}Available gas in a running EVM frame. Every admitted transaction gas
limit originates in the execution payload's SSZ uint64 gas-limit
domain, and child frames can only receive gas from their parent.
type gas = range(0, 2 ^ 64 - 1)Amsterdam's per-frame state-gas reservoir. The transaction's total gas
allowance remains in the execution payload's uint64 domain; only the
regular-gas portion and state-gas spill into that portion are capped by
EIP-7825.
type state_gas = range(0, 2 ^ 64 - 1)function warm_access_list_keys¶
function warm_access_list_keys(cursor, addr) = {
if cursor.len == 0 then {
return ()
};
let key = rlp_decode_item(cursor);
let next = rlp_cursor_advance(cursor, key.source.len);
let slot = rlp_decode_word(key);
k_prewarm_slot(addr, slot);
warm_access_list_keys(next, addr)
}Warms an explicitly addressed storage slot while preparing a transaction's access list, before any EVM frame owns the execution context.
function k_prewarm_slot(a : address, s : word) -> unit = storage_mark_warm(a, s)function rlp_cursor_advance(cursor, consumed) =
slice_suffix(cursor, consumed)function rlp_decode_item(cursor) = {
if cursor.len == 0 then {
fatal_error(RlpDecode)
};
let (is_list, content_off, content_len_value) = rlp_ref_hdr(cursor);
let (content_len as 'content_len) = content_len_value;
if cursor.len < content_off then {
fatal_error(RlpDecode)
};
if cursor.len - content_off < content_len then {
fatal_error(RlpDecode)
};
let (full_len as 'full_len) = content_off + content_len;
if (0 < full_len) & (full_len <= cursor.len) then {
let field_source = sub_slice(cursor, 0, full_len);
let field : RlpFieldRef('source_off, 'full_len, 'content_len) = struct {
source = field_source,
is_list = is_list,
content_len = content_len,
};
field
} else {
fatal_error(RlpDecode)
}
}Decodes a string field of at most 32 bytes into a word.
function rlp_decode_word forall 'source_off 'source_len 'content_len,
rlp_field_ref_valid('source_off, 'source_len, 'content_len). (f :
RlpFieldRef('source_off, 'source_len, 'content_len)) -> (
word
) = {
let n = f.content_len;
if f.is_list | (RLP_WORD_LENGTH_LIMIT < n) then {
fatal_error(RlpDecode)
} else {
slice_load_n(f.source, f.source.len - n, n)
}
}function warm_access_list_keys(cursor, addr) = {
if cursor.len == 0 then {
return ()
};
let key = rlp_decode_item(cursor);
let next = rlp_cursor_advance(cursor, key.source.len);
let slot = rlp_decode_word(key);
k_prewarm_slot(addr, slot);
warm_access_list_keys(next, addr)
}function warm_access_list¶
function warm_access_list(cursor) = {
if cursor.len == 0 then {
return ()
};
let entry = rlp_decode_item(cursor);
let next = rlp_cursor_advance(cursor, entry.source.len);
let fields = rlp_decode_list(entry);
let addr_f = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, addr_f.source.len);
let keys_f = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, keys_f.source.len);
rlp_cursor_expect_end(fields);
let addr_word = rlp_decode_word(addr_f);
let addr = word_to_address(addr_word);
k_account_mark_warm(addr);
let keys = rlp_decode_list(keys_f);
warm_access_list_keys(keys, addr);
warm_access_list(next)
}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)
}function rlp_cursor_advance(cursor, consumed) =
slice_suffix(cursor, consumed)function rlp_cursor_expect_end(cursor) = {
if cursor.len == 0 then {
return ()
};
fatal_error(RlpDecode)
}function rlp_decode_item(cursor) = {
if cursor.len == 0 then {
fatal_error(RlpDecode)
};
let (is_list, content_off, content_len_value) = rlp_ref_hdr(cursor);
let (content_len as 'content_len) = content_len_value;
if cursor.len < content_off then {
fatal_error(RlpDecode)
};
if cursor.len - content_off < content_len then {
fatal_error(RlpDecode)
};
let (full_len as 'full_len) = content_off + content_len;
if (0 < full_len) & (full_len <= cursor.len) then {
let field_source = sub_slice(cursor, 0, full_len);
let field : RlpFieldRef('source_off, 'full_len, 'content_len) = struct {
source = field_source,
is_list = is_list,
content_len = content_len,
};
field
} else {
fatal_error(RlpDecode)
}
}function rlp_decode_list(f) =
if f.is_list then {
sub_slice(f.source, f.source.len - f.content_len, f.content_len)
} else {
fatal_error(RlpDecode)
}Decodes a string field of at most 32 bytes into a word.
function rlp_decode_word forall 'source_off 'source_len 'content_len,
rlp_field_ref_valid('source_off, 'source_len, 'content_len). (f :
RlpFieldRef('source_off, 'source_len, 'content_len)) -> (
word
) = {
let n = f.content_len;
if f.is_list | (RLP_WORD_LENGTH_LIMIT < n) then {
fatal_error(RlpDecode)
} else {
slice_load_n(f.source, f.source.len - n, n)
}
}function warm_access_list(cursor) = {
if cursor.len == 0 then {
return ()
};
let entry = rlp_decode_item(cursor);
let next = rlp_cursor_advance(cursor, entry.source.len);
let fields = rlp_decode_list(entry);
let addr_f = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, addr_f.source.len);
let keys_f = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, keys_f.source.len);
rlp_cursor_expect_end(fields);
let addr_word = rlp_decode_word(addr_f);
let addr = word_to_address(addr_word);
k_account_mark_warm(addr);
let keys = rlp_decode_list(keys_f);
warm_access_list_keys(keys, addr);
warm_access_list(next)
}function warm_access_list_keys(cursor, addr) = {
if cursor.len == 0 then {
return ()
};
let key = rlp_decode_item(cursor);
let next = rlp_cursor_advance(cursor, key.source.len);
let slot = rlp_decode_word(key);
k_prewarm_slot(addr, slot);
warm_access_list_keys(next, addr)
}Converts a word to its low 160-bit address in canonical byte order.
function word_to_address(value : word) -> address = {
let zero_bytes = vector_init(20, 0x00);
var result : address = Address(zero_bytes);
result[0] = get_slice_int(8, value, 152);
result[1] = get_slice_int(8, value, 144);
result[2] = get_slice_int(8, value, 136);
result[3] = get_slice_int(8, value, 128);
result[4] = get_slice_int(8, value, 120);
result[5] = get_slice_int(8, value, 112);
result[6] = get_slice_int(8, value, 104);
result[7] = get_slice_int(8, value, 96);
result[8] = get_slice_int(8, value, 88);
result[9] = get_slice_int(8, value, 80);
result[10] = get_slice_int(8, value, 72);
result[11] = get_slice_int(8, value, 64);
result[12] = get_slice_int(8, value, 56);
result[13] = get_slice_int(8, value, 48);
result[14] = get_slice_int(8, value, 40);
result[15] = get_slice_int(8, value, 32);
result[16] = get_slice_int(8, value, 24);
result[17] = get_slice_int(8, value, 16);
result[18] = get_slice_int(8, value, 8);
result[19] = get_slice_int(8, value, 0);
result
}function prewarm¶
Pre-warms the accessed-address set (EIP-2929): the sender, the call target, and the access list (EIP-2930); EIP-3651 additionally warms the coinbase from Shanghai onward. Active precompiles are a fork-derived warm class in k_account_is_warm and therefore need no table entries.
function prewarm(tx : Transaction) -> unit = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
k_account_mark_warm(tx.sender);
if tx.is_create then {
()
} else {
k_account_mark_warm(tx.recipient)
};
if profile.fork >= Shanghai then {
let coinbase = k_coinbase();
k_account_mark_warm(coinbase)
};
let access_list : StatelessInputSlice = tx.access_list.encoded;
warm_access_list(access_list)
}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)
}The block's fee recipient (COINBASE).
function k_coinbase() -> address = k_header.fee_recipientfunction warm_access_list(cursor) = {
if cursor.len == 0 then {
return ()
};
let entry = rlp_decode_item(cursor);
let next = rlp_cursor_advance(cursor, entry.source.len);
let fields = rlp_decode_list(entry);
let addr_f = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, addr_f.source.len);
let keys_f = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, keys_f.source.len);
rlp_cursor_expect_end(fields);
let addr_word = rlp_decode_word(addr_f);
let addr = word_to_address(addr_word);
k_account_mark_warm(addr);
let keys = rlp_decode_list(keys_f);
warm_access_list_keys(keys, addr);
warm_access_list(next)
}EIP-3651 warm coinbase, EIP-3855 PUSH0, EIP-3860 initcode.
let Shanghai : int(shanghai_fork_value) = sizeof(shanghai_fork_value)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_PROFILEA stateless-input range with its coordinate and length packed existentially.
type StatelessInputSlice = {
'off 'len,
stateless_input_valid_range('off, 'len).
StatelessInputSliceFields('off, 'len)
}A decoded transaction retains the fork-selected blob-count limit that was applied while decoding its envelope. Non-blob transactions carry the inactive zero limit.
type Transaction = {
'blob_limit,
transaction_blob_limit_value('blob_limit).
TransactionFields('blob_limit)
}function eff_gas_price_for¶
The EIP-1559 effective fee: the gas price actually paid is
min(max_fee, base_fee + max_priority_fee), and the priority tip
paid to the coinbase is that price minus the base fee. Legacy and
EIP-2930 transactions carry a single gas_price, passed as
max_fee = max_priority = gas_price, so this recovers
(gas_price, gas_price − base_fee). The priority is clamped at 0 so
an invalid sub-base-fee price (rejected later by validity) never
underflows.
function eff_gas_price_for(base_fee : word, max_fee : word, max_priority_fee : word) -> (word, word) = {
let max_fee_below_base = word_ule(max_fee, base_fee);
let price : word =
if max_fee_below_base then {
max_fee
} else {
let available_priority = word_sub(max_fee, base_fee);
let priority_within_cap = word_ule(max_priority_fee, available_priority);
if priority_within_cap then {
word_add(base_fee, max_priority_fee)
} else {
max_fee
}
};
let base_fee_covered = word_ule(base_fee, price);
let priority =
if base_fee_covered then word_sub(price, base_fee) else ZERO_WORD;
(price, priority)
}function word_ule(a, b) = {
let greater = word_ult(b, a);
not_bool(greater)
}let ZERO_WORD : word = word_from_bits(0x0000000000000000000000000000000000000000000000000000000000000000)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 check_transaction_validity¶
function check_transaction_validity(tx, allowance) = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
let tx_semantics = tx_type_semantics(tx.tx_type);
/* Sender authentication: `v` selects the recovered signer, which must be
the address derived from the witnessed public key. A forged key or bad
v/r/s makes the whole BLOCK invalid, regardless of the validity verdict
below. */
let parity = tx_signature_parity(k_chain_id, tx_semantics.signature, tx.sig_v);
let authenticated = tx_auth_valid(tx.sender, tx.signing_hash, parity, tx.sig_r, tx.sig_s);
let invalid_signature = not_bool(authenticated);
if invalid_signature then {
fatal_error(InvalidSignature)
};
let gas_limit = allowance.total;
/* effective gas price (EIP-1559) */
let (eff_gas_price, eff_priority_fee) = eff_gas_price_for(k_header.base_fee, tx.max_fee, tx.max_priority_fee);
let sender = tx.sender;
let input = tx.input_src;
let input_len = input.len;
let nonce_before = k_get_nonce(sender);
let costs = transaction_costs(profile, tx, gas_limit, k_header.excess_blob_gas);
let expected_nonce = word_of_account_nonce(nonce_before);
if tx.nonce != expected_nonce then {
fatal_error(ExecutionInvalid)
};
/* Transaction validity: an invalid tx is REJECTED with no state change at all
(no nonce bump, no gas charge). The upfront-balance test uses the fee CAPS
(max_fee_per_gas, max_fee_per_blob_gas), not the effective prices. */
let (sender_deleg, _) = k_deleg_target(sender);
/* EIP-4844 / EIP-7691 / EIP-7594: the profile-indexed RLP decoder has
already bounded the count and validated each `0x01` version byte while
consuming the hash-list cursor. A type-3 transaction must still carry
at least one blob and must not create a contract. */
if tx_semantics.blob then {
if (profile.fork < Cancun) | (tx.blob_hashes.count == 0) | tx.is_create then {
fatal_error(ExecutionInvalid)
}
};
/* EIP-7623 (Prague+): the calldata floor is ALSO a validity bound -- a tx whose
gas limit cannot cover 21000 + 10*tokens is rejected outright. */
if (profile.fork >= Prague) & (gas_limit < costs.calldata_floor) then {
fatal_error(ExecutionInvalid)
};
let sender_balance = k_get_balance(sender);
let upfront_affordable = word_ule(costs.upfront, sender_balance);
let insufficient_balance = not_bool(upfront_affordable);
if insufficient_balance then {
fatal_error(ExecutionInvalid)
};
let sender_code_key = k_code_key(sender);
let valid_sender_code = (sender_code_key == KECCAK_EMPTY) | sender_deleg;
let invalid_sender_code = not_bool(valid_sender_code);
if invalid_sender_code then {
fatal_error(ExecutionInvalid)
};
if allowance.regular < costs.calldata_floor then {
fatal_error(ExecutionInvalid)
};
let base_fee_affordable = word_ule(k_header.base_fee, tx.max_fee);
let base_fee_exceeds_cap = not_bool(base_fee_affordable);
if base_fee_exceeds_cap then {
fatal_error(ExecutionInvalid)
};
let valid_initcode_size = initcode_size_allowed(input_len);
let invalid_initcode_size = not_bool(valid_initcode_size);
if tx.is_create & invalid_initcode_size then {
fatal_error(ExecutionInvalid)
};
let valid_priority_fee = word_ule(tx.max_priority_fee, tx.max_fee);
let invalid_priority_fee = not_bool(valid_priority_fee);
if invalid_priority_fee then {
fatal_error(ExecutionInvalid)
};
if profile.fork < tx_semantics.minimum_fork then {
fatal_error(ExecutionInvalid)
};
if tx_semantics.set_code & tx.is_create then {
fatal_error(ExecutionInvalid)
};
let authorizations = tx.authorizations;
if tx_semantics.set_code & (authorizations.count == 0) then {
fatal_error(ExecutionInvalid)
};
let typed_signature = tx_semantics.signature == TypedSignature;
if typed_signature & (tx.chain_id != k_chain_id) then {
fatal_error(ExecutionInvalid)
};
if nonce_before == sizeof(account_nonce_bound) then {
fatal_error(ExecutionInvalid)
};
let initial_gas = transaction_initial_gas(
allowance,
costs.intrinsic_execution,
costs.intrinsic_state,
costs.calldata_floor,
);
tx_validity_fields(sender, nonce_before, initial_gas, costs.blob_fee, eff_gas_price, eff_priority_fee)
}function check_transaction_validity(tx, allowance) = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
let tx_semantics = tx_type_semantics(tx.tx_type);
/* Sender authentication: `v` selects the recovered signer, which must be
the address derived from the witnessed public key. A forged key or bad
v/r/s makes the whole BLOCK invalid, regardless of the validity verdict
below. */
let parity = tx_signature_parity(k_chain_id, tx_semantics.signature, tx.sig_v);
let authenticated = tx_auth_valid(tx.sender, tx.signing_hash, parity, tx.sig_r, tx.sig_s);
let invalid_signature = not_bool(authenticated);
if invalid_signature then {
fatal_error(InvalidSignature)
};
let gas_limit = allowance.total;
/* effective gas price (EIP-1559) */
let (eff_gas_price, eff_priority_fee) = eff_gas_price_for(k_header.base_fee, tx.max_fee, tx.max_priority_fee);
let sender = tx.sender;
let input = tx.input_src;
let input_len = input.len;
let nonce_before = k_get_nonce(sender);
let costs = transaction_costs(profile, tx, gas_limit, k_header.excess_blob_gas);
let expected_nonce = word_of_account_nonce(nonce_before);
if tx.nonce != expected_nonce then {
fatal_error(ExecutionInvalid)
};
/* Transaction validity: an invalid tx is REJECTED with no state change at all
(no nonce bump, no gas charge). The upfront-balance test uses the fee CAPS
(max_fee_per_gas, max_fee_per_blob_gas), not the effective prices. */
let (sender_deleg, _) = k_deleg_target(sender);
/* EIP-4844 / EIP-7691 / EIP-7594: the profile-indexed RLP decoder has
already bounded the count and validated each `0x01` version byte while
consuming the hash-list cursor. A type-3 transaction must still carry
at least one blob and must not create a contract. */
if tx_semantics.blob then {
if (profile.fork < Cancun) | (tx.blob_hashes.count == 0) | tx.is_create then {
fatal_error(ExecutionInvalid)
}
};
/* EIP-7623 (Prague+): the calldata floor is ALSO a validity bound -- a tx whose
gas limit cannot cover 21000 + 10*tokens is rejected outright. */
if (profile.fork >= Prague) & (gas_limit < costs.calldata_floor) then {
fatal_error(ExecutionInvalid)
};
let sender_balance = k_get_balance(sender);
let upfront_affordable = word_ule(costs.upfront, sender_balance);
let insufficient_balance = not_bool(upfront_affordable);
if insufficient_balance then {
fatal_error(ExecutionInvalid)
};
let sender_code_key = k_code_key(sender);
let valid_sender_code = (sender_code_key == KECCAK_EMPTY) | sender_deleg;
let invalid_sender_code = not_bool(valid_sender_code);
if invalid_sender_code then {
fatal_error(ExecutionInvalid)
};
if allowance.regular < costs.calldata_floor then {
fatal_error(ExecutionInvalid)
};
let base_fee_affordable = word_ule(k_header.base_fee, tx.max_fee);
let base_fee_exceeds_cap = not_bool(base_fee_affordable);
if base_fee_exceeds_cap then {
fatal_error(ExecutionInvalid)
};
let valid_initcode_size = initcode_size_allowed(input_len);
let invalid_initcode_size = not_bool(valid_initcode_size);
if tx.is_create & invalid_initcode_size then {
fatal_error(ExecutionInvalid)
};
let valid_priority_fee = word_ule(tx.max_priority_fee, tx.max_fee);
let invalid_priority_fee = not_bool(valid_priority_fee);
if invalid_priority_fee then {
fatal_error(ExecutionInvalid)
};
if profile.fork < tx_semantics.minimum_fork then {
fatal_error(ExecutionInvalid)
};
if tx_semantics.set_code & tx.is_create then {
fatal_error(ExecutionInvalid)
};
let authorizations = tx.authorizations;
if tx_semantics.set_code & (authorizations.count == 0) then {
fatal_error(ExecutionInvalid)
};
let typed_signature = tx_semantics.signature == TypedSignature;
if typed_signature & (tx.chain_id != k_chain_id) then {
fatal_error(ExecutionInvalid)
};
if nonce_before == sizeof(account_nonce_bound) then {
fatal_error(ExecutionInvalid)
};
let initial_gas = transaction_initial_gas(
allowance,
costs.intrinsic_execution,
costs.intrinsic_state,
costs.calldata_floor,
);
tx_validity_fields(sender, nonce_before, initial_gas, costs.blob_fee, eff_gas_price, eff_priority_fee)
}The EIP-1559 effective fee: the gas price actually paid is
min(max_fee, base_fee + max_priority_fee), and the priority tip
paid to the coinbase is that price minus the base fee. Legacy and
EIP-2930 transactions carry a single gas_price, passed as
max_fee = max_priority = gas_price, so this recovers
(gas_price, gas_price − base_fee). The priority is clamped at 0 so
an invalid sub-base-fee price (rejected later by validity) never
underflows.
function eff_gas_price_for(base_fee : word, max_fee : word, max_priority_fee : word) -> (word, word) = {
let max_fee_below_base = word_ule(max_fee, base_fee);
let price : word =
if max_fee_below_base then {
max_fee
} else {
let available_priority = word_sub(max_fee, base_fee);
let priority_within_cap = word_ule(max_priority_fee, available_priority);
if priority_within_cap then {
word_add(base_fee, max_priority_fee)
} else {
max_fee
}
};
let base_fee_covered = word_ule(base_fee, price);
let priority =
if base_fee_covered then word_sub(price, base_fee) else ZERO_WORD;
(price, priority)
}function fatal_error(_reason) = exit(())function initcode_size_allowed(size) = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
let limit = profile.initcode_size_limit;
(limit == 0) | (size <= limit)
}The account's code hash — the code-store key.
function k_code_key(a : address) -> hash = k_aload(a).info.code_hashThe delegation target of an account's code, with a validity flag — false when the code is not a designator.
function k_deleg_target(a : address) -> (bool, address) = {
let h : hash = k_code_key(a);
let r : AddressResult = code_db_read_delegation(h);
(r.success, r.address)
}The account balance (BALANCE, SELFBALANCE).
function k_get_balance(a : address) -> word = {
k_aload(a).info.balance
}The account nonce.
function k_get_nonce(a : address) -> account_nonce = {
k_aload(a).info.nonce
}val not_bool = pure {coq: "negb", lean: "_lean_not", _: "not"}: forall ('p : Bool). bool('p) -> bool(not('p))Computes transaction costs as mathematical naturals, narrowing only the externally observable word-valued results.
function transaction_costs(
profile : ProtocolProfile,
tx : Transaction,
gas_limit : block_gas_limit,
excess_blob_gas : excess_blob_gas,
) -> (
TransactionCosts
) = {
let intrinsic = intrinsic_gas(tx);
let blob_gas : transaction_blob_gas = sizeof(gas_per_blob_value) * tx.blob_hashes.count;
let blob_fee_value : nat =
if blob_gas == 0 then {
0
} else {
let blob_price = blob_base_fee(
profile.fork,
profile.blob_schedule,
profile.excess_blob_gas_limit,
excess_blob_gas,
);
if blob_price <= tx.max_blob_fee then {
transaction_blob_fee(blob_price, blob_gas)
} else {
fatal_error(ExecutionInvalid)
}
};
let upfront_value = transaction_upfront_cost(tx.max_fee, gas_limit, tx.value, tx.max_blob_fee, blob_gas);
if (blob_fee_value < sizeof(word_modulus)) & (upfront_value < sizeof(word_modulus)) then {
struct {
intrinsic_execution = intrinsic.execution,
intrinsic_state = intrinsic.state,
calldata_floor = intrinsic.calldata_floor,
blob_gas = blob_gas,
blob_fee = protocol_word(blob_fee_value),
upfront = protocol_word(upfront_value),
}
} else {
fatal_error(ExecutionInvalid)
}
}function transaction_initial_gas(allowance, intrinsic_execution, intrinsic_state, calldata_floor) = {
if allowance.total < intrinsic_execution then {
fatal_error(ExecutionInvalid)
} else {
let after_execution = allowance.total - intrinsic_execution;
if after_execution < intrinsic_state then {
fatal_error(ExecutionInvalid)
} else if (allowance.regular < intrinsic_execution) | (allowance.regular < calldata_floor) then {
fatal_error(ExecutionInvalid)
} else {
let available = after_execution - intrinsic_state;
let regular_budget = allowance.regular - intrinsic_execution;
if available < regular_budget then {
transaction_initial_gas_fields(
allowance.total,
allowance.regular,
intrinsic_execution,
intrinsic_state,
calldata_floor,
available,
0,
)
} else {
transaction_initial_gas_fields(
allowance.total,
allowance.regular,
intrinsic_execution,
intrinsic_state,
calldata_floor,
regular_budget,
available - regular_budget,
)
}
}
}
}Authenticates a transaction: enforce the EIP-2 low-s bound, recover the
signer selected by y_parity, and bind it to the address derived from the
witnessed 65-byte public key.
function tx_auth_valid(sender : address, h : hash, parity : y_parity, r : word, s : word) -> bool =
let high_s = word_ult(SECP_N_HALF, s) in
if high_s then {
false
} else {
let recovered = ecrecover_addr(h, parity, r, s);
recovered.success & (recovered.address == sender)
}Validates the selected signature scheme's v domain and returns its
recovery parity. Legacy accepts 27/28 or an EIP-155 value binding the
chain id; typed envelopes accept only an explicit zero-or-one parity.
An invalid v rejects the block here, so an unvalidated parity is
impossible to pass to sender recovery.
function tx_signature_parity(chain_id : chain_identifier, scheme : TxSignatureScheme, v : word) -> y_parity =
match scheme {
LegacySignature => {
let eip155_v = word_ule(35, v);
let signature_chain_id = legacy_sig_chain_id(v);
let expected_chain_id = word_of_chain_identifier(chain_id);
if (v == 27) | (v == 28) | (eip155_v & (signature_chain_id == expected_chain_id)) then {
let parity_bit = word_and(v, WORD_ONE);
if parity_bit == WORD_ONE then {
0
} else {
1
}
} else {
fatal_error(InvalidSignature)
}
},
TypedSignature => {
if v == WORD_ZERO then {
0
} else if v == WORD_ONE then {
1
} else {
fatal_error(InvalidSignature)
}
},
}Derives the protocol requirements of one transaction envelope.
function tx_type_semantics(t : TxType) -> TxTypeSemantics = match t {
LegacyTx => struct { minimum_fork = Frontier, signature = LegacySignature, blob = false, set_code = false },
AccessListTx => struct { minimum_fork = Berlin, signature = TypedSignature, blob = false, set_code = false },
FeeMarketTx => struct { minimum_fork = London, signature = TypedSignature, blob = false, set_code = false },
BlobTx => struct { minimum_fork = Cancun, signature = TypedSignature, blob = true, set_code = false },
SetCodeTx => struct { minimum_fork = Prague, signature = TypedSignature, blob = false, set_code = true },
}function tx_validity_fields(sender, nonce_before, gas, blob_fee, gas_price, priority_fee) =
struct {
sender = sender,
nonce_before = nonce_before,
gas = gas,
blob_fee = blob_fee,
gas_price = gas_price,
priority_fee = priority_fee,
}Embeds an EIP-2681 account nonce in the EVM word domain.
function word_of_account_nonce(value : account_nonce) -> account_nonce = valuefunction word_ule(a, b) = {
let greater = word_ult(b, a);
not_bool(greater)
}EIP-1153/4844; precompiles 0x01-0x0a.
let Cancun : int(first_blob_fork_value) = sizeof(first_blob_fork_value)keccak256 of the empty string: the codeHash of every codeless
account.
let KECCAK_EMPTY : hash = hash_from_bits(0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470)EIP-7623 calldata floor; BLS precompiles 0x0b-0x11.
let Prague : int(prague_fork_value) = sizeof(prague_fork_value)The chain id (CHAINID, EIP-155 domains).
register k_chain_id : chain_identifier = 1The 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_PROFILEThe 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 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 two transaction-signature encodings. Legacy transactions use the
original/EIP-155 v domain; every EIP-2718 typed envelope carries an
explicit zero-or-one parity.
enum TxSignatureScheme = { LegacySignature, TypedSignature }The largest account nonce admitted by EIP-2681.
type account_nonce_bound : Int = 2 ^ 64 - 1The accumulated excess blob gas carried between headers (EIP-4844).
type excess_blob_gas = range(0, excess_blob_gas_bound)function apply_transaction_upfront_effects¶
The upfront effects, taken before the execution snapshot so they persist across a dispatched-frame revert: charge the full execution/state gas limit and the EIP-4844 blob-gas fee, bump the sender nonce, and prewarm the transaction access set. Before Amsterdam, EIP-7702 authorizations are also applied here; Amsterdam applies them inside the separately reversible top-frame preparation phase.
function apply_transaction_upfront_effects(
tx : Transaction,
v : TxValidity,
authorizations : PreparedAuthorizationList,
) -> (
TxUpfrontResult
) = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
let initial_gas = v.gas;
let create_target_prestate_empty : bool =
if (profile.fork >= Amsterdam) & tx.is_create then {
let create_target = k_create_addr(v.sender, v.nonce_before);
let target_exists = k_account_exists(create_target);
not_bool(target_exists)
} else {
false
};
/* upfront gas + nonce bump (persist across revert; taken before the snapshot) */
let gas_debit = validated_word_product(v.gas_price, initial_gas.admitted_limit);
k_sub_balance(v.sender, gas_debit);
/* EIP-4844: burn the blob fee upfront (blob_gas_used * blob_gas_price), no refund */
let has_blob_fee = word_nonzero(v.blob_fee);
if has_blob_fee then {
k_sub_balance(v.sender, v.blob_fee)
};
k_bump_nonce(v.sender);
prewarm(tx);
let authorization_refund =
if profile.fork < Amsterdam then process_auth_list(authorizations) else 0;
struct { authorization_refund = authorization_refund, create_target_prestate_empty = create_target_prestate_empty }
}Whether the account exists (post-EIP-161 sense).
function k_account_exists(a : address) -> bool = k_aload(a).presentIncrements 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)
}
}The CREATE address rule, in kernel form.
function k_create_addr(a : address, nonce : account_nonce) -> address = create_address(a, nonce)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 })
}
}val not_bool = pure {coq: "negb", lean: "_lean_not", _: "not"}: forall ('p : Bool). bool('p) -> bool(not('p))Pre-warms the accessed-address set (EIP-2929): the sender, the call target, and the access list (EIP-2930); EIP-3651 additionally warms the coinbase from Shanghai onward. Active precompiles are a fork-derived warm class in k_account_is_warm and therefore need no table entries.
function prewarm(tx : Transaction) -> unit = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
k_account_mark_warm(tx.sender);
if tx.is_create then {
()
} else {
k_account_mark_warm(tx.recipient)
};
if profile.fork >= Shanghai then {
let coinbase = k_coinbase();
k_account_mark_warm(coinbase)
};
let access_list : StatelessInputSlice = tx.access_list.encoded;
warm_access_list(access_list)
}Applies a prepared authorization collection in order.
function process_auth_list(authorizations : PreparedAuthorizationList) -> authorization_refund =
process_auth_cursor(authorizations, authorizations.count)function validated_word_product(value, factor) = {
let product = value * factor;
if product < sizeof(word_modulus) then {
protocol_word(product)
} else {
fatal_error(ExecutionInvalid)
}
}function word_nonzero(w) = {
let is_zero = word_is_zero(w);
not_bool(is_zero)
}EIP-7954 code/initcode size bump (65536/131072).
let Amsterdam : int(amsterdam_fork_value) = sizeof(amsterdam_fork_value)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_PROFILEAuthorizations decoded and signature-recovered after transaction validity but before any world-state mutation. The semantic model retains an immutable list. Optimized C represents the same ordered collection as a cursor into a fixed-capacity transaction workspace.
struct PreparedAuthorizationList = {
entries : list(Authorization),
count : prepared_authorization_count,
}A decoded transaction retains the fork-selected blob-count limit that was applied while decoding its envelope. Non-blob transactions carry the inactive zero limit.
type Transaction = {
'blob_limit,
transaction_blob_limit_value('blob_limit).
TransactionFields('blob_limit)
}Values established before entering the top-level transaction frame.
struct TxUpfrontResult = {
authorization_refund : authorization_refund,
create_target_prestate_empty : bool,
}A validity result whose limit is not needed by the consumer.
type TxValidity = {
'limit
'regular
'intrinsic_execution
'intrinsic_state
'calldata_floor
'execution
'state,
transaction_initial_gas_relation(
'limit,
'regular,
'intrinsic_execution,
'intrinsic_state,
'calldata_floor,
'execution,
'state,
).
TxValidityFields(
'limit,
'regular,
'intrinsic_execution,
'intrinsic_state,
'calldata_floor,
'execution,
'state,
)
}The aggregate EIP-7702 refund admitted by one transaction.
type authorization_refund = range(
0,
authorization_refund_per_item * transaction_length_bound,
)Available gas in a running EVM frame. Every admitted transaction gas
limit originates in the execution payload's SSZ uint64 gas-limit
domain, and child frames can only receive gas from their parent.
type gas = range(0, 2 ^ 64 - 1)function enter_transaction_frame¶
Resets the user-space machine for the transaction's top-level frame,
funding it with gas_limit − intrinsic.
function enter_transaction_frame(v : TxValidity) -> (
(gas, state_gas, state_gas_spill, gas_refund, StackPointer, memory_base, memory_height)
) = {
let initial_gas = v.gas;
let stack = stack_reset();
(
initial_gas.execution_remaining,
initial_gas.state_remaining,
STATE_GAS_SPILL_ZERO,
GAS_REFUND_ZERO,
stack,
MEMORY_BASE_ZERO,
MEMORY_HEIGHT_ZERO,
)
}function stack_reset() -> StackPointer =
struct { storage = stack_reset_host(), height = 0 }let GAS_REFUND_ZERO : gas_refund = 0The top-level frame begins at the shared arena's semantic offset zero.
let MEMORY_BASE_ZERO : memory_base = 0The empty EVM-memory high-water mark.
let MEMORY_HEIGHT_ZERO : memory_height = 0let STATE_GAS_SPILL_ZERO : int(0) = 0The operand-stack cursor for the active frame, threaded by value through
the interpreter in the state-passing convention and held in the
stack_top frame register at frame boundaries. storage is an opaque
host coordinate while height is the semantic stack height. Keeping the
height in the cursor makes stack validation independent of the host stack
representation. Optimized C refines storage to a native u256 *.
struct StackPointer = {
storage : bits(64),
height : operand_stack_height,
}A validity result whose limit is not needed by the consumer.
type TxValidity = {
'limit
'regular
'intrinsic_execution
'intrinsic_state
'calldata_floor
'execution
'state,
transaction_initial_gas_relation(
'limit,
'regular,
'intrinsic_execution,
'intrinsic_state,
'calldata_floor,
'execution,
'state,
).
TxValidityFields(
'limit,
'regular,
'intrinsic_execution,
'intrinsic_state,
'calldata_floor,
'execution,
'state,
)
}Available gas in a running EVM frame. Every admitted transaction gas
limit originates in the execution payload's SSZ uint64 gas-limit
domain, and child frames can only receive gas from their parent.
type gas = range(0, 2 ^ 64 - 1)The signed transaction refund accumulator before capping.
type gas_refund = range(
-gas_refund_bound,
gas_refund_bound,
)An absolute byte position in the shared EVM-memory arena.
type memory_base = range(0, memory_region_bound)The active EVM frame's exact relative byte high-water mark. It is carried
beside the frame's absolute memory_base; the host retains no hidden
frame coordinate or lifecycle state.
type memory_height = memory_lengthAmsterdam's per-frame state-gas reservoir. The transaction's total gas
allowance remains in the execution payload's uint64 domain; only the
regular-gas portion and state-gas spill into that portion are capped by
EIP-7825.
type state_gas = range(0, 2 ^ 64 - 1)Execution gas temporarily consumed by Amsterdam state charges. EIP-8037 draws spill only from the regular-gas pool, which is capped by EIP-7825.
type state_gas_spill = range(0, transaction_execution_gas_limit_value)type TransactionPreparation¶
The outcome of top-level dispatch preparation: whether the frame is ready to run, and whether a call recipient delegated, which disables direct precompile dispatch.
struct TransactionPreparation = {
ready : bool,
delegated : bool,
}function prepare_amsterdam_transaction_dispatch¶
Charges Amsterdam's state-dependent top-level dispatch costs and installs the code selected for execution. This phase deliberately performs no revertible account mutation: its state-gas charges are therefore refilled if the subsequently dispatched frame fails.
function prepare_amsterdam_transaction_dispatch(
tx : Transaction,
v : TxValidity,
upfront : TxUpfrontResult,
carried_gas : gas,
carried_state_gas : state_gas,
carried_state_spill : state_gas_spill,
) -> (
(TransactionPreparation, gas, state_gas, state_gas_spill, address, address, Code, CalldataSlice)
) = {
var gas_after : gas = carried_gas;
var state_gas_after : state_gas = carried_state_gas;
var state_spill_after : state_gas_spill = carried_state_spill;
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
let current_target =
if tx.is_create then k_create_addr(v.sender, v.nonce_before) else tx.recipient;
if tx.is_create then {
if upfront.create_target_prestate_empty then {
let (state_gas_halt, next_gas, next_state_gas, next_state_spill) = charge_state_gas(
gas_after,
state_gas_after,
state_spill_after,
G_amsterdam_state_new_account,
);
gas_after = next_gas;
state_gas_after = next_state_gas;
state_spill_after = next_state_spill;
if state_gas_halt then {
return (
struct { ready = false, delegated = false },
gas_after,
state_gas_after,
state_spill_after,
current_target,
current_target,
EMPTY_CODE,
EMPTY_CALLDATA,
)
}
};
let initcode = transaction_initcode_slice(tx.input_src);
let code_id = code_db_insert(initcode, profile.fork);
let code = code_db_resolve(code_id);
(
struct { ready = true, delegated = false },
gas_after,
state_gas_after,
state_spill_after,
current_target,
current_target,
code,
EMPTY_CALLDATA,
)
} else {
let calldata = InputCalldata(tx.input_src);
let transfers_value = word_nonzero(tx.value);
let recipient_empty = k_account_is_empty(tx.recipient);
if transfers_value & recipient_empty then {
let (state_gas_halt, next_gas, next_state_gas, next_state_spill) = charge_state_gas(
gas_after,
state_gas_after,
state_spill_after,
G_amsterdam_state_new_account,
);
gas_after = next_gas;
state_gas_after = next_state_gas;
state_spill_after = next_state_spill;
if state_gas_halt then {
return (
struct { ready = false, delegated = false },
gas_after,
state_gas_after,
state_spill_after,
current_target,
current_target,
EMPTY_CODE,
calldata,
)
}
};
let (delegated, delegate) = k_deleg_target(tx.recipient);
if delegated then {
let warm = k_account_is_warm(delegate);
let access_cost = account_cost(warm);
if gas_after < access_cost then {
return (
struct { ready = false, delegated = false },
GAS_ZERO,
state_gas_after,
state_spill_after,
current_target,
current_target,
EMPTY_CODE,
calldata,
)
};
gas_after = gas_sub(gas_after, access_cost);
k_account_mark_warm(delegate)
};
let code_address =
if delegated then delegate else current_target;
let code = executable_code(tx.recipient, delegated, delegate);
(
struct { ready = true, delegated = delegated },
gas_after,
state_gas_after,
state_spill_after,
current_target,
code_address,
code,
calldata,
)
}
}The account-access cost for a prior warm bit.
function account_cost(warm : bool) -> gas_constant = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
if warm then {
G_warm_access
} else if profile.fork >= Amsterdam then {
G_amsterdam_cold_account_access
} else {
G_cold_account
}
}function charge_state_gas(g, state_gas_remaining, state_gas_spilled, amount) = {
if amount == 0 then {
return (false, g, state_gas_remaining, state_gas_spilled)
};
let state_left = state_gas_remaining;
if amount <= state_left then {
(false, g, state_left - amount, state_gas_spilled)
} else {
let remainder = amount - state_left;
if remainder <= g then {
let spilled = state_gas_spilled;
(false, g - remainder, STATE_GAS_ZERO, state_gas_spill_add(spilled, remainder))
} else {
(true, g, state_gas_remaining, state_gas_spilled)
}
}
}Analyzes and stores code, returning its content hash.
function code_db_insert(code : CodeSlice, fork : Fork) -> hash = {
let jumpdest_table = analyze_code(code, fork);
let analyzed = analyzed_code(code, jumpdest_table);
code_db_store(analyzed)
}The code for a code hash; KECCAK_EMPTY resolves to empty code, and
an unwitnessed hash is a deficient witness.
function code_db_resolve(code_hash : hash) -> Code =
if code_hash == KECCAK_EMPTY then {
EMPTY_CODE
} else {
let code = code_db_lookup(code_hash);
if code.len == 0 then {
fatal_error(WitnessDeficient)
} else {
code
}
}Selects the code a frame actually executes (EIP-7702). A delegated account runs the code at its delegation target, following exactly one hop; a delegation whose target is a precompile (or has no code) executes as empty code. An undelegated account runs its own code.
function executable_code(target : address, dele : bool, dtgt : address) -> Code =
if dele then {
let delegate_key = k_code_key(dtgt);
let delegate_code = code_db_resolve(delegate_key);
let delegate_precompile = precompile_id_for_address(dtgt);
if delegate_precompile != NotPrecompile then {
EMPTY_CODE
} else {
delegate_code
}
} else {
let target_key = k_code_key(target);
code_db_resolve(target_key)
}Total gas subtraction. Protocol callers establish affordability first; the saturated arm keeps the primitive representation-safe by construction.
function gas_sub(left : gas, right : gas_cost) -> gas =
if right <= left then {
left - right
} else {
GAS_ZERO
}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)
}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)
}
}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)
}The CREATE address rule, in kernel form.
function k_create_addr(a : address, nonce : account_nonce) -> address = create_address(a, nonce)The delegation target of an account's code, with a validity flag — false when the code is not a designator.
function k_deleg_target(a : address) -> (bool, address) = {
let h : hash = k_code_key(a);
let r : AddressResult = code_db_read_delegation(h);
(r.success, r.address)
}Reclassifies transaction initcode as executable after re-establishing the enclosing SSZ transaction-envelope bound. This bound is structural and is deliberately independent of the active protocol deployment limit.
function transaction_initcode_slice(input : TransactionInputSlice) -> CodeSlice = {
let input_slice = stateless_input_slice(input.bytes, input.len);
code_db_intern_input(input_slice)
}function word_nonzero(w) = {
let is_zero = word_is_zero(w);
not_bool(is_zero)
}let EMPTY_CALLDATA : CalldataSlice = InputCalldata(EMPTY_STATELESS_INPUT_SLICE)let EMPTY_CODE : Code = analyzed_code(EMPTY_CODE_SLICE, EMPTY_JUMP_TABLE)let GAS_ZERO : int(0) = 0let G_amsterdam_state_new_account : state_gas_spill = 183600The 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_PROFILECalldata is either the immutable top-level transaction input or a frozen range of the suspended caller's memory. The variants state the only two protocol-valid provenances instead of exposing the host's region enum.
union CalldataSlice = {
/* the immutable top-level transaction input */
InputCalldata : StatelessInputSlice,
/* a frozen range of the suspended caller's memory */
MemoryCalldata : EvmMemorySlice,
}Existential executable-code value whose concrete byte address and length
remain correlated inside CodeFields.
type Code = {
'off 'len,
code_region_valid_range('off, 'len) & code_valid_length('len).
CodeFields('off, 'len)
}A decoded transaction retains the fork-selected blob-count limit that was applied while decoding its envelope. Non-blob transactions carry the inactive zero limit.
type Transaction = {
'blob_limit,
transaction_blob_limit_value('blob_limit).
TransactionFields('blob_limit)
}The outcome of top-level dispatch preparation: whether the frame is ready to run, and whether a call recipient delegated, which disables direct precompile dispatch.
struct TransactionPreparation = {
ready : bool,
delegated : bool,
}Values established before entering the top-level transaction frame.
struct TxUpfrontResult = {
authorization_refund : authorization_refund,
create_target_prestate_empty : bool,
}A validity result whose limit is not needed by the consumer.
type TxValidity = {
'limit
'regular
'intrinsic_execution
'intrinsic_state
'calldata_floor
'execution
'state,
transaction_initial_gas_relation(
'limit,
'regular,
'intrinsic_execution,
'intrinsic_state,
'calldata_floor,
'execution,
'state,
).
TxValidityFields(
'limit,
'regular,
'intrinsic_execution,
'intrinsic_state,
'calldata_floor,
'execution,
'state,
)
}A 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)Available gas in a running EVM frame. Every admitted transaction gas
limit originates in the execution payload's SSZ uint64 gas-limit
domain, and child frames can only receive gas from their parent.
type gas = range(0, 2 ^ 64 - 1)Amsterdam's per-frame state-gas reservoir. The transaction's total gas
allowance remains in the execution payload's uint64 domain; only the
regular-gas portion and state-gas spill into that portion are capped by
EIP-7825.
type state_gas = range(0, 2 ^ 64 - 1)Execution gas temporarily consumed by Amsterdam state charges. EIP-8037 draws spill only from the regular-gas pool, which is capped by EIP-7825.
type state_gas_spill = range(0, transaction_execution_gas_limit_value)function run_create_transaction_frame¶
Runs a create transaction's top-level frame: derives the new address
from (sender, nonce_before), fails outright on an address collision
(all gas consumed, no initcode runs — EIP-684/EIP-7610), and
otherwise deploys via the initcode path.
function run_create_transaction_frame(
tx : Transaction,
sender : address,
nonce_before : account_nonce,
carried_gas : gas,
carried_state_gas : state_gas,
carried_state_spill : state_gas_spill,
carried_refund : gas_refund,
carried_stack : StackPointer,
carried_memory_base : memory_base,
carried_memory_height : memory_height,
carried_code : Code,
carried_calldata : CalldataSlice,
state_gas_reservoir : state_gas,
) -> (
(gas, state_gas, state_gas_spill, gas_refund, FrameStatus, OutputSlice)
) = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
let new_addr = k_create_addr(sender, nonce_before);
var gas_after : gas = carried_gas;
var state_gas_after : state_gas = carried_state_gas;
var state_spill_after : state_gas_spill = carried_state_spill;
var refund_after : gas_refund = carried_refund;
var status_after : FrameStatus = Running();
var output_after : OutputSlice = EMPTY_OUTPUT_SLICE;
k_account_mark_warm(new_addr);
/* create-tx address collision (code, nonzero nonce, or storage at the
* target): the tx fails outright consuming ALL gas, no initcode runs
* (EIP-684/EIP-7610; gas_left = 0). */
let occupied = k_account_occupied(new_addr);
if occupied then {
gas_after = GAS_ZERO;
let exceptional = exceptional_state(state_gas_after, state_spill_after, state_gas_reservoir, AddressCollision);
state_gas_after = exceptional.state_gas_remaining;
state_spill_after = exceptional.state_gas_spilled;
status_after = exceptional.status
} else {
k_mark_created(new_addr); /* EIP-6780: created this tx */
k_clear_storage(new_addr);
k_bump_nonce(new_addr);
let transfers_value = word_nonzero(tx.value);
if transfers_value then {
k_transfer(sender, new_addr, tx.value)
};
var frame_code : Code = carried_code;
var frame_calldata : CalldataSlice = carried_calldata;
if profile.fork < Amsterdam then {
let initcode = transaction_initcode_slice(tx.input_src);
let code_id = code_db_insert(initcode, profile.fork);
frame_code = code_db_resolve(code_id);
frame_calldata = EMPTY_CALLDATA
};
(gas_after, state_gas_after, state_spill_after, refund_after, status_after, output_after) = interpret(
gas_after,
state_gas_after,
state_spill_after,
refund_after,
carried_stack,
carried_memory_base,
carried_memory_height,
sender,
new_addr,
new_addr,
tx.value,
state_gas_reservoir,
false,
0,
frame_code,
frame_calldata,
);
let initcode_succeeded = frame_succeeded(status_after);
if initcode_succeeded then {
let deployed_output = output_after;
let dep_len = deployed_output.len;
let deployed_length = dep_len;
let valid_deployed_size = deployed_code_size_allowed(deployed_length);
let valid_prefix =
if (profile.fork < London) | (deployed_length == 0) then {
true
} else {
let first_byte = slice_byte(deployed_output, 0);
first_byte != 0xef
};
if valid_deployed_size & valid_prefix then {
let deployment_charge = code_deployment_execution_cost(dep_len, gas_after);
if deployment_charge.affordable then {
let execution_deposit = deployment_charge.cost;
gas_after = gas_sub(gas_after, execution_deposit);
let state_deposit = code_deployment_state_cost(dep_len);
var deployment_halt : bool = false;
(deployment_halt, gas_after, state_gas_after, state_spill_after) = charge_state_gas(
gas_after,
state_gas_after,
state_spill_after,
state_deposit,
);
if deployment_halt then {
gas_after = GAS_ZERO;
let exceptional = exceptional_state(
state_gas_after,
state_spill_after,
state_gas_reservoir,
OutOfGas,
);
state_gas_after = exceptional.state_gas_remaining;
state_spill_after = exceptional.state_gas_spilled;
status_after = exceptional.status
};
let deployment_succeeded = frame_succeeded(status_after);
if deployment_succeeded then {
let stored_code = code_db_intern_output(deployed_output);
k_deploy_code(new_addr, stored_code)
}
} else if profile.fork < Homestead then {
/* Frontier consumes the remaining gas and keeps the
* created account with empty code. */
gas_after = GAS_ZERO;
k_deploy_code(new_addr, EMPTY_CODE_SLICE)
} else {
gas_after = GAS_ZERO;
let exceptional = exceptional_state(
state_gas_after,
state_spill_after,
state_gas_reservoir,
OutOfGas,
);
state_gas_after = exceptional.state_gas_remaining;
state_spill_after = exceptional.state_gas_spilled;
status_after = exceptional.status
}
} else {
gas_after = GAS_ZERO;
let exceptional = exceptional_state(state_gas_after, state_spill_after, state_gas_reservoir, OutOfGas);
state_gas_after = exceptional.state_gas_remaining;
state_spill_after = exceptional.state_gas_spilled;
status_after = exceptional.status
} /* failed deploy */
}
};
(gas_after, state_gas_after, state_spill_after, refund_after, status_after, output_after)
}function charge_state_gas(g, state_gas_remaining, state_gas_spilled, amount) = {
if amount == 0 then {
return (false, g, state_gas_remaining, state_gas_spilled)
};
let state_left = state_gas_remaining;
if amount <= state_left then {
(false, g, state_left - amount, state_gas_spilled)
} else {
let remainder = amount - state_left;
if remainder <= g then {
let spilled = state_gas_spilled;
(false, g - remainder, STATE_GAS_ZERO, state_gas_spill_add(spilled, remainder))
} else {
(true, g, state_gas_remaining, state_gas_spilled)
}
}
}Analyzes and stores code, returning its content hash.
function code_db_insert(code : CodeSlice, fork : Fork) -> hash = {
let jumpdest_table = analyze_code(code, fork);
let analyzed = analyzed_code(code, jumpdest_table);
code_db_store(analyzed)
}Normalizes frozen creation output into the code arena before deployment.
function code_db_intern_output(bytes : OutputSlice) -> CodeSlice = {
let region = code_region_from_output(bytes);
validated_code_slice(region)
}The code for a code hash; KECCAK_EMPTY resolves to empty code, and
an unwitnessed hash is a deficient witness.
function code_db_resolve(code_hash : hash) -> Code =
if code_hash == KECCAK_EMPTY then {
EMPTY_CODE
} else {
let code = code_db_lookup(code_hash);
if code.len == 0 then {
fatal_error(WitnessDeficient)
} else {
code
}
}Returns the affordable execution-gas charge after successful initcode. Legacy forks charge per byte; Amsterdam charges the keccak word cost. The affordability guard bounds the native product without imposing a protocol code-size limit on Frontier or Homestead.
function code_deployment_execution_cost(byte_len : code_length, available : gas) -> GasCharge = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
if profile.fork >= Amsterdam then {
if byte_len <= profile.deployed_code_size_limit then {
let words = memory_word_count(byte_len);
if words <= available / G_keccak_word then {
let cost : gas_cost = G_keccak_word * words;
gas_charge(cost)
} else {
GAS_CHARGE_UNAFFORDABLE
}
} else {
GAS_CHARGE_UNAFFORDABLE
}
} else if byte_len <= available / G_codedeposit then {
let cost : gas_cost = G_codedeposit * byte_len;
gas_charge(cost)
} else {
GAS_CHARGE_UNAFFORDABLE
}
}Amsterdam state gas charged for each byte of newly deployed code.
function code_deployment_state_cost(byte_len : code_length) -> gas_cost = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
if profile.fork >= Amsterdam then {
if byte_len <= profile.deployed_code_size_limit then {
G_amsterdam_state_byte * byte_len
} else {
fatal_error(ExecutionInvalid)
}
} else {
GAS_COST_ZERO
}
}function deployed_code_size_allowed(size) = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
size <= profile.deployed_code_size_limit
}function exceptional_state(state_gas_remaining, state_gas_spilled, state_gas_reservoir, k) = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
if profile.fork >= Amsterdam then {
struct {
state_gas_remaining = state_gas_reservoir,
state_gas_spilled = STATE_GAS_SPILL_ZERO,
status = Exceptional(k),
}
} else {
struct {
state_gas_remaining = state_gas_remaining,
state_gas_spilled = state_gas_spilled,
status = Exceptional(k),
}
}
}Whether the just-finished frame ended successfully: a normal halt
succeeds; a REVERT and any exceptional halt do not (their world
effects are rolled back and CALL/CREATE reports failure).
function frame_succeeded(frame_status : FrameStatus) -> bool =
match frame_status {
Halted(HaltRevert(_)) => false,
Halted(_) => true,
Running() => true,
Exceptional(_) => false,
}Total gas subtraction. Protocol callers establish affordability first; the saturated arm keeps the primitive representation-safe by construction.
function gas_sub(left : gas, right : gas_cost) -> gas =
if right <= left then {
left - right
} else {
GAS_ZERO
}The non-recursive step loop for one complete call tree. It executes
the active frame, resumes suspended parents through
frame_stack_pop as children halt, and returns the top-level
frame's output. Each step's carried state is supplied from the frame
registers and its returned state is assigned back; the handlers
themselves never touch the registers. STOP, SELFDESTRUCT, and
exceptional halts return the empty slice; RETURN and REVERT carry
their frozen memory slice in the halt value.
function interpret(
initial_gas : gas,
initial_state_gas : state_gas,
initial_state_spill : state_gas_spill,
initial_refund : gas_refund,
initial_sp : StackPointer,
initial_memory_base : memory_base,
initial_memory_height : memory_height,
initial_caller : address,
initial_address : address,
initial_code_address : address,
initial_value : word,
initial_state_gas_reservoir : state_gas,
initial_is_static : bool,
initial_depth : frame_depth,
initial_code : Code,
initial_calldata : CalldataSlice,
) -> (
(gas, state_gas, state_gas_spill, gas_refund, FrameStatus, OutputSlice)
) = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
let fork = profile.fork;
let blob_fee = blob_base_fee(fork, profile.blob_schedule, profile.excess_blob_gas_limit, k_header.excess_blob_gas);
frame_stack_reset();
var interpreting : bool = true;
var result : OutputSlice = EMPTY_OUTPUT_SLICE;
var carried_pc : code_pointer = 0;
var carried_sp : StackPointer = initial_sp;
var carried_memory_base : memory_base = initial_memory_base;
var carried_memory_height : memory_height = initial_memory_height;
var carried_gas : gas = initial_gas;
var carried_state_gas : state_gas = initial_state_gas;
var carried_state_spill : state_gas_spill = initial_state_spill;
var carried_refund : gas_refund = initial_refund;
var carried_status : FrameStatus = Running();
var carried_caller : address = initial_caller;
var carried_address : address = initial_address;
var carried_account_context : AccountExecutionContext = account_execution_context(initial_address);
var carried_code_address : address = initial_code_address;
var carried_value : word = initial_value;
var carried_state_gas_reservoir : state_gas = initial_state_gas_reservoir;
var carried_is_static : bool = initial_is_static;
var carried_depth : frame_depth = initial_depth;
var carried_code : Code = initial_code;
var carried_calldata : CalldataSlice = initial_calldata;
var carried_returndata : OutputSlice = EMPTY_OUTPUT_SLICE;
let initial_call_tree_gas = initial_gas + initial_state_gas;
var call_tree_steps_remaining : call_tree_steps = 3 * initial_call_tree_gas + 2;
while interpreting termination_measure(call_tree_steps_remaining) do {
let running = is_running(carried_status);
if running then {
let (fetched_pc, instruction) = fetch(carried_code, carried_pc, fork);
carried_pc = fetched_pc;
match instruction {
opcode_CREATE() => {
let previous_address = carried_address;
let transition = run_create(
carried_pc,
carried_gas,
carried_state_gas,
carried_state_spill,
carried_refund,
carried_sp,
carried_memory_base,
carried_memory_height,
carried_caller,
carried_address,
carried_code_address,
carried_value,
carried_state_gas_reservoir,
carried_is_static,
carried_depth,
carried_code,
carried_calldata,
carried_returndata,
CreateByNonce,
);
carried_pc = transition.pc;
carried_gas = transition.gas_remaining;
carried_state_gas = transition.state_gas_remaining;
carried_state_spill = transition.state_gas_spilled;
carried_refund = transition.refund;
carried_status = transition.status;
carried_sp = transition.stack_top;
carried_memory_base = transition.memory_base;
carried_memory_height = transition.memory_height;
carried_caller = transition.message.caller;
carried_address = transition.message.address;
carried_code_address = transition.message.code_address;
carried_value = transition.message.value;
carried_state_gas_reservoir = transition.message.state_gas_reservoir;
carried_is_static = transition.message.is_static;
carried_depth = transition.message.depth;
carried_code = transition.code;
carried_calldata = transition.calldata;
carried_returndata = transition.returndata;
carried_account_context = refresh_account_execution_context(
carried_account_context,
previous_address,
carried_address,
)
},
CREATE2() => {
let previous_address = carried_address;
let transition = run_create(
carried_pc,
carried_gas,
carried_state_gas,
carried_state_spill,
carried_refund,
carried_sp,
carried_memory_base,
carried_memory_height,
carried_caller,
carried_address,
carried_code_address,
carried_value,
carried_state_gas_reservoir,
…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)
}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)
}
}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)
}
}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)
}The CREATE address rule, in kernel form.
function k_create_addr(a : address, nonce : account_nonce) -> address = create_address(a, nonce)Deploys code to an account: analyzes, stores, and binds its hash.
function k_deploy_code(a : address, code : CodeSlice) -> unit = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
let cur = k_aload(a);
let h : hash = code_db_insert(code, profile.fork);
store_account_info(a, cur, { cur.info with code_hash = h })
}Marks an account as created in this transaction (EIP-6780's same-transaction test).
function k_mark_created(a : address) -> unit = {
let cur = k_aload(a);
store_account(a, { cur with created = true })
}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)
}Reclassifies transaction initcode as executable after re-establishing the enclosing SSZ transaction-envelope bound. This bound is structural and is deliberately independent of the active protocol deployment limit.
function transaction_initcode_slice(input : TransactionInputSlice) -> CodeSlice = {
let input_slice = stateless_input_slice(input.bytes, input.len);
code_db_intern_input(input_slice)
}function word_nonzero(w) = {
let is_zero = word_is_zero(w);
not_bool(is_zero)
}EIP-7954 code/initcode size bump (65536/131072).
let Amsterdam : int(amsterdam_fork_value) = sizeof(amsterdam_fork_value)let EMPTY_CALLDATA : CalldataSlice = InputCalldata(EMPTY_STATELESS_INPUT_SLICE)Canonical empty executable code.
let EMPTY_CODE_SLICE : CodeSlice = code_slice(EMPTY_CODE_REGION_SLICE)let EMPTY_OUTPUT_SLICE : OutputSliceFields(0, 0) = output_slice(0, 0)let GAS_ZERO : int(0) = 0EIP-2 create rules, EIP-7 DELEGATECALL.
let Homestead : int(homestead_fork_value) = sizeof(homestead_fork_value)EIP-1559 fee market and EIP-3529 refund reduction.
let London : int(london_fork_value) = sizeof(london_fork_value)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_PROFILECalldata is either the immutable top-level transaction input or a frozen range of the suspended caller's memory. The variants state the only two protocol-valid provenances instead of exposing the host's region enum.
union CalldataSlice = {
/* the immutable top-level transaction input */
InputCalldata : StatelessInputSlice,
/* a frozen range of the suspended caller's memory */
MemoryCalldata : EvmMemorySlice,
}Existential executable-code value whose concrete byte address and length
remain correlated inside CodeFields.
type Code = {
'off 'len,
code_region_valid_range('off, 'len) & code_valid_length('len).
CodeFields('off, 'len)
}Exceptional halts (YP §9.4.2): each consumes all remaining gas and reverts the frame's state changes.
enum ExceptionKind = {
/* an opcode pops more items than the stack holds */
StackUnderflow,
/* a push would exceed the 1024-item stack limit */
StackOverflow,
/* the operation's cost exceeds the remaining gas */
OutOfGas,
/* an unassigned or fork-inactive opcode, or INVALID (0xfe) */
InvalidOpcode,
/* a jump target that is not a valid JUMPDEST */
InvalidJump,
/* EIP-214: state-changing op inside a STATICCALL */
StaticViolation,
/* a call or create beyond depth 1024 */
CallDepthExceeded,
/* a value transfer exceeding the sender's balance */
InsufficientBalance,
/* EIP-214 write protection */
WriteProtection,
/* EIP-3860 */
InitCodeTooLarge,
/* a nonce at its maximum cannot be bumped (EIP-2681) */
NonceOverflow,
/* EIP-684: CREATE into an occupied account */
AddressCollision,
}Per-frame execution status: running, halted normally, or exceptionally halted.
union FrameStatus = {
/* mid-execution */
Running : unit,
/* halted normally (YP §9.4.4) */
Halted : HaltKind,
/* halted exceptionally: all frame gas consumed, effects void */
Exceptional : ExceptionKind
}A frame-output range with its coordinate and length packed existentially.
type OutputSlice = {
'off 'len,
output_region_valid_range('off, 'len).
OutputSliceFields('off, 'len)
}The operand-stack cursor for the active frame, threaded by value through
the interpreter in the state-passing convention and held in the
stack_top frame register at frame boundaries. storage is an opaque
host coordinate while height is the semantic stack height. Keeping the
height in the cursor makes stack validation independent of the host stack
representation. Optimized C refines storage to a native u256 *.
struct StackPointer = {
storage : bits(64),
height : operand_stack_height,
}A decoded transaction retains the fork-selected blob-count limit that was applied while decoding its envelope. Non-blob transactions carry the inactive zero limit.
type Transaction = {
'blob_limit,
transaction_blob_limit_value('blob_limit).
TransactionFields('blob_limit)
}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)Available gas in a running EVM frame. Every admitted transaction gas
limit originates in the execution payload's SSZ uint64 gas-limit
domain, and child frames can only receive gas from their parent.
type gas = range(0, 2 ^ 64 - 1)The signed transaction refund accumulator before capping.
type gas_refund = range(
-gas_refund_bound,
gas_refund_bound,
)An absolute byte position in the shared EVM-memory arena.
type memory_base = range(0, memory_region_bound)The active EVM frame's exact relative byte high-water mark. It is carried
beside the frame's absolute memory_base; the host retains no hidden
frame coordinate or lifecycle state.
type memory_height = memory_lengthAmsterdam's per-frame state-gas reservoir. The transaction's total gas
allowance remains in the execution payload's uint64 domain; only the
regular-gas portion and state-gas spill into that portion are capped by
EIP-7825.
type state_gas = range(0, 2 ^ 64 - 1)Execution gas temporarily consumed by Amsterdam state charges. EIP-8037 draws spill only from the regular-gas pool, which is capped by EIP-7825.
type state_gas_spill = range(0, transaction_execution_gas_limit_value)function run_call_transaction_frame¶
Runs a call transaction's top-level frame: transfers value, then either runs a direct recipient precompile or interprets the selected code. At Amsterdam the preparation phase has already resolved and charged a recipient delegation; a delegated recipient never dispatches a precompile directly.
function run_call_transaction_frame(
tx : Transaction,
sender : address,
delegated : bool,
carried_gas : gas,
carried_state_gas : state_gas,
carried_state_spill : state_gas_spill,
carried_refund : gas_refund,
carried_stack : StackPointer,
carried_memory_base : memory_base,
carried_memory_height : memory_height,
carried_code_address : address,
carried_code : Code,
carried_calldata : CalldataSlice,
state_gas_reservoir : state_gas,
) -> (
(gas, state_gas, state_gas_spill, gas_refund, FrameStatus, OutputSlice)
) = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
var gas_after : gas = carried_gas;
var state_gas_after : state_gas = carried_state_gas;
var state_spill_after : state_gas_spill = carried_state_spill;
var refund_after : gas_refund = carried_refund;
var status_after : FrameStatus = Running();
var output_after : OutputSlice = EMPTY_OUTPUT_SLICE;
var code_address : address = carried_code_address;
var frame_code : Code = carried_code;
var frame_calldata : CalldataSlice = carried_calldata;
/* The recipient account is read during message setup (for its code) on
* every call-transaction, so it is always a state access -- keep it in the
* account set (BAL), including a tx sent directly to a precompile. */
let _ = k_aload(tx.recipient);
let transfers_value = word_nonzero(tx.value);
if transfers_value then {
k_transfer(sender, tx.recipient, tx.value)
};
let selected_precompile = precompile_id_for_address(tx.recipient);
var direct_precompile : bool = false;
if not_bool(delegated) then {
direct_precompile = selected_precompile != NotPrecompile
};
if direct_precompile then {
/* tx directly to a precompile: run it as the top-level frame, gas-checked
FIRST (an OOG precompile must not execute). Failure or OOG is an
exceptional halt (all gas consumed, value transfer reverted). */
let input_src : StatelessInputSlice = tx.input_src;
let precompile_input = InputCalldata(input_src);
let precompile_charge = precompile_gas(selected_precompile, precompile_input, gas_after);
if precompile_charge.affordable then {
let used = precompile_charge.cost;
let result = run_precompile_slice(selected_precompile, precompile_input);
if result.success then {
gas_after = gas_sub(gas_after, used);
output_after = result.output;
let halt_reason = HaltReturn(result.output);
status_after = Halted(halt_reason)
} else {
gas_after = GAS_ZERO;
let exceptional = exceptional_state(state_gas_after, state_spill_after, state_gas_reservoir, OutOfGas);
state_gas_after = exceptional.state_gas_remaining;
state_spill_after = exceptional.state_gas_spilled;
status_after = exceptional.status
}
} else {
gas_after = GAS_ZERO;
let exceptional = exceptional_state(state_gas_after, state_spill_after, state_gas_reservoir, OutOfGas);
state_gas_after = exceptional.state_gas_remaining;
state_spill_after = exceptional.state_gas_spilled;
status_after = exceptional.status
}
} else {
if profile.fork < Amsterdam then {
frame_calldata = InputCalldata(tx.input_src);
code_address = tx.recipient;
/* EIP-7702: before Amsterdam the transaction-level delegate is
warmed but has no separate access charge. */
let (tx_deleg, tx_dtgt) = k_deleg_target(tx.recipient);
if tx_deleg then {
k_account_mark_warm(tx_dtgt);
let _ = k_aload(tx_dtgt);
()
};
if tx_deleg then {
code_address = tx_dtgt
};
frame_code = executable_code(tx.recipient, tx_deleg, tx_dtgt)
};
(gas_after, state_gas_after, state_spill_after, refund_after, status_after, output_after) = interpret(
gas_after,
state_gas_after,
state_spill_after,
refund_after,
carried_stack,
carried_memory_base,
carried_memory_height,
sender,
tx.recipient,
code_address,
tx.value,
state_gas_reservoir,
false,
0,
frame_code,
frame_calldata,
)
};
(gas_after, state_gas_after, state_spill_after, refund_after, status_after, output_after)
}function exceptional_state(state_gas_remaining, state_gas_spilled, state_gas_reservoir, k) = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
if profile.fork >= Amsterdam then {
struct {
state_gas_remaining = state_gas_reservoir,
state_gas_spilled = STATE_GAS_SPILL_ZERO,
status = Exceptional(k),
}
} else {
struct {
state_gas_remaining = state_gas_remaining,
state_gas_spilled = state_gas_spilled,
status = Exceptional(k),
}
}
}Selects the code a frame actually executes (EIP-7702). A delegated account runs the code at its delegation target, following exactly one hop; a delegation whose target is a precompile (or has no code) executes as empty code. An undelegated account runs its own code.
function executable_code(target : address, dele : bool, dtgt : address) -> Code =
if dele then {
let delegate_key = k_code_key(dtgt);
let delegate_code = code_db_resolve(delegate_key);
let delegate_precompile = precompile_id_for_address(dtgt);
if delegate_precompile != NotPrecompile then {
EMPTY_CODE
} else {
delegate_code
}
} else {
let target_key = k_code_key(target);
code_db_resolve(target_key)
}Total gas subtraction. Protocol callers establish affordability first; the saturated arm keeps the primitive representation-safe by construction.
function gas_sub(left : gas, right : gas_cost) -> gas =
if right <= left then {
left - right
} else {
GAS_ZERO
}The non-recursive step loop for one complete call tree. It executes
the active frame, resumes suspended parents through
frame_stack_pop as children halt, and returns the top-level
frame's output. Each step's carried state is supplied from the frame
registers and its returned state is assigned back; the handlers
themselves never touch the registers. STOP, SELFDESTRUCT, and
exceptional halts return the empty slice; RETURN and REVERT carry
their frozen memory slice in the halt value.
function interpret(
initial_gas : gas,
initial_state_gas : state_gas,
initial_state_spill : state_gas_spill,
initial_refund : gas_refund,
initial_sp : StackPointer,
initial_memory_base : memory_base,
initial_memory_height : memory_height,
initial_caller : address,
initial_address : address,
initial_code_address : address,
initial_value : word,
initial_state_gas_reservoir : state_gas,
initial_is_static : bool,
initial_depth : frame_depth,
initial_code : Code,
initial_calldata : CalldataSlice,
) -> (
(gas, state_gas, state_gas_spill, gas_refund, FrameStatus, OutputSlice)
) = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
let fork = profile.fork;
let blob_fee = blob_base_fee(fork, profile.blob_schedule, profile.excess_blob_gas_limit, k_header.excess_blob_gas);
frame_stack_reset();
var interpreting : bool = true;
var result : OutputSlice = EMPTY_OUTPUT_SLICE;
var carried_pc : code_pointer = 0;
var carried_sp : StackPointer = initial_sp;
var carried_memory_base : memory_base = initial_memory_base;
var carried_memory_height : memory_height = initial_memory_height;
var carried_gas : gas = initial_gas;
var carried_state_gas : state_gas = initial_state_gas;
var carried_state_spill : state_gas_spill = initial_state_spill;
var carried_refund : gas_refund = initial_refund;
var carried_status : FrameStatus = Running();
var carried_caller : address = initial_caller;
var carried_address : address = initial_address;
var carried_account_context : AccountExecutionContext = account_execution_context(initial_address);
var carried_code_address : address = initial_code_address;
var carried_value : word = initial_value;
var carried_state_gas_reservoir : state_gas = initial_state_gas_reservoir;
var carried_is_static : bool = initial_is_static;
var carried_depth : frame_depth = initial_depth;
var carried_code : Code = initial_code;
var carried_calldata : CalldataSlice = initial_calldata;
var carried_returndata : OutputSlice = EMPTY_OUTPUT_SLICE;
let initial_call_tree_gas = initial_gas + initial_state_gas;
var call_tree_steps_remaining : call_tree_steps = 3 * initial_call_tree_gas + 2;
while interpreting termination_measure(call_tree_steps_remaining) do {
let running = is_running(carried_status);
if running then {
let (fetched_pc, instruction) = fetch(carried_code, carried_pc, fork);
carried_pc = fetched_pc;
match instruction {
opcode_CREATE() => {
let previous_address = carried_address;
let transition = run_create(
carried_pc,
carried_gas,
carried_state_gas,
carried_state_spill,
carried_refund,
carried_sp,
carried_memory_base,
carried_memory_height,
carried_caller,
carried_address,
carried_code_address,
carried_value,
carried_state_gas_reservoir,
carried_is_static,
carried_depth,
carried_code,
carried_calldata,
carried_returndata,
CreateByNonce,
);
carried_pc = transition.pc;
carried_gas = transition.gas_remaining;
carried_state_gas = transition.state_gas_remaining;
carried_state_spill = transition.state_gas_spilled;
carried_refund = transition.refund;
carried_status = transition.status;
carried_sp = transition.stack_top;
carried_memory_base = transition.memory_base;
carried_memory_height = transition.memory_height;
carried_caller = transition.message.caller;
carried_address = transition.message.address;
carried_code_address = transition.message.code_address;
carried_value = transition.message.value;
carried_state_gas_reservoir = transition.message.state_gas_reservoir;
carried_is_static = transition.message.is_static;
carried_depth = transition.message.depth;
carried_code = transition.code;
carried_calldata = transition.calldata;
carried_returndata = transition.returndata;
carried_account_context = refresh_account_execution_context(
carried_account_context,
previous_address,
carried_address,
)
},
CREATE2() => {
let previous_address = carried_address;
let transition = run_create(
carried_pc,
carried_gas,
carried_state_gas,
carried_state_spill,
carried_refund,
carried_sp,
carried_memory_base,
carried_memory_height,
carried_caller,
carried_address,
carried_code_address,
carried_value,
carried_state_gas_reservoir,
…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)
}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
}The delegation target of an account's code, with a validity flag — false when the code is not a designator.
function k_deleg_target(a : address) -> (bool, address) = {
let h : hash = k_code_key(a);
let r : AddressResult = code_db_read_delegation(h);
(r.success, r.address)
}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)
}val not_bool = pure {coq: "negb", lean: "_lean_not", _: "not"}: forall ('p : Bool). bool('p) -> bool(not('p))The gas of the precompile at address num for a given input. Gas is
protocol policy defined entirely here; implementations return only
output. Length-only costs
derive from the word count; the two input-dependent curves (MODEXP,
BLAKE2F rounds) read the input in place. The match arms are the
precompile catalog with their addresses and pricing EIPs.
function precompile_gas(num : precompile_id, input : CalldataSlice, available : gas) -> GasCharge = {
let input_len = calldata_slice_length(input);
let input_length = input_len;
let words = memory_word_count(input_len);
match num {
NotPrecompile => GAS_CHARGE_UNAFFORDABLE,
Ecrecover => fixed_precompile_gas(3000, available), /* ECRECOVER (0x01) */
Sha256 => linear_gas(60, 12, words, available),
Ripemd160 => linear_gas(600, 120, words, available),
Identity => linear_gas(15, 3, words, available),
Modexp => modexp_gas(input, available), /* MODEXP (0x05), EIP-2565/7883 */
Bn254Add => fixed_precompile_gas(150, available), /* BN_ADD (0x06), EIP-1108 */
Bn254Mul => fixed_precompile_gas(6000, available), /* BN_MUL (0x07), EIP-1108 */
Bn254Pairing => linear_gas(45000, 34000, input_length / 192, available), /* BN_PAIRING (0x08), EIP-1108 */
Blake2f => {
let rounds = pc_blake2_rounds(input);
fixed_precompile_gas(rounds, available)
}, /* BLAKE2F */
KzgPointEvaluation => fixed_precompile_gas(50000, available), /* POINT_EVALUATION */
/* EIP-2537 BLS12-381 */
BlsG1Add => fixed_precompile_gas(375, available), /* G1ADD */
BlsG1Msm => bls_msm_gas(BLS_G1_DISCOUNT, 12000, 519, input_length / 160, available),
BlsG2Add => fixed_precompile_gas(600, available), /* G2ADD */
BlsG2Msm => bls_msm_gas(BLS_G2_DISCOUNT, 22500, 524, input_length / 288, available),
BlsPairing => linear_gas(37700, 32600, input_length / 384, available), /* PAIRING_CHECK */
BlsMapFpToG1 => fixed_precompile_gas(5500, available), /* MAP_FP_TO_G1 */
BlsMapFp2ToG2 => fixed_precompile_gas(23800, available), /* MAP_FP2_TO_G2 */
P256Verify => fixed_precompile_gas(6900, available), /* P256VERIFY */
}
}Maps 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 precompile dispatch: address to implementation. Gas has already been charged by the caller (precompile_gas).
function run_precompile_slice(num : precompile_id, input : CalldataSlice) -> PrecompileResult =
match num {
NotPrecompile => precompile_failure(),
Ecrecover => run_ecrecover(input),
Sha256 => run_sha256(input),
Ripemd160 => run_ripemd160(input),
Identity => copied_result(input),
Modexp => run_modexp(input),
Bn254Add => {
let success = accelerator_bn254_add(input);
accelerator_result(success, PRECOMPILE_DOUBLE_WORD_LENGTH)
},
Bn254Mul => {
let success = accelerator_bn254_mul(input);
accelerator_result(success, PRECOMPILE_DOUBLE_WORD_LENGTH)
},
Bn254Pairing => {
let input_length = calldata_slice_length(input);
let item_length = BN254_PAIRING_ITEM_LENGTH;
if input_length == (input_length / item_length) * item_length then {
let result = accelerator_bn254_pairing(input);
pairing_result(result)
} else {
precompile_failure()
}
},
Blake2f => run_blake2f(input),
KzgPointEvaluation => run_kzg_point_evaluation(input),
BlsG1Add => run_bls_g1_add(input),
BlsG1Msm => run_bls_g1_msm(input),
BlsG2Add => run_bls_g2_add(input),
BlsG2Msm => run_bls_g2_msm(input),
BlsPairing => run_bls_pairing(input),
BlsMapFpToG1 => run_bls_map_fp_to_g1(input),
BlsMapFp2ToG2 => run_bls_map_fp2_to_g2(input),
P256Verify => run_p256_verify(input),
}function word_nonzero(w) = {
let is_zero = word_is_zero(w);
not_bool(is_zero)
}EIP-7954 code/initcode size bump (65536/131072).
let Amsterdam : int(amsterdam_fork_value) = sizeof(amsterdam_fork_value)let EMPTY_OUTPUT_SLICE : OutputSliceFields(0, 0) = output_slice(0, 0)let GAS_ZERO : int(0) = 0The 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_PROFILECalldata is either the immutable top-level transaction input or a frozen range of the suspended caller's memory. The variants state the only two protocol-valid provenances instead of exposing the host's region enum.
union CalldataSlice = {
/* the immutable top-level transaction input */
InputCalldata : StatelessInputSlice,
/* a frozen range of the suspended caller's memory */
MemoryCalldata : EvmMemorySlice,
}Existential executable-code value whose concrete byte address and length
remain correlated inside CodeFields.
type Code = {
'off 'len,
code_region_valid_range('off, 'len) & code_valid_length('len).
CodeFields('off, 'len)
}Exceptional halts (YP §9.4.2): each consumes all remaining gas and reverts the frame's state changes.
enum ExceptionKind = {
/* an opcode pops more items than the stack holds */
StackUnderflow,
/* a push would exceed the 1024-item stack limit */
StackOverflow,
/* the operation's cost exceeds the remaining gas */
OutOfGas,
/* an unassigned or fork-inactive opcode, or INVALID (0xfe) */
InvalidOpcode,
/* a jump target that is not a valid JUMPDEST */
InvalidJump,
/* EIP-214: state-changing op inside a STATICCALL */
StaticViolation,
/* a call or create beyond depth 1024 */
CallDepthExceeded,
/* a value transfer exceeding the sender's balance */
InsufficientBalance,
/* EIP-214 write protection */
WriteProtection,
/* EIP-3860 */
InitCodeTooLarge,
/* a nonce at its maximum cannot be bumped (EIP-2681) */
NonceOverflow,
/* EIP-684: CREATE into an occupied account */
AddressCollision,
}Per-frame execution status: running, halted normally, or exceptionally halted.
union FrameStatus = {
/* mid-execution */
Running : unit,
/* halted normally (YP §9.4.4) */
Halted : HaltKind,
/* halted exceptionally: all frame gas consumed, effects void */
Exceptional : ExceptionKind
}A frame-output range with its coordinate and length packed existentially.
type OutputSlice = {
'off 'len,
output_region_valid_range('off, 'len).
OutputSliceFields('off, 'len)
}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,
}The operand-stack cursor for the active frame, threaded by value through
the interpreter in the state-passing convention and held in the
stack_top frame register at frame boundaries. storage is an opaque
host coordinate while height is the semantic stack height. Keeping the
height in the cursor makes stack validation independent of the host stack
representation. Optimized C refines storage to a native u256 *.
struct StackPointer = {
storage : bits(64),
height : operand_stack_height,
}A stateless-input range with its coordinate and length packed existentially.
type StatelessInputSlice = {
'off 'len,
stateless_input_valid_range('off, 'len).
StatelessInputSliceFields('off, 'len)
}A decoded transaction retains the fork-selected blob-count limit that was applied while decoding its envelope. Non-blob transactions carry the inactive zero limit.
type Transaction = {
'blob_limit,
transaction_blob_limit_value('blob_limit).
TransactionFields('blob_limit)
}A 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)Available gas in a running EVM frame. Every admitted transaction gas
limit originates in the execution payload's SSZ uint64 gas-limit
domain, and child frames can only receive gas from their parent.
type gas = range(0, 2 ^ 64 - 1)The signed transaction refund accumulator before capping.
type gas_refund = range(
-gas_refund_bound,
gas_refund_bound,
)An absolute byte position in the shared EVM-memory arena.
type memory_base = range(0, memory_region_bound)The active EVM frame's exact relative byte high-water mark. It is carried
beside the frame's absolute memory_base; the host retains no hidden
frame coordinate or lifecycle state.
type memory_height = memory_lengthAmsterdam's per-frame state-gas reservoir. The transaction's total gas
allowance remains in the execution payload's uint64 domain; only the
regular-gas portion and state-gas spill into that portion are capped by
EIP-7825.
type state_gas = range(0, 2 ^ 64 - 1)Execution gas temporarily consumed by Amsterdam state charges. EIP-8037 draws spill only from the regular-gas pool, which is capped by EIP-7825.
type state_gas_spill = range(0, transaction_execution_gas_limit_value)function run_legacy_transaction_frame¶
function run_legacy_transaction_frame(tx, v) = {
let initial_gas = v.gas;
k_journal_checkpoint();
let (
initial_execution_gas,
initial_state_gas,
initial_state_spill,
initial_refund,
initial_stack,
initial_memory_base,
initial_memory_height,
) = enter_transaction_frame(v);
let state_gas_reservoir = initial_state_gas;
let (gas_after, state_gas_after, state_spill_after, refund_after, status_after, _) =
if tx.is_create
then run_create_transaction_frame(
tx,
v.sender,
v.nonce_before,
initial_execution_gas,
initial_state_gas,
initial_state_spill,
initial_refund,
initial_stack,
initial_memory_base,
initial_memory_height,
EMPTY_CODE,
EMPTY_CALLDATA,
state_gas_reservoir,
)
else run_call_transaction_frame(
tx,
v.sender,
false,
initial_execution_gas,
initial_state_gas,
initial_state_spill,
initial_refund,
initial_stack,
initial_memory_base,
initial_memory_height,
tx.recipient,
EMPTY_CODE,
EMPTY_CALLDATA,
state_gas_reservoir,
);
let success = frame_succeeded(status_after);
let failed = not_bool(success);
if failed then {
k_journal_revert()
} else {
k_journal_commit()
};
let state_delta = frame_state_gas_used(state_gas_reservoir, state_gas_after, state_spill_after);
let retained_refund =
if success then refund_after else GAS_REFUND_ZERO;
struct {
success = success,
gas = tx_frame_gas_snapshot(initial_gas, gas_after, state_gas_after, state_delta),
refund = retained_refund,
}
}Resets the user-space machine for the transaction's top-level frame,
funding it with gas_limit − intrinsic.
function enter_transaction_frame(v : TxValidity) -> (
(gas, state_gas, state_gas_spill, gas_refund, StackPointer, memory_base, memory_height)
) = {
let initial_gas = v.gas;
let stack = stack_reset();
(
initial_gas.execution_remaining,
initial_gas.state_remaining,
STATE_GAS_SPILL_ZERO,
GAS_REFUND_ZERO,
stack,
MEMORY_BASE_ZERO,
MEMORY_HEIGHT_ZERO,
)
}Computes the signed state gas consumed by the current frame.
function frame_state_gas_used(
state_gas_reservoir : state_gas,
state_gas_remaining : state_gas,
state_gas_spilled : state_gas_spill,
) -> (
frame_state_gas_delta
) = {
let entry = state_gas_reservoir;
let remaining = state_gas_remaining;
let spilled = state_gas_spilled;
entry - remaining + spilled
}Whether the just-finished frame ended successfully: a normal halt
succeeds; a REVERT and any exceptional halt do not (their world
effects are rolled back and CALL/CREATE reports failure).
function frame_succeeded(frame_status : FrameStatus) -> bool =
match frame_status {
Halted(HaltRevert(_)) => false,
Halted(_) => true,
Running() => true,
Exceptional(_) => false,
}Appends a frame marker to the state journal. The suspended frame stores its refund counter separately.
function k_journal_checkpoint() -> unit = state_journal_checkpoint()Records a successful child frame without discarding its reversible entries.
function k_journal_commit() -> unit = state_journal_commit()Replays the state journal backwards to its innermost open frame boundary.
function k_journal_revert() -> unit = state_journal_revert()val not_bool = pure {coq: "negb", lean: "_lean_not", _: "not"}: forall ('p : Bool). bool('p) -> bool(not('p))Runs a call transaction's top-level frame: transfers value, then either runs a direct recipient precompile or interprets the selected code. At Amsterdam the preparation phase has already resolved and charged a recipient delegation; a delegated recipient never dispatches a precompile directly.
function run_call_transaction_frame(
tx : Transaction,
sender : address,
delegated : bool,
carried_gas : gas,
carried_state_gas : state_gas,
carried_state_spill : state_gas_spill,
carried_refund : gas_refund,
carried_stack : StackPointer,
carried_memory_base : memory_base,
carried_memory_height : memory_height,
carried_code_address : address,
carried_code : Code,
carried_calldata : CalldataSlice,
state_gas_reservoir : state_gas,
) -> (
(gas, state_gas, state_gas_spill, gas_refund, FrameStatus, OutputSlice)
) = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
var gas_after : gas = carried_gas;
var state_gas_after : state_gas = carried_state_gas;
var state_spill_after : state_gas_spill = carried_state_spill;
var refund_after : gas_refund = carried_refund;
var status_after : FrameStatus = Running();
var output_after : OutputSlice = EMPTY_OUTPUT_SLICE;
var code_address : address = carried_code_address;
var frame_code : Code = carried_code;
var frame_calldata : CalldataSlice = carried_calldata;
/* The recipient account is read during message setup (for its code) on
* every call-transaction, so it is always a state access -- keep it in the
* account set (BAL), including a tx sent directly to a precompile. */
let _ = k_aload(tx.recipient);
let transfers_value = word_nonzero(tx.value);
if transfers_value then {
k_transfer(sender, tx.recipient, tx.value)
};
let selected_precompile = precompile_id_for_address(tx.recipient);
var direct_precompile : bool = false;
if not_bool(delegated) then {
direct_precompile = selected_precompile != NotPrecompile
};
if direct_precompile then {
/* tx directly to a precompile: run it as the top-level frame, gas-checked
FIRST (an OOG precompile must not execute). Failure or OOG is an
exceptional halt (all gas consumed, value transfer reverted). */
let input_src : StatelessInputSlice = tx.input_src;
let precompile_input = InputCalldata(input_src);
let precompile_charge = precompile_gas(selected_precompile, precompile_input, gas_after);
if precompile_charge.affordable then {
let used = precompile_charge.cost;
let result = run_precompile_slice(selected_precompile, precompile_input);
if result.success then {
gas_after = gas_sub(gas_after, used);
output_after = result.output;
let halt_reason = HaltReturn(result.output);
status_after = Halted(halt_reason)
} else {
gas_after = GAS_ZERO;
let exceptional = exceptional_state(state_gas_after, state_spill_after, state_gas_reservoir, OutOfGas);
state_gas_after = exceptional.state_gas_remaining;
state_spill_after = exceptional.state_gas_spilled;
status_after = exceptional.status
}
} else {
gas_after = GAS_ZERO;
let exceptional = exceptional_state(state_gas_after, state_spill_after, state_gas_reservoir, OutOfGas);
state_gas_after = exceptional.state_gas_remaining;
state_spill_after = exceptional.state_gas_spilled;
status_after = exceptional.status
}
} else {
if profile.fork < Amsterdam then {
frame_calldata = InputCalldata(tx.input_src);
code_address = tx.recipient;
/* EIP-7702: before Amsterdam the transaction-level delegate is
warmed but has no separate access charge. */
let (tx_deleg, tx_dtgt) = k_deleg_target(tx.recipient);
if tx_deleg then {
k_account_mark_warm(tx_dtgt);
let _ = k_aload(tx_dtgt);
()
};
if tx_deleg then {
code_address = tx_dtgt
};
frame_code = executable_code(tx.recipient, tx_deleg, tx_dtgt)
};
(gas_after, state_gas_after, state_spill_after, refund_after, status_after, output_after) = interpret(
gas_after,
state_gas_after,
state_spill_after,
refund_after,
carried_stack,
carried_memory_base,
carried_memory_height,
sender,
tx.recipient,
code_address,
tx.value,
state_gas_reservoir,
false,
0,
frame_code,
frame_calldata,
)
};
(gas_after, state_gas_after, state_spill_after, refund_after, status_after, output_after)
}Runs a create transaction's top-level frame: derives the new address
from (sender, nonce_before), fails outright on an address collision
(all gas consumed, no initcode runs — EIP-684/EIP-7610), and
otherwise deploys via the initcode path.
function run_create_transaction_frame(
tx : Transaction,
sender : address,
nonce_before : account_nonce,
carried_gas : gas,
carried_state_gas : state_gas,
carried_state_spill : state_gas_spill,
carried_refund : gas_refund,
carried_stack : StackPointer,
carried_memory_base : memory_base,
carried_memory_height : memory_height,
carried_code : Code,
carried_calldata : CalldataSlice,
state_gas_reservoir : state_gas,
) -> (
(gas, state_gas, state_gas_spill, gas_refund, FrameStatus, OutputSlice)
) = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
let new_addr = k_create_addr(sender, nonce_before);
var gas_after : gas = carried_gas;
var state_gas_after : state_gas = carried_state_gas;
var state_spill_after : state_gas_spill = carried_state_spill;
var refund_after : gas_refund = carried_refund;
var status_after : FrameStatus = Running();
var output_after : OutputSlice = EMPTY_OUTPUT_SLICE;
k_account_mark_warm(new_addr);
/* create-tx address collision (code, nonzero nonce, or storage at the
* target): the tx fails outright consuming ALL gas, no initcode runs
* (EIP-684/EIP-7610; gas_left = 0). */
let occupied = k_account_occupied(new_addr);
if occupied then {
gas_after = GAS_ZERO;
let exceptional = exceptional_state(state_gas_after, state_spill_after, state_gas_reservoir, AddressCollision);
state_gas_after = exceptional.state_gas_remaining;
state_spill_after = exceptional.state_gas_spilled;
status_after = exceptional.status
} else {
k_mark_created(new_addr); /* EIP-6780: created this tx */
k_clear_storage(new_addr);
k_bump_nonce(new_addr);
let transfers_value = word_nonzero(tx.value);
if transfers_value then {
k_transfer(sender, new_addr, tx.value)
};
var frame_code : Code = carried_code;
var frame_calldata : CalldataSlice = carried_calldata;
if profile.fork < Amsterdam then {
let initcode = transaction_initcode_slice(tx.input_src);
let code_id = code_db_insert(initcode, profile.fork);
frame_code = code_db_resolve(code_id);
frame_calldata = EMPTY_CALLDATA
};
(gas_after, state_gas_after, state_spill_after, refund_after, status_after, output_after) = interpret(
gas_after,
state_gas_after,
state_spill_after,
refund_after,
carried_stack,
carried_memory_base,
carried_memory_height,
sender,
new_addr,
new_addr,
tx.value,
state_gas_reservoir,
false,
0,
frame_code,
frame_calldata,
);
let initcode_succeeded = frame_succeeded(status_after);
if initcode_succeeded then {
let deployed_output = output_after;
let dep_len = deployed_output.len;
let deployed_length = dep_len;
let valid_deployed_size = deployed_code_size_allowed(deployed_length);
let valid_prefix =
if (profile.fork < London) | (deployed_length == 0) then {
true
} else {
let first_byte = slice_byte(deployed_output, 0);
first_byte != 0xef
};
if valid_deployed_size & valid_prefix then {
let deployment_charge = code_deployment_execution_cost(dep_len, gas_after);
if deployment_charge.affordable then {
let execution_deposit = deployment_charge.cost;
gas_after = gas_sub(gas_after, execution_deposit);
let state_deposit = code_deployment_state_cost(dep_len);
var deployment_halt : bool = false;
(deployment_halt, gas_after, state_gas_after, state_spill_after) = charge_state_gas(
gas_after,
state_gas_after,
state_spill_after,
state_deposit,
);
if deployment_halt then {
gas_after = GAS_ZERO;
let exceptional = exceptional_state(
state_gas_after,
state_spill_after,
state_gas_reservoir,
OutOfGas,
);
state_gas_after = exceptional.state_gas_remaining;
state_spill_after = exceptional.state_gas_spilled;
status_after = exceptional.status
};
let deployment_succeeded = frame_succeeded(status_after);
if deployment_succeeded then {
let stored_code = code_db_intern_output(deployed_output);
k_deploy_code(new_addr, stored_code)
}
} else if profile.fork < Homestead then {
/* Frontier consumes the remaining gas and keeps the
* created account with empty code. */
gas_after = GAS_ZERO;
k_deploy_code(new_addr, EMPTY_CODE_SLICE)
…function run_legacy_transaction_frame(tx, v) = {
let initial_gas = v.gas;
k_journal_checkpoint();
let (
initial_execution_gas,
initial_state_gas,
initial_state_spill,
initial_refund,
initial_stack,
initial_memory_base,
initial_memory_height,
) = enter_transaction_frame(v);
let state_gas_reservoir = initial_state_gas;
let (gas_after, state_gas_after, state_spill_after, refund_after, status_after, _) =
if tx.is_create
then run_create_transaction_frame(
tx,
v.sender,
v.nonce_before,
initial_execution_gas,
initial_state_gas,
initial_state_spill,
initial_refund,
initial_stack,
initial_memory_base,
initial_memory_height,
EMPTY_CODE,
EMPTY_CALLDATA,
state_gas_reservoir,
)
else run_call_transaction_frame(
tx,
v.sender,
false,
initial_execution_gas,
initial_state_gas,
initial_state_spill,
initial_refund,
initial_stack,
initial_memory_base,
initial_memory_height,
tx.recipient,
EMPTY_CODE,
EMPTY_CALLDATA,
state_gas_reservoir,
);
let success = frame_succeeded(status_after);
let failed = not_bool(success);
if failed then {
k_journal_revert()
} else {
k_journal_commit()
};
let state_delta = frame_state_gas_used(state_gas_reservoir, state_gas_after, state_spill_after);
let retained_refund =
if success then refund_after else GAS_REFUND_ZERO;
struct {
success = success,
gas = tx_frame_gas_snapshot(initial_gas, gas_after, state_gas_after, state_delta),
refund = retained_refund,
}
}function tx_frame_gas_snapshot(initial, execution, state, state_delta) = {
let limit = initial.admitted_limit;
let regular = initial.regular_limit;
if execution <= limit then {
let room = limit - execution;
if state <= room then {
let (remaining as 'remaining) = execution + state;
let spent = limit - remaining;
let raw_state_used : transaction_state_gas_delta = initial.intrinsic_state + state_delta;
if raw_state_used <= 0 then {
if spent <= regular then {
tx_frame_gas_snapshot_fields(limit, regular, initial.calldata_floor, remaining, 0)
} else {
fatal_error(ExecutionInvalid)
}
} else {
let positive_state_used : transaction_state_gas_used = raw_state_used;
if positive_state_used <= spent then {
/* The comparison is the semantic boundary that narrows the
signed transaction delta back into the admitted reservoir.
Name that refinement explicitly so optimized lowering can
convert directly to the concrete gas width instead of first
materializing the wider positive range of the signed sum. */
let (bounded_state_used as 'bounded_state_used) : range(0, 'limit - 'remaining) = positive_state_used;
if spent - bounded_state_used <= regular then {
tx_frame_gas_snapshot_fields(
limit,
regular,
initial.calldata_floor,
remaining,
bounded_state_used,
)
} else {
fatal_error(ExecutionInvalid)
}
} else {
fatal_error(ExecutionInvalid)
}
}
} else {
fatal_error(ExecutionInvalid)
}
} else {
fatal_error(ExecutionInvalid)
}
}let EMPTY_CALLDATA : CalldataSlice = InputCalldata(EMPTY_STATELESS_INPUT_SLICE)let EMPTY_CODE : Code = analyzed_code(EMPTY_CODE_SLICE, EMPTY_JUMP_TABLE)let GAS_REFUND_ZERO : gas_refund = 0Available gas in a running EVM frame. Every admitted transaction gas
limit originates in the execution payload's SSZ uint64 gas-limit
domain, and child frames can only receive gas from their parent.
type gas = range(0, 2 ^ 64 - 1)function run_amsterdam_transaction_frame¶
function run_amsterdam_transaction_frame(tx, v, upfront, authorizations) = {
let (
entered_gas,
entered_state_gas,
entered_state_spill,
entered_refund,
entered_stack,
entered_memory_base,
entered_memory,
) = enter_transaction_frame(v);
var gas_after : gas = entered_gas;
var state_gas_after : state_gas = entered_state_gas;
var state_spill_after : state_gas_spill = entered_state_spill;
var refund_after : gas_refund = entered_refund;
var status_after : FrameStatus = Running();
var output_after : OutputSlice = EMPTY_OUTPUT_SLICE;
let initial_gas = v.gas;
k_journal_checkpoint();
let preparation_reservoir = state_gas_after;
let current_target =
if tx.is_create then k_create_addr(v.sender, v.nonce_before) else tx.recipient;
authorization_tracker_reset(authorizations.count);
let transfers_value = word_nonzero(tx.value);
var preparation_ready : bool = false;
(preparation_ready, gas_after, state_gas_after, state_spill_after) = process_amsterdam_auth_cursor(
authorizations,
authorizations.count,
v.sender,
current_target,
transfers_value,
gas_after,
state_gas_after,
state_spill_after,
);
var authorization_state_gas : frame_state_gas_delta = FRAME_STATE_GAS_DELTA_ZERO;
var delegated : bool = false;
var execution_reservoir : state_gas = state_gas_after;
var prepared_code_address : address = current_target;
var prepared_code : Code = EMPTY_CODE;
var prepared_calldata : CalldataSlice = EMPTY_CALLDATA;
if preparation_ready then {
authorization_state_gas = frame_state_gas_used(preparation_reservoir, state_gas_after, state_spill_after);
execution_reservoir = state_gas_after;
state_spill_after = STATE_GAS_SPILL_ZERO;
let (preparation, prepared_gas, prepared_state_gas, prepared_state_spill, _, code_address, code, calldata) = prepare_amsterdam_transaction_dispatch(
tx,
v,
upfront,
gas_after,
state_gas_after,
state_spill_after,
);
gas_after = prepared_gas;
state_gas_after = prepared_state_gas;
state_spill_after = prepared_state_spill;
preparation_ready = preparation.ready;
delegated = preparation.delegated;
prepared_code_address = code_address;
prepared_code = code;
prepared_calldata = calldata
};
let preparation_failed = not_bool(preparation_ready);
if preparation_failed then {
k_journal_revert();
state_gas_after = preparation_reservoir;
state_spill_after = STATE_GAS_SPILL_ZERO;
return struct {
success = false,
gas = tx_frame_gas_snapshot(initial_gas, GAS_ZERO, STATE_GAS_ZERO, FRAME_STATE_GAS_DELTA_ZERO),
refund = GAS_REFUND_ZERO,
}
};
k_journal_checkpoint();
if tx.is_create then {
(gas_after, state_gas_after, state_spill_after, refund_after, status_after, output_after) = run_create_transaction_frame(
tx,
v.sender,
v.nonce_before,
gas_after,
state_gas_after,
state_spill_after,
refund_after,
entered_stack,
entered_memory_base,
entered_memory,
prepared_code,
prepared_calldata,
execution_reservoir,
)
} else {
(gas_after, state_gas_after, state_spill_after, refund_after, status_after, output_after) = run_call_transaction_frame(
tx,
v.sender,
delegated,
gas_after,
state_gas_after,
state_spill_after,
refund_after,
entered_stack,
entered_memory_base,
entered_memory,
prepared_code_address,
prepared_code,
prepared_calldata,
execution_reservoir,
)
};
let success = frame_succeeded(status_after);
let failed = not_bool(success);
if failed then {
k_journal_revert()
} else {
k_journal_commit()
};
/* Authorization writes live in the preparation scope and survive an EVM
execution failure once preparation itself has completed. */
k_journal_commit();
let execution_state_delta = frame_state_gas_used(execution_reservoir, state_gas_after, state_spill_after);
let state_delta = authorization_state_gas + execution_state_delta;
let retained_refund =
if success then refund_after else GAS_REFUND_ZERO;
struct {
success = success,
gas = tx_frame_gas_snapshot(initial_gas, gas_after, state_gas_after, state_delta),
refund = retained_refund,
}
}Clears and pre-sizes the authorization tracker for one transaction.
val authorization_tracker_reset = impure { c: "authorization_tracker_reset" } : prepared_authorization_count -> unitResets the user-space machine for the transaction's top-level frame,
funding it with gas_limit − intrinsic.
function enter_transaction_frame(v : TxValidity) -> (
(gas, state_gas, state_gas_spill, gas_refund, StackPointer, memory_base, memory_height)
) = {
let initial_gas = v.gas;
let stack = stack_reset();
(
initial_gas.execution_remaining,
initial_gas.state_remaining,
STATE_GAS_SPILL_ZERO,
GAS_REFUND_ZERO,
stack,
MEMORY_BASE_ZERO,
MEMORY_HEIGHT_ZERO,
)
}Computes the signed state gas consumed by the current frame.
function frame_state_gas_used(
state_gas_reservoir : state_gas,
state_gas_remaining : state_gas,
state_gas_spilled : state_gas_spill,
) -> (
frame_state_gas_delta
) = {
let entry = state_gas_reservoir;
let remaining = state_gas_remaining;
let spilled = state_gas_spilled;
entry - remaining + spilled
}Whether the just-finished frame ended successfully: a normal halt
succeeds; a REVERT and any exceptional halt do not (their world
effects are rolled back and CALL/CREATE reports failure).
function frame_succeeded(frame_status : FrameStatus) -> bool =
match frame_status {
Halted(HaltRevert(_)) => false,
Halted(_) => true,
Running() => true,
Exceptional(_) => false,
}The CREATE address rule, in kernel form.
function k_create_addr(a : address, nonce : account_nonce) -> address = create_address(a, nonce)Appends a frame marker to the state journal. The suspended frame stores its refund counter separately.
function k_journal_checkpoint() -> unit = state_journal_checkpoint()Records a successful child frame without discarding its reversible entries.
function k_journal_commit() -> unit = state_journal_commit()Replays the state journal backwards to its innermost open frame boundary.
function k_journal_revert() -> unit = state_journal_revert()val not_bool = pure {coq: "negb", lean: "_lean_not", _: "not"}: forall ('p : Bool). bool('p) -> bool(not('p))Charges Amsterdam's state-dependent top-level dispatch costs and installs the code selected for execution. This phase deliberately performs no revertible account mutation: its state-gas charges are therefore refilled if the subsequently dispatched frame fails.
function prepare_amsterdam_transaction_dispatch(
tx : Transaction,
v : TxValidity,
upfront : TxUpfrontResult,
carried_gas : gas,
carried_state_gas : state_gas,
carried_state_spill : state_gas_spill,
) -> (
(TransactionPreparation, gas, state_gas, state_gas_spill, address, address, Code, CalldataSlice)
) = {
var gas_after : gas = carried_gas;
var state_gas_after : state_gas = carried_state_gas;
var state_spill_after : state_gas_spill = carried_state_spill;
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
let current_target =
if tx.is_create then k_create_addr(v.sender, v.nonce_before) else tx.recipient;
if tx.is_create then {
if upfront.create_target_prestate_empty then {
let (state_gas_halt, next_gas, next_state_gas, next_state_spill) = charge_state_gas(
gas_after,
state_gas_after,
state_spill_after,
G_amsterdam_state_new_account,
);
gas_after = next_gas;
state_gas_after = next_state_gas;
state_spill_after = next_state_spill;
if state_gas_halt then {
return (
struct { ready = false, delegated = false },
gas_after,
state_gas_after,
state_spill_after,
current_target,
current_target,
EMPTY_CODE,
EMPTY_CALLDATA,
)
}
};
let initcode = transaction_initcode_slice(tx.input_src);
let code_id = code_db_insert(initcode, profile.fork);
let code = code_db_resolve(code_id);
(
struct { ready = true, delegated = false },
gas_after,
state_gas_after,
state_spill_after,
current_target,
current_target,
code,
EMPTY_CALLDATA,
)
} else {
let calldata = InputCalldata(tx.input_src);
let transfers_value = word_nonzero(tx.value);
let recipient_empty = k_account_is_empty(tx.recipient);
if transfers_value & recipient_empty then {
let (state_gas_halt, next_gas, next_state_gas, next_state_spill) = charge_state_gas(
gas_after,
state_gas_after,
state_spill_after,
G_amsterdam_state_new_account,
);
gas_after = next_gas;
state_gas_after = next_state_gas;
state_spill_after = next_state_spill;
if state_gas_halt then {
return (
struct { ready = false, delegated = false },
gas_after,
state_gas_after,
state_spill_after,
current_target,
current_target,
EMPTY_CODE,
calldata,
)
}
};
let (delegated, delegate) = k_deleg_target(tx.recipient);
if delegated then {
let warm = k_account_is_warm(delegate);
let access_cost = account_cost(warm);
if gas_after < access_cost then {
return (
struct { ready = false, delegated = false },
GAS_ZERO,
state_gas_after,
state_spill_after,
current_target,
current_target,
EMPTY_CODE,
calldata,
)
};
gas_after = gas_sub(gas_after, access_cost);
k_account_mark_warm(delegate)
};
let code_address =
if delegated then delegate else current_target;
let code = executable_code(tx.recipient, delegated, delegate);
(
struct { ready = true, delegated = delegated },
gas_after,
state_gas_after,
state_spill_after,
current_target,
code_address,
code,
calldata,
)
}
}function process_amsterdam_auth_cursor(
authorizations,
count,
sender,
current_target,
transfers_value,
gas,
state_gas,
state_spill,
) =
if count == 0 then {
(true, gas, state_gas, state_spill)
} else {
let authorization = prepared_authorization_head(authorizations);
let remaining = prepared_authorization_tail(authorizations, count);
let (processed, gas_after, state_gas_after, state_spill_after) = process_amsterdam_auth(
authorization,
sender,
current_target,
transfers_value,
gas,
state_gas,
state_spill,
);
if processed then {
process_amsterdam_auth_cursor(
remaining,
count - 1,
sender,
current_target,
transfers_value,
gas_after,
state_gas_after,
state_spill_after,
)
} else {
(false, gas_after, state_gas_after, state_spill_after)
}
}function run_amsterdam_transaction_frame(tx, v, upfront, authorizations) = {
let (
entered_gas,
entered_state_gas,
entered_state_spill,
entered_refund,
entered_stack,
entered_memory_base,
entered_memory,
) = enter_transaction_frame(v);
var gas_after : gas = entered_gas;
var state_gas_after : state_gas = entered_state_gas;
var state_spill_after : state_gas_spill = entered_state_spill;
var refund_after : gas_refund = entered_refund;
var status_after : FrameStatus = Running();
var output_after : OutputSlice = EMPTY_OUTPUT_SLICE;
let initial_gas = v.gas;
k_journal_checkpoint();
let preparation_reservoir = state_gas_after;
let current_target =
if tx.is_create then k_create_addr(v.sender, v.nonce_before) else tx.recipient;
authorization_tracker_reset(authorizations.count);
let transfers_value = word_nonzero(tx.value);
var preparation_ready : bool = false;
(preparation_ready, gas_after, state_gas_after, state_spill_after) = process_amsterdam_auth_cursor(
authorizations,
authorizations.count,
v.sender,
current_target,
transfers_value,
gas_after,
state_gas_after,
state_spill_after,
);
var authorization_state_gas : frame_state_gas_delta = FRAME_STATE_GAS_DELTA_ZERO;
var delegated : bool = false;
var execution_reservoir : state_gas = state_gas_after;
var prepared_code_address : address = current_target;
var prepared_code : Code = EMPTY_CODE;
var prepared_calldata : CalldataSlice = EMPTY_CALLDATA;
if preparation_ready then {
authorization_state_gas = frame_state_gas_used(preparation_reservoir, state_gas_after, state_spill_after);
execution_reservoir = state_gas_after;
state_spill_after = STATE_GAS_SPILL_ZERO;
let (preparation, prepared_gas, prepared_state_gas, prepared_state_spill, _, code_address, code, calldata) = prepare_amsterdam_transaction_dispatch(
tx,
v,
upfront,
gas_after,
state_gas_after,
state_spill_after,
);
gas_after = prepared_gas;
state_gas_after = prepared_state_gas;
state_spill_after = prepared_state_spill;
preparation_ready = preparation.ready;
delegated = preparation.delegated;
prepared_code_address = code_address;
prepared_code = code;
prepared_calldata = calldata
};
let preparation_failed = not_bool(preparation_ready);
if preparation_failed then {
k_journal_revert();
state_gas_after = preparation_reservoir;
state_spill_after = STATE_GAS_SPILL_ZERO;
return struct {
success = false,
gas = tx_frame_gas_snapshot(initial_gas, GAS_ZERO, STATE_GAS_ZERO, FRAME_STATE_GAS_DELTA_ZERO),
refund = GAS_REFUND_ZERO,
}
};
k_journal_checkpoint();
if tx.is_create then {
(gas_after, state_gas_after, state_spill_after, refund_after, status_after, output_after) = run_create_transaction_frame(
tx,
v.sender,
v.nonce_before,
gas_after,
state_gas_after,
state_spill_after,
refund_after,
entered_stack,
entered_memory_base,
entered_memory,
prepared_code,
prepared_calldata,
execution_reservoir,
)
} else {
(gas_after, state_gas_after, state_spill_after, refund_after, status_after, output_after) = run_call_transaction_frame(
tx,
v.sender,
delegated,
gas_after,
state_gas_after,
state_spill_after,
refund_after,
entered_stack,
entered_memory_base,
entered_memory,
prepared_code_address,
prepared_code,
prepared_calldata,
execution_reservoir,
)
};
let success = frame_succeeded(status_after);
let failed = not_bool(success);
if failed then {
k_journal_revert()
} else {
k_journal_commit()
};
…Runs a call transaction's top-level frame: transfers value, then either runs a direct recipient precompile or interprets the selected code. At Amsterdam the preparation phase has already resolved and charged a recipient delegation; a delegated recipient never dispatches a precompile directly.
function run_call_transaction_frame(
tx : Transaction,
sender : address,
delegated : bool,
carried_gas : gas,
carried_state_gas : state_gas,
carried_state_spill : state_gas_spill,
carried_refund : gas_refund,
carried_stack : StackPointer,
carried_memory_base : memory_base,
carried_memory_height : memory_height,
carried_code_address : address,
carried_code : Code,
carried_calldata : CalldataSlice,
state_gas_reservoir : state_gas,
) -> (
(gas, state_gas, state_gas_spill, gas_refund, FrameStatus, OutputSlice)
) = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
var gas_after : gas = carried_gas;
var state_gas_after : state_gas = carried_state_gas;
var state_spill_after : state_gas_spill = carried_state_spill;
var refund_after : gas_refund = carried_refund;
var status_after : FrameStatus = Running();
var output_after : OutputSlice = EMPTY_OUTPUT_SLICE;
var code_address : address = carried_code_address;
var frame_code : Code = carried_code;
var frame_calldata : CalldataSlice = carried_calldata;
/* The recipient account is read during message setup (for its code) on
* every call-transaction, so it is always a state access -- keep it in the
* account set (BAL), including a tx sent directly to a precompile. */
let _ = k_aload(tx.recipient);
let transfers_value = word_nonzero(tx.value);
if transfers_value then {
k_transfer(sender, tx.recipient, tx.value)
};
let selected_precompile = precompile_id_for_address(tx.recipient);
var direct_precompile : bool = false;
if not_bool(delegated) then {
direct_precompile = selected_precompile != NotPrecompile
};
if direct_precompile then {
/* tx directly to a precompile: run it as the top-level frame, gas-checked
FIRST (an OOG precompile must not execute). Failure or OOG is an
exceptional halt (all gas consumed, value transfer reverted). */
let input_src : StatelessInputSlice = tx.input_src;
let precompile_input = InputCalldata(input_src);
let precompile_charge = precompile_gas(selected_precompile, precompile_input, gas_after);
if precompile_charge.affordable then {
let used = precompile_charge.cost;
let result = run_precompile_slice(selected_precompile, precompile_input);
if result.success then {
gas_after = gas_sub(gas_after, used);
output_after = result.output;
let halt_reason = HaltReturn(result.output);
status_after = Halted(halt_reason)
} else {
gas_after = GAS_ZERO;
let exceptional = exceptional_state(state_gas_after, state_spill_after, state_gas_reservoir, OutOfGas);
state_gas_after = exceptional.state_gas_remaining;
state_spill_after = exceptional.state_gas_spilled;
status_after = exceptional.status
}
} else {
gas_after = GAS_ZERO;
let exceptional = exceptional_state(state_gas_after, state_spill_after, state_gas_reservoir, OutOfGas);
state_gas_after = exceptional.state_gas_remaining;
state_spill_after = exceptional.state_gas_spilled;
status_after = exceptional.status
}
} else {
if profile.fork < Amsterdam then {
frame_calldata = InputCalldata(tx.input_src);
code_address = tx.recipient;
/* EIP-7702: before Amsterdam the transaction-level delegate is
warmed but has no separate access charge. */
let (tx_deleg, tx_dtgt) = k_deleg_target(tx.recipient);
if tx_deleg then {
k_account_mark_warm(tx_dtgt);
let _ = k_aload(tx_dtgt);
()
};
if tx_deleg then {
code_address = tx_dtgt
};
frame_code = executable_code(tx.recipient, tx_deleg, tx_dtgt)
};
(gas_after, state_gas_after, state_spill_after, refund_after, status_after, output_after) = interpret(
gas_after,
state_gas_after,
state_spill_after,
refund_after,
carried_stack,
carried_memory_base,
carried_memory_height,
sender,
tx.recipient,
code_address,
tx.value,
state_gas_reservoir,
false,
0,
frame_code,
frame_calldata,
)
};
(gas_after, state_gas_after, state_spill_after, refund_after, status_after, output_after)
}Runs a create transaction's top-level frame: derives the new address
from (sender, nonce_before), fails outright on an address collision
(all gas consumed, no initcode runs — EIP-684/EIP-7610), and
otherwise deploys via the initcode path.
function run_create_transaction_frame(
tx : Transaction,
sender : address,
nonce_before : account_nonce,
carried_gas : gas,
carried_state_gas : state_gas,
carried_state_spill : state_gas_spill,
carried_refund : gas_refund,
carried_stack : StackPointer,
carried_memory_base : memory_base,
carried_memory_height : memory_height,
carried_code : Code,
carried_calldata : CalldataSlice,
state_gas_reservoir : state_gas,
) -> (
(gas, state_gas, state_gas_spill, gas_refund, FrameStatus, OutputSlice)
) = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
let new_addr = k_create_addr(sender, nonce_before);
var gas_after : gas = carried_gas;
var state_gas_after : state_gas = carried_state_gas;
var state_spill_after : state_gas_spill = carried_state_spill;
var refund_after : gas_refund = carried_refund;
var status_after : FrameStatus = Running();
var output_after : OutputSlice = EMPTY_OUTPUT_SLICE;
k_account_mark_warm(new_addr);
/* create-tx address collision (code, nonzero nonce, or storage at the
* target): the tx fails outright consuming ALL gas, no initcode runs
* (EIP-684/EIP-7610; gas_left = 0). */
let occupied = k_account_occupied(new_addr);
if occupied then {
gas_after = GAS_ZERO;
let exceptional = exceptional_state(state_gas_after, state_spill_after, state_gas_reservoir, AddressCollision);
state_gas_after = exceptional.state_gas_remaining;
state_spill_after = exceptional.state_gas_spilled;
status_after = exceptional.status
} else {
k_mark_created(new_addr); /* EIP-6780: created this tx */
k_clear_storage(new_addr);
k_bump_nonce(new_addr);
let transfers_value = word_nonzero(tx.value);
if transfers_value then {
k_transfer(sender, new_addr, tx.value)
};
var frame_code : Code = carried_code;
var frame_calldata : CalldataSlice = carried_calldata;
if profile.fork < Amsterdam then {
let initcode = transaction_initcode_slice(tx.input_src);
let code_id = code_db_insert(initcode, profile.fork);
frame_code = code_db_resolve(code_id);
frame_calldata = EMPTY_CALLDATA
};
(gas_after, state_gas_after, state_spill_after, refund_after, status_after, output_after) = interpret(
gas_after,
state_gas_after,
state_spill_after,
refund_after,
carried_stack,
carried_memory_base,
carried_memory_height,
sender,
new_addr,
new_addr,
tx.value,
state_gas_reservoir,
false,
0,
frame_code,
frame_calldata,
);
let initcode_succeeded = frame_succeeded(status_after);
if initcode_succeeded then {
let deployed_output = output_after;
let dep_len = deployed_output.len;
let deployed_length = dep_len;
let valid_deployed_size = deployed_code_size_allowed(deployed_length);
let valid_prefix =
if (profile.fork < London) | (deployed_length == 0) then {
true
} else {
let first_byte = slice_byte(deployed_output, 0);
first_byte != 0xef
};
if valid_deployed_size & valid_prefix then {
let deployment_charge = code_deployment_execution_cost(dep_len, gas_after);
if deployment_charge.affordable then {
let execution_deposit = deployment_charge.cost;
gas_after = gas_sub(gas_after, execution_deposit);
let state_deposit = code_deployment_state_cost(dep_len);
var deployment_halt : bool = false;
(deployment_halt, gas_after, state_gas_after, state_spill_after) = charge_state_gas(
gas_after,
state_gas_after,
state_spill_after,
state_deposit,
);
if deployment_halt then {
gas_after = GAS_ZERO;
let exceptional = exceptional_state(
state_gas_after,
state_spill_after,
state_gas_reservoir,
OutOfGas,
);
state_gas_after = exceptional.state_gas_remaining;
state_spill_after = exceptional.state_gas_spilled;
status_after = exceptional.status
};
let deployment_succeeded = frame_succeeded(status_after);
if deployment_succeeded then {
let stored_code = code_db_intern_output(deployed_output);
k_deploy_code(new_addr, stored_code)
}
} else if profile.fork < Homestead then {
/* Frontier consumes the remaining gas and keeps the
* created account with empty code. */
gas_after = GAS_ZERO;
k_deploy_code(new_addr, EMPTY_CODE_SLICE)
…function tx_frame_gas_snapshot(initial, execution, state, state_delta) = {
let limit = initial.admitted_limit;
let regular = initial.regular_limit;
if execution <= limit then {
let room = limit - execution;
if state <= room then {
let (remaining as 'remaining) = execution + state;
let spent = limit - remaining;
let raw_state_used : transaction_state_gas_delta = initial.intrinsic_state + state_delta;
if raw_state_used <= 0 then {
if spent <= regular then {
tx_frame_gas_snapshot_fields(limit, regular, initial.calldata_floor, remaining, 0)
} else {
fatal_error(ExecutionInvalid)
}
} else {
let positive_state_used : transaction_state_gas_used = raw_state_used;
if positive_state_used <= spent then {
/* The comparison is the semantic boundary that narrows the
signed transaction delta back into the admitted reservoir.
Name that refinement explicitly so optimized lowering can
convert directly to the concrete gas width instead of first
materializing the wider positive range of the signed sum. */
let (bounded_state_used as 'bounded_state_used) : range(0, 'limit - 'remaining) = positive_state_used;
if spent - bounded_state_used <= regular then {
tx_frame_gas_snapshot_fields(
limit,
regular,
initial.calldata_floor,
remaining,
bounded_state_used,
)
} else {
fatal_error(ExecutionInvalid)
}
} else {
fatal_error(ExecutionInvalid)
}
}
} else {
fatal_error(ExecutionInvalid)
}
} else {
fatal_error(ExecutionInvalid)
}
}function word_nonzero(w) = {
let is_zero = word_is_zero(w);
not_bool(is_zero)
}let EMPTY_CALLDATA : CalldataSlice = InputCalldata(EMPTY_STATELESS_INPUT_SLICE)let EMPTY_CODE : Code = analyzed_code(EMPTY_CODE_SLICE, EMPTY_JUMP_TABLE)let EMPTY_OUTPUT_SLICE : OutputSliceFields(0, 0) = output_slice(0, 0)let FRAME_STATE_GAS_DELTA_ZERO : frame_state_gas_delta = 0let GAS_REFUND_ZERO : gas_refund = 0let GAS_ZERO : int(0) = 0let STATE_GAS_SPILL_ZERO : int(0) = 0let STATE_GAS_ZERO : int(0) = 0Calldata is either the immutable top-level transaction input or a frozen range of the suspended caller's memory. The variants state the only two protocol-valid provenances instead of exposing the host's region enum.
union CalldataSlice = {
/* the immutable top-level transaction input */
InputCalldata : StatelessInputSlice,
/* a frozen range of the suspended caller's memory */
MemoryCalldata : EvmMemorySlice,
}Existential executable-code value whose concrete byte address and length
remain correlated inside CodeFields.
type Code = {
'off 'len,
code_region_valid_range('off, 'len) & code_valid_length('len).
CodeFields('off, 'len)
}Per-frame execution status: running, halted normally, or exceptionally halted.
union FrameStatus = {
/* mid-execution */
Running : unit,
/* halted normally (YP §9.4.4) */
Halted : HaltKind,
/* halted exceptionally: all frame gas consumed, effects void */
Exceptional : ExceptionKind
}A frame-output range with its coordinate and length packed existentially.
type OutputSlice = {
'off 'len,
output_region_valid_range('off, 'len).
OutputSliceFields('off, 'len)
}A 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)Net state gas consumed by one execution frame. A credit can make this
negative until transaction settlement clamps the block-level value at
zero. The bounds follow from subtracting two live uint64 counters and
adding at most one EIP-7825 regular-pool spill.
type frame_state_gas_delta = range(
-(2 ^ 64 - 1),
(2 ^ 64 - 1) + transaction_execution_gas_limit_value,
)Available gas in a running EVM frame. Every admitted transaction gas
limit originates in the execution payload's SSZ uint64 gas-limit
domain, and child frames can only receive gas from their parent.
type gas = range(0, 2 ^ 64 - 1)The signed transaction refund accumulator before capping.
type gas_refund = range(
-gas_refund_bound,
gas_refund_bound,
)Amsterdam's per-frame state-gas reservoir. The transaction's total gas
allowance remains in the execution payload's uint64 domain; only the
regular-gas portion and state-gas spill into that portion are capped by
EIP-7825.
type state_gas = range(0, 2 ^ 64 - 1)Execution gas temporarily consumed by Amsterdam state charges. EIP-8037 draws spill only from the regular-gas pool, which is capped by EIP-7825.
type state_gas_spill = range(0, transaction_execution_gas_limit_value)function run_transaction_frame¶
function run_transaction_frame(tx, v, upfront, authorizations) = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
if profile.fork >= Amsterdam then {
run_amsterdam_transaction_frame(tx, v, upfront, authorizations)
} else {
run_legacy_transaction_frame(tx, v)
}
}function run_amsterdam_transaction_frame(tx, v, upfront, authorizations) = {
let (
entered_gas,
entered_state_gas,
entered_state_spill,
entered_refund,
entered_stack,
entered_memory_base,
entered_memory,
) = enter_transaction_frame(v);
var gas_after : gas = entered_gas;
var state_gas_after : state_gas = entered_state_gas;
var state_spill_after : state_gas_spill = entered_state_spill;
var refund_after : gas_refund = entered_refund;
var status_after : FrameStatus = Running();
var output_after : OutputSlice = EMPTY_OUTPUT_SLICE;
let initial_gas = v.gas;
k_journal_checkpoint();
let preparation_reservoir = state_gas_after;
let current_target =
if tx.is_create then k_create_addr(v.sender, v.nonce_before) else tx.recipient;
authorization_tracker_reset(authorizations.count);
let transfers_value = word_nonzero(tx.value);
var preparation_ready : bool = false;
(preparation_ready, gas_after, state_gas_after, state_spill_after) = process_amsterdam_auth_cursor(
authorizations,
authorizations.count,
v.sender,
current_target,
transfers_value,
gas_after,
state_gas_after,
state_spill_after,
);
var authorization_state_gas : frame_state_gas_delta = FRAME_STATE_GAS_DELTA_ZERO;
var delegated : bool = false;
var execution_reservoir : state_gas = state_gas_after;
var prepared_code_address : address = current_target;
var prepared_code : Code = EMPTY_CODE;
var prepared_calldata : CalldataSlice = EMPTY_CALLDATA;
if preparation_ready then {
authorization_state_gas = frame_state_gas_used(preparation_reservoir, state_gas_after, state_spill_after);
execution_reservoir = state_gas_after;
state_spill_after = STATE_GAS_SPILL_ZERO;
let (preparation, prepared_gas, prepared_state_gas, prepared_state_spill, _, code_address, code, calldata) = prepare_amsterdam_transaction_dispatch(
tx,
v,
upfront,
gas_after,
state_gas_after,
state_spill_after,
);
gas_after = prepared_gas;
state_gas_after = prepared_state_gas;
state_spill_after = prepared_state_spill;
preparation_ready = preparation.ready;
delegated = preparation.delegated;
prepared_code_address = code_address;
prepared_code = code;
prepared_calldata = calldata
};
let preparation_failed = not_bool(preparation_ready);
if preparation_failed then {
k_journal_revert();
state_gas_after = preparation_reservoir;
state_spill_after = STATE_GAS_SPILL_ZERO;
return struct {
success = false,
gas = tx_frame_gas_snapshot(initial_gas, GAS_ZERO, STATE_GAS_ZERO, FRAME_STATE_GAS_DELTA_ZERO),
refund = GAS_REFUND_ZERO,
}
};
k_journal_checkpoint();
if tx.is_create then {
(gas_after, state_gas_after, state_spill_after, refund_after, status_after, output_after) = run_create_transaction_frame(
tx,
v.sender,
v.nonce_before,
gas_after,
state_gas_after,
state_spill_after,
refund_after,
entered_stack,
entered_memory_base,
entered_memory,
prepared_code,
prepared_calldata,
execution_reservoir,
)
} else {
(gas_after, state_gas_after, state_spill_after, refund_after, status_after, output_after) = run_call_transaction_frame(
tx,
v.sender,
delegated,
gas_after,
state_gas_after,
state_spill_after,
refund_after,
entered_stack,
entered_memory_base,
entered_memory,
prepared_code_address,
prepared_code,
prepared_calldata,
execution_reservoir,
)
};
let success = frame_succeeded(status_after);
let failed = not_bool(success);
if failed then {
k_journal_revert()
} else {
k_journal_commit()
};
…function run_legacy_transaction_frame(tx, v) = {
let initial_gas = v.gas;
k_journal_checkpoint();
let (
initial_execution_gas,
initial_state_gas,
initial_state_spill,
initial_refund,
initial_stack,
initial_memory_base,
initial_memory_height,
) = enter_transaction_frame(v);
let state_gas_reservoir = initial_state_gas;
let (gas_after, state_gas_after, state_spill_after, refund_after, status_after, _) =
if tx.is_create
then run_create_transaction_frame(
tx,
v.sender,
v.nonce_before,
initial_execution_gas,
initial_state_gas,
initial_state_spill,
initial_refund,
initial_stack,
initial_memory_base,
initial_memory_height,
EMPTY_CODE,
EMPTY_CALLDATA,
state_gas_reservoir,
)
else run_call_transaction_frame(
tx,
v.sender,
false,
initial_execution_gas,
initial_state_gas,
initial_state_spill,
initial_refund,
initial_stack,
initial_memory_base,
initial_memory_height,
tx.recipient,
EMPTY_CODE,
EMPTY_CALLDATA,
state_gas_reservoir,
);
let success = frame_succeeded(status_after);
let failed = not_bool(success);
if failed then {
k_journal_revert()
} else {
k_journal_commit()
};
let state_delta = frame_state_gas_used(state_gas_reservoir, state_gas_after, state_spill_after);
let retained_refund =
if success then refund_after else GAS_REFUND_ZERO;
struct {
success = success,
gas = tx_frame_gas_snapshot(initial_gas, gas_after, state_gas_after, state_delta),
refund = retained_refund,
}
}function run_transaction_frame(tx, v, upfront, authorizations) = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
if profile.fork >= Amsterdam then {
run_amsterdam_transaction_frame(tx, v, upfront, authorizations)
} else {
run_legacy_transaction_frame(tx, v)
}
}EIP-7954 code/initcode size bump (65536/131072).
let Amsterdam : int(amsterdam_fork_value) = sizeof(amsterdam_fork_value)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_PROFILEfunction remaining_gas_after_refund¶
function remaining_gas_after_refund(_limit, total, remaining, cap) = {
let refund =
if total <= 0 then 0 else if total <= cap then total else cap;
remaining + refund
}function remaining_gas_after_refund(_limit, total, remaining, cap) = {
let refund =
if total <= 0 then 0 else if total <= cap then total else cap;
remaining + refund
}function settle_transaction¶
function settle_transaction(tx, v, authorization_refund, fr) = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
/* Refund cap: gas_used / 2 before London, gas_used / 5 after EIP-3529. */
let gas_snapshot = fr.gas;
let gas_limit = gas_snapshot.admitted_limit;
let gas_left = gas_snapshot.remaining;
let gas_used0 = gas_limit - gas_left;
let refund_quotient = profile.refund_divisor;
let refund_cap = gas_used0 / refund_quotient;
let total_refund = authorization_refund + fr.refund;
let gas_left = remaining_gas_after_refund(gas_limit, total_refund, gas_left, refund_cap);
let gas_used1 = gas_limit - gas_left;
/* EIP-7623 (Prague+): a tx pays at least the calldata floor 21000 + 10*tokens */
let floor =
if profile.fork >= Prague then gas_snapshot.calldata_floor else 0;
let gas_used : range(0, 'limit) =
if gas_used1 < floor then floor else gas_used1;
let gas_left = gas_limit - gas_used;
let tx_state_gas = gas_snapshot.state_used;
let unrefunded_execution_gas : range(0, 'regular) = gas_limit - gas_snapshot.remaining - tx_state_gas;
/* Retain the unrefunded regular-gas contribution for Amsterdam block
accounting. Earlier forks accumulate `gas_used` instead; keeping this
auxiliary field under the regular allowance in every receipt preserves
one uniform dependent type. */
let execution_gas : range(0, 'regular) =
if unrefunded_execution_gas < floor then floor else unrefunded_execution_gas;
/* This auxiliary field retains the state-reservoir contribution even on
earlier profiles, where it is necessarily zero. Keeping the conserved
split in the receipt type proves that its cumulative gas can never
exceed the two block reservoirs. */
let state_gas : range(0, 'limit) = tx_state_gas;
/* return unused gas to sender; pay coinbase the priority fee */
let sender_refund = validated_word_product(v.gas_price, gas_left);
k_add_balance(v.sender, sender_refund);
let coinbase = k_coinbase();
let priority_payment = validated_word_product(v.priority_fee, gas_used);
k_add_balance(coinbase, priority_payment);
k_tx_merge();
let logs = read_logs();
let (gas_used_value as 'gas_used) = gas_used;
let (execution_gas_value as 'execution_gas) = execution_gas;
let (state_gas_value as 'state_gas) = state_gas;
if gas_used_value <= execution_gas_value + state_gas_value then {
receipt_within(
gas_limit,
gas_snapshot.regular_limit,
tx.tx_type,
fr.success,
gas_used_value,
execution_gas_value,
state_gas_value,
logs,
)
} else {
/* A violation would mean the frame snapshot failed to conserve the
two admitted gas reservoirs. This is the one transaction boundary
that validates that internal invariant; block and receipt
accumulation consume its dependent proof without rechecking it. */
fatal_error(ExecutionInvalid)
}
}function fatal_error(_reason) = exit(())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 })
}
}The block's fee recipient (COINBASE).
function k_coinbase() -> address = k_header.fee_recipientThe transaction-end merge: drains the transaction overlays into the block layer, applying the fork-specific selfdestruct clearing rule, storage-clear generations, and recording nonce/balance/code/storage changes for the EIP-7928 block access list. Lifecycle flags reset as rows merge.
function k_tx_merge() -> unit = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
let semantics = transaction_merge_semantics(profile.fork);
var more : bool = true;
/* Each pop advances a cursor over a protocol-sized host table. */
while more termination_measure(2 ^ 64) do {
let popped_account = acct_tx_pop();
match popped_account {
AcctTxPopRow(e) => {
var curr : Account = e.value.curr;
let deleted = account_deleted_at_tx_end(semantics, curr);
if deleted then {
let cleared_account =
if semantics.preserve_selfdestruct_balance
then account_clear_preserving_balance(curr)
else account_delete(curr);
curr = cleared_account;
/* The account's active transaction generation belongs to
the deleted incarnation. Retire it before the storage
drain so its writes cannot be reinserted after the
block-layer clear. */
storage_tx_clear(e.addr)
};
let original_storage_retained = not_bool(e.value.orig.storage_cleared);
if deleted | (curr.storage_cleared & original_storage_retained) then {
storage_block_clear(e.addr)
};
if curr.info.nonce != e.value.orig.info.nonce then {
bal_nonce_change(k_current_transaction_epoch, e.addr, curr.info.nonce)
};
if curr.info.balance != e.value.orig.info.balance then {
bal_balance_change(k_current_transaction_epoch, e.addr, curr.info.balance)
};
if curr.info.code_hash != e.value.orig.info.code_hash then {
bal_code_change(k_current_transaction_epoch, e.addr, curr.info.code_hash)
};
curr = { curr with created = false, selfdestructed = false };
let changed = account_changed(curr, e.value.orig);
if changed then {
acct_block_write(struct { addr = e.addr, value = struct { curr = curr, orig = e.value.orig } })
}
},
AcctTxPopExhausted(_) => more = false,
}
};
more = true;
/* Each pop advances a cursor over a protocol-sized host table. */
while more termination_measure(2 ^ 64) do {
let popped_storage = storage_tx_pop();
match popped_storage {
StorageTxPopRow(e) => {
let account = acct_block_get(e.key.addr);
if account.found then {
let acc = account.account;
if acc.present & e.value.curr != e.value.orig then {
bal_storage_change(k_current_transaction_epoch, e.key.addr, e.key.slot, e.value.curr);
storage_block_put(e)
}
}
},
StorageTxPopExhausted(_) => more = false,
}
};
storage_tx_reset();
acct_tx_reset()
}Captures the current transaction's consecutive retained log range.
function read_logs() -> LogSeriesRef = {
let start = logs_tx_start();
let count = logs_tx_count();
struct { start = start, count = count }
}function receipt_within(limit, regular_limit, tx_type, success, gas_used, execution_gas, state_gas, logs) =
receipt_fields(limit, regular_limit, tx_type, success, gas_used, execution_gas, state_gas, logs)function remaining_gas_after_refund(_limit, total, remaining, cap) = {
let refund =
if total <= 0 then 0 else if total <= cap then total else cap;
remaining + refund
}function settle_transaction(tx, v, authorization_refund, fr) = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
/* Refund cap: gas_used / 2 before London, gas_used / 5 after EIP-3529. */
let gas_snapshot = fr.gas;
let gas_limit = gas_snapshot.admitted_limit;
let gas_left = gas_snapshot.remaining;
let gas_used0 = gas_limit - gas_left;
let refund_quotient = profile.refund_divisor;
let refund_cap = gas_used0 / refund_quotient;
let total_refund = authorization_refund + fr.refund;
let gas_left = remaining_gas_after_refund(gas_limit, total_refund, gas_left, refund_cap);
let gas_used1 = gas_limit - gas_left;
/* EIP-7623 (Prague+): a tx pays at least the calldata floor 21000 + 10*tokens */
let floor =
if profile.fork >= Prague then gas_snapshot.calldata_floor else 0;
let gas_used : range(0, 'limit) =
if gas_used1 < floor then floor else gas_used1;
let gas_left = gas_limit - gas_used;
let tx_state_gas = gas_snapshot.state_used;
let unrefunded_execution_gas : range(0, 'regular) = gas_limit - gas_snapshot.remaining - tx_state_gas;
/* Retain the unrefunded regular-gas contribution for Amsterdam block
accounting. Earlier forks accumulate `gas_used` instead; keeping this
auxiliary field under the regular allowance in every receipt preserves
one uniform dependent type. */
let execution_gas : range(0, 'regular) =
if unrefunded_execution_gas < floor then floor else unrefunded_execution_gas;
/* This auxiliary field retains the state-reservoir contribution even on
earlier profiles, where it is necessarily zero. Keeping the conserved
split in the receipt type proves that its cumulative gas can never
exceed the two block reservoirs. */
let state_gas : range(0, 'limit) = tx_state_gas;
/* return unused gas to sender; pay coinbase the priority fee */
let sender_refund = validated_word_product(v.gas_price, gas_left);
k_add_balance(v.sender, sender_refund);
let coinbase = k_coinbase();
let priority_payment = validated_word_product(v.priority_fee, gas_used);
k_add_balance(coinbase, priority_payment);
k_tx_merge();
let logs = read_logs();
let (gas_used_value as 'gas_used) = gas_used;
let (execution_gas_value as 'execution_gas) = execution_gas;
let (state_gas_value as 'state_gas) = state_gas;
if gas_used_value <= execution_gas_value + state_gas_value then {
receipt_within(
gas_limit,
gas_snapshot.regular_limit,
tx.tx_type,
fr.success,
gas_used_value,
execution_gas_value,
state_gas_value,
logs,
)
} else {
/* A violation would mean the frame snapshot failed to conserve the
two admitted gas reservoirs. This is the one transaction boundary
that validates that internal invariant; block and receipt
accumulation consume its dependent proof without rechecking it. */
fatal_error(ExecutionInvalid)
}
}function validated_word_product(value, factor) = {
let product = value * factor;
if product < sizeof(word_modulus) then {
protocol_word(product)
} else {
fatal_error(ExecutionInvalid)
}
}EIP-7623 calldata floor; BLS precompiles 0x0b-0x11.
let Prague : int(prague_fork_value) = sizeof(prague_fork_value)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_PROFILEThe 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 aggregate EIP-7702 refund admitted by one transaction.
type authorization_refund = range(
0,
authorization_refund_per_item * transaction_length_bound,
)Available gas in a running EVM frame. Every admitted transaction gas
limit originates in the execution payload's SSZ uint64 gas-limit
domain, and child frames can only receive gas from their parent.
type gas = range(0, 2 ^ 64 - 1)Amsterdam's per-frame state-gas reservoir. The transaction's total gas
allowance remains in the execution payload's uint64 domain; only the
regular-gas portion and state-gas spill into that portion are capped by
EIP-7825.
type state_gas = range(0, 2 ^ 64 - 1)function process_transaction¶
function process_transaction(tx, allowance) = {
k_tx_reset();
let validity = check_transaction_validity(tx, allowance);
let authorizations = prepare_authorizations(tx.authorizations);
let environment = tx_env(tx.sender, validity.gas_price, tx.blob_hashes);
k_set_tx(environment);
let upfront = apply_transaction_upfront_effects(tx, validity, authorizations);
let frame_result = run_transaction_frame(tx, validity, upfront, authorizations);
let receipt = settle_transaction(tx, validity, upfront.authorization_refund, frame_result);
receipt
}The upfront effects, taken before the execution snapshot so they persist across a dispatched-frame revert: charge the full execution/state gas limit and the EIP-4844 blob-gas fee, bump the sender nonce, and prewarm the transaction access set. Before Amsterdam, EIP-7702 authorizations are also applied here; Amsterdam applies them inside the separately reversible top-frame preparation phase.
function apply_transaction_upfront_effects(
tx : Transaction,
v : TxValidity,
authorizations : PreparedAuthorizationList,
) -> (
TxUpfrontResult
) = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
let initial_gas = v.gas;
let create_target_prestate_empty : bool =
if (profile.fork >= Amsterdam) & tx.is_create then {
let create_target = k_create_addr(v.sender, v.nonce_before);
let target_exists = k_account_exists(create_target);
not_bool(target_exists)
} else {
false
};
/* upfront gas + nonce bump (persist across revert; taken before the snapshot) */
let gas_debit = validated_word_product(v.gas_price, initial_gas.admitted_limit);
k_sub_balance(v.sender, gas_debit);
/* EIP-4844: burn the blob fee upfront (blob_gas_used * blob_gas_price), no refund */
let has_blob_fee = word_nonzero(v.blob_fee);
if has_blob_fee then {
k_sub_balance(v.sender, v.blob_fee)
};
k_bump_nonce(v.sender);
prewarm(tx);
let authorization_refund =
if profile.fork < Amsterdam then process_auth_list(authorizations) else 0;
struct { authorization_refund = authorization_refund, create_target_prestate_empty = create_target_prestate_empty }
}function check_transaction_validity(tx, allowance) = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
let tx_semantics = tx_type_semantics(tx.tx_type);
/* Sender authentication: `v` selects the recovered signer, which must be
the address derived from the witnessed public key. A forged key or bad
v/r/s makes the whole BLOCK invalid, regardless of the validity verdict
below. */
let parity = tx_signature_parity(k_chain_id, tx_semantics.signature, tx.sig_v);
let authenticated = tx_auth_valid(tx.sender, tx.signing_hash, parity, tx.sig_r, tx.sig_s);
let invalid_signature = not_bool(authenticated);
if invalid_signature then {
fatal_error(InvalidSignature)
};
let gas_limit = allowance.total;
/* effective gas price (EIP-1559) */
let (eff_gas_price, eff_priority_fee) = eff_gas_price_for(k_header.base_fee, tx.max_fee, tx.max_priority_fee);
let sender = tx.sender;
let input = tx.input_src;
let input_len = input.len;
let nonce_before = k_get_nonce(sender);
let costs = transaction_costs(profile, tx, gas_limit, k_header.excess_blob_gas);
let expected_nonce = word_of_account_nonce(nonce_before);
if tx.nonce != expected_nonce then {
fatal_error(ExecutionInvalid)
};
/* Transaction validity: an invalid tx is REJECTED with no state change at all
(no nonce bump, no gas charge). The upfront-balance test uses the fee CAPS
(max_fee_per_gas, max_fee_per_blob_gas), not the effective prices. */
let (sender_deleg, _) = k_deleg_target(sender);
/* EIP-4844 / EIP-7691 / EIP-7594: the profile-indexed RLP decoder has
already bounded the count and validated each `0x01` version byte while
consuming the hash-list cursor. A type-3 transaction must still carry
at least one blob and must not create a contract. */
if tx_semantics.blob then {
if (profile.fork < Cancun) | (tx.blob_hashes.count == 0) | tx.is_create then {
fatal_error(ExecutionInvalid)
}
};
/* EIP-7623 (Prague+): the calldata floor is ALSO a validity bound -- a tx whose
gas limit cannot cover 21000 + 10*tokens is rejected outright. */
if (profile.fork >= Prague) & (gas_limit < costs.calldata_floor) then {
fatal_error(ExecutionInvalid)
};
let sender_balance = k_get_balance(sender);
let upfront_affordable = word_ule(costs.upfront, sender_balance);
let insufficient_balance = not_bool(upfront_affordable);
if insufficient_balance then {
fatal_error(ExecutionInvalid)
};
let sender_code_key = k_code_key(sender);
let valid_sender_code = (sender_code_key == KECCAK_EMPTY) | sender_deleg;
let invalid_sender_code = not_bool(valid_sender_code);
if invalid_sender_code then {
fatal_error(ExecutionInvalid)
};
if allowance.regular < costs.calldata_floor then {
fatal_error(ExecutionInvalid)
};
let base_fee_affordable = word_ule(k_header.base_fee, tx.max_fee);
let base_fee_exceeds_cap = not_bool(base_fee_affordable);
if base_fee_exceeds_cap then {
fatal_error(ExecutionInvalid)
};
let valid_initcode_size = initcode_size_allowed(input_len);
let invalid_initcode_size = not_bool(valid_initcode_size);
if tx.is_create & invalid_initcode_size then {
fatal_error(ExecutionInvalid)
};
let valid_priority_fee = word_ule(tx.max_priority_fee, tx.max_fee);
let invalid_priority_fee = not_bool(valid_priority_fee);
if invalid_priority_fee then {
fatal_error(ExecutionInvalid)
};
if profile.fork < tx_semantics.minimum_fork then {
fatal_error(ExecutionInvalid)
};
if tx_semantics.set_code & tx.is_create then {
fatal_error(ExecutionInvalid)
};
let authorizations = tx.authorizations;
if tx_semantics.set_code & (authorizations.count == 0) then {
fatal_error(ExecutionInvalid)
};
let typed_signature = tx_semantics.signature == TypedSignature;
if typed_signature & (tx.chain_id != k_chain_id) then {
fatal_error(ExecutionInvalid)
};
if nonce_before == sizeof(account_nonce_bound) then {
fatal_error(ExecutionInvalid)
};
let initial_gas = transaction_initial_gas(
allowance,
costs.intrinsic_execution,
costs.intrinsic_state,
costs.calldata_floor,
);
tx_validity_fields(sender, nonce_before, initial_gas, costs.blob_fee, eff_gas_price, eff_priority_fee)
}Installs the per-transaction environment.
function k_set_tx(env : TxEnv) -> unit = k_tx = envResets every per-transaction store and the state journal.
function k_tx_reset() -> unit = {
/* Storage reset consumes the per-account transaction worklists owned by
the account table, so it must precede the account reset. */
storage_tx_reset();
acct_tx_reset();
warm_reset(k_current_transaction_epoch);
transient_reset();
logs_tx_reset();
state_journal_reset()
}Materializes a transaction's authorizations only after successful validity has made this narrowing guard unreachable for protocol-valid input.
function prepare_authorizations(authorizations : AuthorizationListRef) -> PreparedAuthorizationList = {
if authorizations.count <= sizeof(prepared_authorization_count_bound) then {
let encoded : StatelessInputSlice = authorizations.encoded;
let entries = prepare_authorization_entries(encoded, authorizations.count);
struct { entries = entries, count = authorizations.count }
} else {
fatal_error(ExecutionInvalid)
}
}function process_transaction(tx, allowance) = {
k_tx_reset();
let validity = check_transaction_validity(tx, allowance);
let authorizations = prepare_authorizations(tx.authorizations);
let environment = tx_env(tx.sender, validity.gas_price, tx.blob_hashes);
k_set_tx(environment);
let upfront = apply_transaction_upfront_effects(tx, validity, authorizations);
let frame_result = run_transaction_frame(tx, validity, upfront, authorizations);
let receipt = settle_transaction(tx, validity, upfront.authorization_refund, frame_result);
receipt
}function run_transaction_frame(tx, v, upfront, authorizations) = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
if profile.fork >= Amsterdam then {
run_amsterdam_transaction_frame(tx, v, upfront, authorizations)
} else {
run_legacy_transaction_frame(tx, v)
}
}function settle_transaction(tx, v, authorization_refund, fr) = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
/* Refund cap: gas_used / 2 before London, gas_used / 5 after EIP-3529. */
let gas_snapshot = fr.gas;
let gas_limit = gas_snapshot.admitted_limit;
let gas_left = gas_snapshot.remaining;
let gas_used0 = gas_limit - gas_left;
let refund_quotient = profile.refund_divisor;
let refund_cap = gas_used0 / refund_quotient;
let total_refund = authorization_refund + fr.refund;
let gas_left = remaining_gas_after_refund(gas_limit, total_refund, gas_left, refund_cap);
let gas_used1 = gas_limit - gas_left;
/* EIP-7623 (Prague+): a tx pays at least the calldata floor 21000 + 10*tokens */
let floor =
if profile.fork >= Prague then gas_snapshot.calldata_floor else 0;
let gas_used : range(0, 'limit) =
if gas_used1 < floor then floor else gas_used1;
let gas_left = gas_limit - gas_used;
let tx_state_gas = gas_snapshot.state_used;
let unrefunded_execution_gas : range(0, 'regular) = gas_limit - gas_snapshot.remaining - tx_state_gas;
/* Retain the unrefunded regular-gas contribution for Amsterdam block
accounting. Earlier forks accumulate `gas_used` instead; keeping this
auxiliary field under the regular allowance in every receipt preserves
one uniform dependent type. */
let execution_gas : range(0, 'regular) =
if unrefunded_execution_gas < floor then floor else unrefunded_execution_gas;
/* This auxiliary field retains the state-reservoir contribution even on
earlier profiles, where it is necessarily zero. Keeping the conserved
split in the receipt type proves that its cumulative gas can never
exceed the two block reservoirs. */
let state_gas : range(0, 'limit) = tx_state_gas;
/* return unused gas to sender; pay coinbase the priority fee */
let sender_refund = validated_word_product(v.gas_price, gas_left);
k_add_balance(v.sender, sender_refund);
let coinbase = k_coinbase();
let priority_payment = validated_word_product(v.priority_fee, gas_used);
k_add_balance(coinbase, priority_payment);
k_tx_merge();
let logs = read_logs();
let (gas_used_value as 'gas_used) = gas_used;
let (execution_gas_value as 'execution_gas) = execution_gas;
let (state_gas_value as 'state_gas) = state_gas;
if gas_used_value <= execution_gas_value + state_gas_value then {
receipt_within(
gas_limit,
gas_snapshot.regular_limit,
tx.tx_type,
fr.success,
gas_used_value,
execution_gas_value,
state_gas_value,
logs,
)
} else {
/* A violation would mean the frame snapshot failed to conserve the
two admitted gas reservoirs. This is the one transaction boundary
that validates that internal invariant; block and receipt
accumulation consume its dependent proof without rechecking it. */
fatal_error(ExecutionInvalid)
}
}function tx_env(origin, gas_price, blob_hashes) =
struct { origin = origin, gas_price = gas_price, blob_hashes = blob_hashes }The aggregate EIP-7702 refund admitted by one transaction.
type authorization_refund = range(
0,
authorization_refund_per_item * transaction_length_bound,
)