pub struct VM<'a> {Show 32 fields
pub call_frames: Vec<CallFrame>,
pub current_call_frame: CallFrame,
pub env: Environment,
pub substate: Substate,
pub db: &'a mut GeneralizedDatabase,
pub tx: &'a Transaction,
pub hooks: Vec<Rc<RefCell<dyn Hook>>>,
pub storage_original_values: FxHashMap<Address, FxHashMap<H256, U256>>,
pub tracer: LevmCallTracer,
pub opcode_tracer: LevmOpcodeTracer,
pub validation_observer: ValidationObserver,
pub debug_mode: DebugMode,
pub stack_pool: Vec<Stack>,
pub vm_type: VMType,
pub frame_tx_context: Option<FrameTxContext>,
pub state_gas_used: i64,
pub state_gas_reservoir: u64,
pub state_gas_reservoir_initial: u64,
pub state_gas_spill: u64,
pub cost_per_state_byte: u64,
pub state_gas_new_account: u64,
pub state_gas_storage_set: u64,
pub state_gas_auth_base: u64,
pub intrinsic_state_gas: u64,
pub value_new_account_charged: bool,
pub prep_baseline_state_gas: i64,
pub prep_baseline_reservoir: u64,
pub prep_baseline_state_gas_spill: u64,
pub prep_region_backup_marker: PrepareRegionBackupMarker,
pub pending_prep_oog: bool,
pub crypto: &'a dyn Crypto,
pub stateless_validator: Option<&'a dyn StatelessValidator>,
/* private fields */
}Fields§
§call_frames: Vec<CallFrame>Stack of parent call frames (for nested calls).
current_call_frame: CallFrameThe currently executing call frame.
env: EnvironmentBlock and transaction environment.
substate: SubstateExecution substate (accessed addresses, logs, refunds, etc.).
db: &'a mut GeneralizedDatabaseDatabase for reading/writing account state.
tx: &'a TransactionThe transaction being executed. Borrowed for the VM’s lifetime (the caller owns it for at least that long), avoiding a per-tx deep clone of the access/authorization lists.
hooks: Vec<Rc<RefCell<dyn Hook>>>Execution hooks for tracing and debugging.
storage_original_values: FxHashMap<Address, FxHashMap<H256, U256>>Original storage values before transaction (for SSTORE gas calculation), keyed first by account to avoid hashing the full tuple on each access.
tracer: LevmCallTracerCall tracer for execution tracing.
opcode_tracer: LevmOpcodeTracerOpcode (EIP-3155) tracer. Disabled by default; zero overhead when inactive.
validation_observer: ValidationObserverEIP-8141 mempool validation-trace observer. Disabled by default; active
only during simulate_frame_validation_prefix. Read only behind
if self.validation_observer.active, so an inactive observer adds one
branch to the dispatch loop and nothing more (mirrors opcode_tracer).
debug_mode: DebugModeDebug mode for development diagnostics.
stack_pool: Vec<Stack>Pool of reusable stacks to reduce allocations.
vm_type: VMTypeVM type (L1 or L2 with fee config).
frame_tx_context: Option<FrameTxContext>Frame transaction context (EIP-8141). Set when executing a frame tx.
state_gas_used: i64EIP-8037: Accumulated state gas for this transaction (Amsterdam+). Signed: goes negative when inline refunds exceed gross charges in the local frame (e.g. SSTORE 0→x→0 restoration matching an ancestor’s charge).
state_gas_reservoir: u64EIP-8037: State gas reservoir pre-funded from excess gas_limit (Amsterdam+).
state_gas_reservoir_initial: u64EIP-8037: Initial reservoir at tx start (before any execution). Captured in add_intrinsic_gas so block-dimensional regular gas can be computed independently of mid-tx reservoir activity (auth refunds, SSTORE credits).
state_gas_spill: u64EIP-8037: Cumulative state gas that spilled to regular gas during execution (when reservoir was insufficient). Subtracted when computing dimensional regular gas for block accounting — EELS charge_state_gas spills don’t increment regular_gas_used.
cost_per_state_byte: u64EIP-8037: Dynamic cost per state byte (computed from block_gas_limit, Amsterdam+).
state_gas_new_account: u64EIP-8037: State gas for new account creation (STATE_BYTES_PER_NEW_ACCOUNT * cost_per_state_byte).
state_gas_storage_set: u64EIP-8037: State gas for storage slot creation (STATE_BYTES_PER_STORAGE_SET * cost_per_state_byte).
state_gas_auth_base: u64EIP-8037: State gas for the 23-byte EIP-7702 delegation indicator
(STATE_BYTES_PER_AUTH_BASE * cost_per_state_byte). Charged in-region by
eip7702_set_access_code (EELS set_delegation) once per authority when a
net-new delegation indicator is written, and never credited back.
intrinsic_state_gas: u64EIP-8037: intrinsic state gas (tx_env.intrinsic_state_gas in EELS). Captured at
add_intrinsic_gas time. ethrex lumps intrinsic + execution into state_gas_used,
so on top-level error this field is what we leave behind when refunding the
execution portion to the reservoir — block accounting then bills the intrinsic
(matches EELS tx_state_gas = intrinsic_state_gas + tx_output.state_gas_used).
value_new_account_charged: boolEIP-8037: set by prepare_execution when the in-region value-to-not-alive
NEW_ACCOUNT charge fires (a value-bearing call to a not-yet-alive
recipient). Consumed once, at the top of run_execution, to decide
whether an Amsterdam precompile-halt must roll the charge back (the
recipient never materializes on halt).
prep_baseline_state_gas: i64EIP-8037: current_call_frame.state_gas_used_at_entry captured by
enter_prepare_region right before the atomic prepare region (EIP-7702
auth + prepare-dispatch charges) begins. fail_prepare_region rewinds the
frame’s state_gas_used back to this pre-region baseline on an internal OOG,
mirroring EELS restore_tx_state(prep_snapshot).
prep_baseline_reservoir: u64EIP-8037: state_gas_reservoir captured by enter_prepare_region. The
set_delegation auth lock-in zeroes the frame spill, so fail_prepare_region
cannot reconstruct the pre-region reservoir from refill_frame_state_gas
arithmetic; it restores this captured value directly (EELS
message.state_gas_reservoir = prep_reservoir).
prep_baseline_state_gas_spill: u64EIP-8037: cumulative state_gas_spill captured by enter_prepare_region, so
fail_prepare_region restores block-accounting spill to its pre-region value
(the region’s spilled state gas is fully rolled back / burned as regular).
prep_region_backup_marker: PrepareRegionBackupMarkerEIP-8037: pre-region key-space snapshot of call_frame_backup, recorded by
enter_prepare_region. Lets fail_prepare_region revert only region-added
writes, leaving the sender nonce-bump / fee-deduction entries intact.
pending_prep_oog: boolEIP-8037: set by fail_prepare_region when an internal OOG rolls back the
atomic prepare region. Consumed by run_execution, which turns it into a
full-gas revert ContextResult (mirrors EELS depth-0
except ExceptionalHalt: evm.regular_gas_used += evm.gas_left; evm.gas_left = 0)
instead of a tx-level rejection Err that would wrongly invalidate the block.
crypto: &'a dyn CryptoCrypto provider for cryptographic operations.
stateless_validator: Option<&'a dyn StatelessValidator>Optional stateless validator for the EXECUTE precompile.
None on VMs that never dispatch EXECUTE (guest program, witness
generation, the stateless validator itself) — those paths can’t supply
one without a dependency cycle or recursion.
Implementations§
Source§impl<'a> VM<'a>
impl<'a> VM<'a>
Sourcepub fn add_callframe(&mut self, new_call_frame: CallFrame)
pub fn add_callframe(&mut self, new_call_frame: CallFrame)
Adds current calframe to call_frames, sets current call frame to the passed callframe.
pub fn pop_call_frame(&mut self) -> Result<CallFrame, InternalError>
pub fn is_initial_call_frame(&self) -> bool
Sourcepub fn restore_cache_state(&mut self) -> Result<(), VMError>
pub fn restore_cache_state(&mut self) -> Result<(), VMError>
Restores the cache state to the state before changes made during a callframe.
Sourcepub fn restore_cache_state_consuming(&mut self) -> Result<(), VMError>
pub fn restore_cache_state_consuming(&mut self) -> Result<(), VMError>
Like Self::restore_cache_state but moves the current frame’s backup out instead of
cloning it. Only sound when nothing reads call_frame_backup afterward: the inner-call
revert in handle_return (the frame is popped right after, so its backup is dead), and
the top-level / invalid-tx revert when no BackupHook is installed (normal L1 block
execution, gated on VM::preserve_top_level_backup).
When a BackupHook IS present (L2 / stateless) the top-level paths must keep cloning,
because BackupHook::finalize reads the backup to build the tx-level undo snapshot.
pub fn merge_call_frame_backup_with_parent( &mut self, child_call_frame_backup: &CallFrameBackup, ) -> Result<(), VMError>
pub fn advance_pc(&mut self)
Source§impl<'a> VM<'a>
impl<'a> VM<'a>
pub fn get_account_mut( &mut self, address: Address, ) -> Result<&mut LevmAccount, InternalError>
pub fn increase_account_balance( &mut self, address: Address, increase: U256, ) -> Result<(), InternalError>
pub fn decrease_account_balance( &mut self, address: Address, decrease: U256, ) -> Result<(), InternalError>
pub fn transfer( &mut self, from: Address, to: Address, value: U256, ) -> Result<(), InternalError>
Sourcepub fn update_account_bytecode(
&mut self,
address: Address,
new_bytecode: Code,
) -> Result<(), InternalError>
pub fn update_account_bytecode( &mut self, address: Address, new_bytecode: Code, ) -> Result<(), InternalError>
Updates bytecode of given account.
Sourcepub fn increment_account_nonce(
&mut self,
address: Address,
) -> Result<u64, InternalError>
pub fn increment_account_nonce( &mut self, address: Address, ) -> Result<u64, InternalError>
Increments the nonce of the given account. Per EIP-7928, nonce changes are recorded for:
- EOA senders
- Contracts performing CREATE/CREATE2
- Deployed contracts
- EIP-7702 authorities
Sourcepub fn access_storage_slot_for_sstore(
&mut self,
address: Address,
key: H256,
) -> Result<(U256, U256, bool), InternalError>
pub fn access_storage_slot_for_sstore( &mut self, address: Address, key: H256, ) -> Result<(U256, U256, bool), InternalError>
SSTORE-specialized storage access path that returns current and original values together. This keeps the SSTORE hot path tighter by avoiding extra method-level plumbing.
Sourcepub fn record_storage_slot_to_bal(&mut self, address: Address, key: U256)
pub fn record_storage_slot_to_bal(&mut self, address: Address, key: U256)
Records a storage slot read to BAL after gas checks have passed. Per EIP-7928: “If pre-state validation fails, the target is never accessed and must not appear in BAL.” This function should be called AFTER the gas check succeeds.
Sourcepub fn get_storage_value(
&mut self,
address: Address,
key: H256,
) -> Result<U256, InternalError>
pub fn get_storage_value( &mut self, address: Address, key: H256, ) -> Result<U256, InternalError>
Gets storage value of an account, caching it if not already cached.
Sourcepub fn update_account_storage(
&mut self,
address: Address,
key: H256,
slot_key: U256,
new_value: U256,
current_value: U256,
) -> Result<(), InternalError>
pub fn update_account_storage( &mut self, address: Address, key: H256, slot_key: U256, new_value: U256, current_value: U256, ) -> Result<(), InternalError>
Updates storage of an account, caching it if not already cached.
pub fn backup_storage_slot( &mut self, address: Address, key: H256, current_value: U256, ) -> Result<(), InternalError>
Source§impl<'a> VM<'a>
impl<'a> VM<'a>
pub fn handle_precompile_result( precompile_result: Result<Bytes, VMError>, gas_limit: u64, gas_remaining: u64, ) -> Result<ContextResult, VMError>
pub fn handle_opcode_result(&mut self) -> Result<ContextResult, VMError>
pub fn handle_opcode_error( &mut self, error: VMError, ) -> Result<ContextResult, VMError>
Sourcepub fn handle_create_transaction(
&mut self,
) -> Result<Option<ContextResult>, VMError>
pub fn handle_create_transaction( &mut self, ) -> Result<Option<ContextResult>, VMError>
Handles external create transaction.
Source§impl<'a> VM<'a>
impl<'a> VM<'a>
Sourcepub fn generic_create(
&mut self,
value: U256,
code_offset_in_memory: usize,
code_size_in_memory: usize,
salt: Option<U256>,
) -> Result<OpcodeResult, VMError>
pub fn generic_create( &mut self, value: U256, code_offset_in_memory: usize, code_size_in_memory: usize, salt: Option<U256>, ) -> Result<OpcodeResult, VMError>
Common behavior for CREATE and CREATE2 opcodes
Sourcepub fn generic_call(
&mut self,
gas_limit: u64,
value: U256,
msg_sender: Address,
to: Address,
code_address: Address,
should_transfer_value: bool,
is_static: bool,
calldata: Bytes,
ret_offset: usize,
ret_size: usize,
bytecode: Code,
is_delegation_7702: bool,
new_account_charged: bool,
) -> Result<OpcodeResult, VMError>
pub fn generic_call( &mut self, gas_limit: u64, value: U256, msg_sender: Address, to: Address, code_address: Address, should_transfer_value: bool, is_static: bool, calldata: Bytes, ret_offset: usize, ret_size: usize, bytecode: Code, is_delegation_7702: bool, new_account_charged: bool, ) -> Result<OpcodeResult, VMError>
This (should) be the only function where gas is used as a U256. This is because we have to use the values that are pushed to the stack.
Sourcepub fn handle_state_backup(
&mut self,
ctx_result: &ContextResult,
consume_backup: bool,
) -> Result<(), VMError>
pub fn handle_state_backup( &mut self, ctx_result: &ContextResult, consume_backup: bool, ) -> Result<(), VMError>
Pop backup from stack and restore substate and cache if transaction reverted.
consume_backup lets the caller move the frame’s backup out (no clone) on the
revert path when nothing reads it afterward; see VM::restore_cache_state_consuming.
The top-level call passes true for normal L1 execution and false when a
BackupHook is installed (L2 / stateless), since that hook reads the backup in
finalize_execution (gated on VM::preserve_top_level_backup).
Sourcepub fn handle_return(
&mut self,
ctx_result: &ContextResult,
) -> Result<(), VMError>
pub fn handle_return( &mut self, ctx_result: &ContextResult, ) -> Result<(), VMError>
Handles case in which callframe was initiated by another callframe (with CALL or CREATE family opcodes)
Returns the pc increment.
pub fn handle_return_call( &mut self, executed_call_frame: CallFrame, ctx_result: &ContextResult, ) -> Result<(), VMError>
pub fn handle_return_create( &mut self, executed_call_frame: CallFrame, ctx_result: &ContextResult, ) -> Result<(), VMError>
Source§impl<'a> VM<'a>
impl<'a> VM<'a>
Sourcepub fn get_trace_result(&mut self) -> Result<CallTraceFrame, VMError>
pub fn get_trace_result(&mut self) -> Result<CallTraceFrame, VMError>
This method is intended to be accessed after transaction execution
Source§impl<'a> VM<'a>
impl<'a> VM<'a>
Sourcepub fn eip7702_set_access_code(&mut self) -> Result<(), VMError>
pub fn eip7702_set_access_code(&mut self) -> Result<(), VMError>
Applies the EIP-7702 authorizations and charges their state-dependent costs at
the top frame, mirroring EELS set_delegation
(amsterdam/vm/eoa_delegation.py::set_delegation).
Amsterdam+ (EIP-8037/8038): the per-auth intrinsic regular charge is only
REGULAR_PER_AUTH_BASE_COST; the state-dependent charges are levied here, in the
atomic prepare region (so an OOG rolls the whole region back and burns all gas
rather than rejecting the tx). Per valid authorization, in EELS order:
NEW_ACCOUNT(state) when the authority’s account leaf does not yet exist;ACCOUNT_WRITE(regular) when this is the transaction’s first write to the authority’s leaf (the sender’s leaf was written at inclusion, and the recipient’s whenvalue > 0, so those pay nothing here — a self-sponsored authority and repeated authorizations on one authority payACCOUNT_WRITEat most once);AUTH_BASE(state) when a net-new delegation indicator is written (the authority was not delegated before the tx and none was set for it earlier in the tx). Charged at most once per authority and never credited back.
On any OutOfGas from a charge, fail_prepare_region rolls back the region and
this returns Ok (the full-gas revert is emitted by run_execution); it never
propagates an Err (that would wrongly invalidate the block).
Pre-Amsterdam keeps the legacy per-auth model: PER_EMPTY_ACCOUNT_COST was
charged at intrinsic and REFUND_AUTH_PER_EXISTING_ACCOUNT is credited here for
an authority that already exists in the trie.
pub fn add_intrinsic_gas( &mut self, intrinsic: &IntrinsicGas, ) -> Result<(), VMError>
Sourcepub fn get_intrinsic_gas(&self) -> Result<IntrinsicGas, VMError>
pub fn get_intrinsic_gas(&self) -> Result<IntrinsicGas, VMError>
Returns (regular_gas, state_gas) intrinsic gas for the transaction.
For Amsterdam+, state_gas is the EIP-8037 state portion.
For pre-Amsterdam, state_gas is always 0.
Sourcepub fn get_min_gas_used(&self) -> Result<u64, VMError>
pub fn get_min_gas_used(&self) -> Result<u64, VMError>
Calculates the minimum gas to be consumed in the transaction.
Sourcepub fn get_tx_callee(
tx: &Transaction,
db: &mut GeneralizedDatabase,
env: &Environment,
substate: &mut Substate,
) -> Result<(Address, bool), VMError>
pub fn get_tx_callee( tx: &Transaction, db: &mut GeneralizedDatabase, env: &Environment, substate: &mut Substate, ) -> Result<(Address, bool), VMError>
Gets transaction callee, calculating create address if it’s a “Create” transaction.
Bool indicates whether it is a create transaction or not.
Source§impl<'a> VM<'a>
impl<'a> VM<'a>
Sourcepub fn new(
env: Environment,
db: &'a mut GeneralizedDatabase,
tx: &'a Transaction,
tracer: LevmCallTracer,
vm_type: VMType,
crypto: &'a dyn Crypto,
stateless_validator: Option<&'a dyn StatelessValidator>,
) -> Result<Self, VMError>
pub fn new( env: Environment, db: &'a mut GeneralizedDatabase, tx: &'a Transaction, tracer: LevmCallTracer, vm_type: VMType, crypto: &'a dyn Crypto, stateless_validator: Option<&'a dyn StatelessValidator>, ) -> Result<Self, VMError>
Constructs a VM, allocating a fresh 32 KB root call-frame stack.
Hot block execution should prefer VM::new_pooled, which draws the root stack from a
reusable pool instead of allocating + zeroing one per transaction.
Sourcepub fn new_pooled(
env: Environment,
db: &'a mut GeneralizedDatabase,
tx: &'a Transaction,
tracer: LevmCallTracer,
vm_type: VMType,
crypto: &'a dyn Crypto,
stateless_validator: Option<&'a dyn StatelessValidator>,
stack_pool: &mut Vec<Stack>,
memory_pool: &mut Vec<Memory>,
) -> Result<Self, VMError>
pub fn new_pooled( env: Environment, db: &'a mut GeneralizedDatabase, tx: &'a Transaction, tracer: LevmCallTracer, vm_type: VMType, crypto: &'a dyn Crypto, stateless_validator: Option<&'a dyn StatelessValidator>, stack_pool: &mut Vec<Stack>, memory_pool: &mut Vec<Memory>, ) -> Result<Self, VMError>
Like VM::new, but draws the root call-frame stack from stack_pool (falling back to a
fresh Stack::default() only when the pool is empty) and adopts the remaining pooled
stacks for sub-call frames. This avoids the per-tx 32 KB stack alloc+zero on a warm pool —
the dominant allocation for transfer-heavy blocks, where the root frame is the only frame.
Pair with VM::reclaim_into after execution to return every stack (root + sub-frame)
to stack_pool and the root memory buffer to memory_pool so the next tx reuses them.
Sourcepub fn reclaim_into(
self,
stack_pool: &mut Vec<Stack>,
memory_pool: &mut Vec<Memory>,
)
pub fn reclaim_into( self, stack_pool: &mut Vec<Stack>, memory_pool: &mut Vec<Memory>, )
Returns this VM’s reusable buffers to the caller’s pools so the next transaction reuses
them instead of allocating: every stack (root call-frame stack plus any sub-frame stacks
still pooled internally) to stack_pool, and the root memory buffer to memory_pool.
Must run on both the success and error paths of VM::execute.
Sourcepub fn increase_state_gas(&mut self, gas: u64) -> Result<(), VMError>
pub fn increase_state_gas(&mut self, gas: u64) -> Result<(), VMError>
EIP-8037: Charge state gas, drawing from reservoir first, spilling to gas_remaining if exhausted.
Must only be called for Amsterdam+ forks. All call sites must guard with
fork >= Fork::Amsterdam before invoking this method.
Sourcepub fn credit_state_gas_refund(&mut self, amount: u64) -> Result<(), VMError>
pub fn credit_state_gas_refund(&mut self, amount: u64) -> Result<(), VMError>
EIP-8037 credit_state_gas_refund: refund amount LIFO, mirroring EELS. The portion
spilled past the reservoir into this frame’s gas_remaining (frame_state_gas_spilled)
is returned to gas_remaining first; only the remainder flows to the shared reservoir.
state_gas_used drops by the full amount (may go negative when the matching charge lives
in an ancestor frame). Block accounting: both spill counters drop by the
gas_remaining-credited portion only, never the full amount. Amsterdam+ only.
Sourcepub fn refund_new_account_state_gas(
&mut self,
charged: bool,
) -> Result<(), VMError>
pub fn refund_new_account_state_gas( &mut self, charged: bool, ) -> Result<(), VMError>
Refund the EIP-8037 new-account state gas when charged is true. Used by the
CALL paths where a value-bearing call to an empty account charged the new-account
state gas but no account ends up created (insufficient balance, max depth, child
revert / failed precompile).
Sourcepub fn repay_state_gas_spill(&mut self) -> Result<(), VMError>
pub fn repay_state_gas_spill(&mut self) -> Result<(), VMError>
EIP-8037 repay_state_gas_spill (EELS incorporate_child, success arm): once a
successful child’s gas has merged into this frame, the reservoir repays the spill
still outstanding, up to what the reservoir holds.
A refund can land in a different frame than the charge it undoes: the refunding
frame’s spill may be smaller than the refund, so the excess credits the reservoir
even though the charge drew from gas_remaining. The merge is where the claim
(this frame’s spill) and the credit (the reservoir) first share a frame, so that
is where the two pools settle between themselves.
No state creation is undone, so state_gas_used does not move: the gas only
crosses back between the pools it drifted across. Both settlements stay
unchanged, which is what makes this repayment billing-neutral:
- user gas (
gas_limit - gas_remaining - state_gas_reservoir) is unchanged becausegas_remainingrises by exactly what the reservoir loses; - the EIP-7778 regular dimension subtracts the cumulative
state_gas_spill, so that counter drops by the repayment too — the reservoir (the state dimension) covered the charge after all, so it must stop being billed as regular gas.
A failed child repays nothing: its rollback already restored the state whose removal any refund credited.
Sourcepub fn refill_frame_state_gas(&mut self, entry: i64) -> Result<(), VMError>
pub fn refill_frame_state_gas(&mut self, entry: i64) -> Result<(), VMError>
EIP-8037 refill_frame_state_gas: roll back this frame’s state gas in LIFO
order on revert or exceptional halt, mirroring EELS refill_frame_state_gas.
entry is the value of state_gas_used when this frame began executing
(current_call_frame.state_gas_used_at_entry). The frame’s net charge is
frame_used = state_gas_used - entry. Of that, frame_state_gas_spilled was
drawn from gas_remaining (spilled past the reservoir) and the remainder came
from the reservoir. LIFO refill returns the spilled portion to gas_remaining
first and the rest to the reservoir, restoring the exact pools the charges drew
from. state_gas_used is rolled back to entry and the per-frame spill counter
is cleared.
Revert-vs-halt equivalence (load-bearing): on revert, the spilled gas returns to
gas_remaining (raising the sender refund / lowering raw_consumed) while
state_gas_spill drops by the same amount, so the regular dimension in
refund_sender (default_hook) drops by exactly the refilled spill. On exceptional
halt the caller subsequently sets gas_remaining = 0 and burns it to the regular
dimension — but state_gas_spill was already decremented here, so the spilled gas
stays counted as regular. Both paths are correct.
Must only be called for Amsterdam+ forks.
Sourcepub fn enter_prepare_region(&mut self)
pub fn enter_prepare_region(&mut self)
EIP-8037: enters the atomic prepare region (EELS interpreter.py’s depth-0
try around set_delegation + prepare_dispatch, interpreter.py:353-375).
Must be called in prepare_execution, after the sender nonce bump and fee
deduction (those survive a region rollback) but before the type-4 auth
handling (the first region charge). Captures the pre-region state-gas
baseline and the call_frame_backup key-space so fail_prepare_region can
later undo exactly the region’s writes.
Sourcepub fn fail_prepare_region(&mut self)
pub fn fail_prepare_region(&mut self)
EIP-8037: rolls back the atomic prepare region on an internal OOG from any of
its charges (7702 auth, CREATE/value NEW_ACCOUNT, recipient delegation-resolve).
Restores every call_frame_backup entry added since enter_prepare_region
(leaving the earlier sender nonce-bump / fee-deduction entries untouched),
refills the frame’s state gas back to the pre-region baseline, and sets
pending_prep_oog so run_execution turns this into a full-gas revert
instead of a tx-level rejection Err (which would wrongly invalidate the
block). Mirrors EELS restore_tx_state(prep_snapshot) + refill_frame_state_gas
(interpreter.py:366-374).
Sourcepub fn execute(&mut self) -> Result<ExecutionReport, VMError>
pub fn execute(&mut self) -> Result<ExecutionReport, VMError>
Executes a whole external transaction. Performing validations at the beginning.
Sourcepub fn run_frame_validation_prefix(
&mut self,
frame_indices: &[usize],
deploy_index: Option<usize>,
canonical_paymaster_pay_frame: Option<usize>,
) -> Result<PrefixSimResult, VMError>
pub fn run_frame_validation_prefix( &mut self, frame_indices: &[usize], deploy_index: Option<usize>, canonical_paymaster_pay_frame: Option<usize>, ) -> Result<PrefixSimResult, VMError>
EIP-8141 mempool entry point: set up the frame-tx context and observer, then simulate the validation prefix.
Performs the frame-tx preamble (static constraints, nonce, fee sanity,
FrameTxContext init, outer-signature validation) — the same checks
execute_frame_tx runs before any frame — then activates the
ValidationObserver for
sender with the prefix’s deploy_index, runs the prefix via
VM::simulate_validation_prefix, and returns the raw simulation
result. Does NOT charge or refund gas. canonical_paymaster_pay_frame
is the index of a canonical paymaster’s pay frame (always None today,
OQ1); when set, the access-restriction skip fires for that frame.
Sourcepub fn simulate_validation_prefix(
&mut self,
frame_indices: &[usize],
) -> Result<PrefixSimResult, VMError>
pub fn simulate_validation_prefix( &mut self, frame_indices: &[usize], ) -> Result<PrefixSimResult, VMError>
EIP-8141 mempool validation-prefix simulation (local peer policy).
Runs ONLY the validation-prefix frames (the verify/pay/deploy frames that
must execute before the transaction’s payer is established) of a frame
transaction, under an active ValidationObserver,
then stops as soon as the payer has been set. Reuses the real frame
execution primitives (eip7702_get_code, execute_default_code,
run_execution, substate backups, value transfer) over the DEFAULT/VERIFY
subset that prefixes are restricted to (Phase 1 structural rules forbid
SENDER and atomic-batch frames in the prefix), so it dispatches real
opcodes through the real handlers — not a separate mini-EVM.
frame_indices are the prefix frame indices (in order), as identified by
FrameTransaction::validation_prefix; expiry-verifier frames interleaved
in the prefix are run too (they may appear between prefix frames). The
caller must have set frame_tx_context and activated the observer.
Returns PrefixSimResult describing the outcome. Does NOT charge fees
or refund gas (mempool simulation only); state changes accumulate in the
shared db and are discarded by the caller (a fresh simulation database).
Sourcepub fn run_execution(&mut self) -> Result<ContextResult, VMError>
pub fn run_execution(&mut self) -> Result<ContextResult, VMError>
Main execution loop.
Sourcepub fn check_validation_banned_opcode(&mut self, opcode: u8)
pub fn check_validation_banned_opcode(&mut self, opcode: u8)
EIP-8141 validation-trace banned-opcode check (mempool simulation only).
Called once per dispatch-loop iteration, AFTER the opcode is fetched and
BEFORE the handler runs, gated by self.validation_observer.active. Byte
values are pinned against opcodes.rs.
Static bans: ORIGIN, GASPRICE, BLOCKHASH, COINBASE, TIMESTAMP
(except when the current frame’s target is EXPIRY_VERIFIER), NUMBER,
PREVRANDAO, GASLIMIT, BASEFEE, BLOBHASH, BLOBBASEFEE, INVALID,
SELFDESTRUCT, BALANCE, SELFBALANCE, TLOAD, TSTORE, and CALLCODE
in non-deploy prefix frames (ERC-7562 bans CALLCODE in validation;
DELEGATECALL is allowed subject to the CALL-family trace rules in the
handlers). SSTORE/CREATE/CREATE2 are allowed only inside the deploy
frame and are enforced in their handlers (state-write rules), not here.
Sequential GAS rule: GAS is allowed only immediately before a
*CALL (CALL/CALLCODE/DELEGATECALL/STATICCALL). We detect this by
remembering last_opcode: if the previous iteration was GAS and this
opcode is NOT a *CALL, the prior GAS was illegal.
Sourcepub fn validation_check_sload(&mut self, address: Address, slot: H256)
pub fn validation_check_sload(&mut self, address: Address, slot: H256)
EIP-8141 validation-trace SLOAD check (mempool simulation only).
SLOAD is allowed only when the storage owner (address, the executing
frame’s to) is the transaction sender. Records the touched slot for the
admission-time revalidation affected-set.
Sourcepub fn validation_check_sstore(&mut self, address: Address, slot: H256)
pub fn validation_check_sstore(&mut self, address: Address, slot: H256)
EIP-8141 validation-trace SSTORE check (mempool simulation only).
SSTORE is allowed only inside the deploy frame AND only when the storage
owner (address, the executing frame’s to) is the transaction sender.
Sourcepub fn validation_check_create(&mut self)
pub fn validation_check_create(&mut self)
EIP-8141 validation-trace state-creation check for CREATE/CREATE2
(mempool simulation only). Contract creation is a state write permitted
only inside the deploy frame.
Sourcepub fn validation_check_call_target(
&mut self,
target: Address,
is_delegation_7702: bool,
) -> Result<(), VMError>
pub fn validation_check_call_target( &mut self, target: Address, is_delegation_7702: bool, ) -> Result<(), VMError>
EIP-8141 validation-trace CALL*/EXTCODE* target check (mempool
simulation only).
The target must be an existing account or a precompile and must NOT be
EIP-7702-delegated, except the sender running its own default code (the
sender is exempt — its existence is a transaction precondition and it may
have no code). is_delegation_7702 is the flag already computed by
eip7702_get_code in the CALL-family handlers, threaded in to avoid a
second delegation resolution (and the &mut VM / &mut db borrow
conflict a dispatch-loop stack-peek would create).
Sourcepub fn validation_check_extcode_target(
&mut self,
target: Address,
) -> Result<(), VMError>
pub fn validation_check_extcode_target( &mut self, target: Address, ) -> Result<(), VMError>
EIP-8141 validation-trace EXTCODE* target check (mempool simulation
only). Like VM::validation_check_call_target, but resolves the
EIP-7702 delegation flag itself (the EXTCODE handlers do not call
eip7702_get_code). The substate.add_accessed_address warming the
EXTCODE gas already performed has happened; resolving here only follows a
delegation indicator to read its flag, mirroring the CALL-family path.
Sourcepub fn execute_precompile(
code_address: H160,
calldata: &Bytes,
gas_limit: u64,
gas_remaining: &mut u64,
fork: Fork,
cache: Option<&PrecompileCache>,
crypto: &dyn Crypto,
stateless_validator: Option<&dyn StatelessValidator>,
) -> Result<ContextResult, VMError>
pub fn execute_precompile( code_address: H160, calldata: &Bytes, gas_limit: u64, gas_remaining: &mut u64, fork: Fork, cache: Option<&PrecompileCache>, crypto: &dyn Crypto, stateless_validator: Option<&dyn StatelessValidator>, ) -> Result<ContextResult, VMError>
Executes precompile and handles the output that it returns, generating a report.
Sourcepub fn is_create(&self) -> Result<bool, InternalError>
pub fn is_create(&self) -> Result<bool, InternalError>
True if external transaction is a contract creation
Sourcepub fn stateless_execute(&mut self) -> Result<ExecutionReport, VMError>
pub fn stateless_execute(&mut self) -> Result<ExecutionReport, VMError>
Executes without making changes to the cache.
Sourcepub fn collect_stack_for_trace(&self) -> Vec<U256>
pub fn collect_stack_for_trace(&self) -> Vec<U256>
Collects the current stack in bottom-first order for struct-log emission.
LEVM stack is top-first in memory (values[offset] = top), so we reverse
the active slice to produce the bottom-first wire format geth uses.
Returns an empty Vec when cfg.disable_stack is true.
Sourcepub fn collect_memory_for_trace(&self) -> Vec<u8> ⓘ
pub fn collect_memory_for_trace(&self) -> Vec<u8> ⓘ
Collects the live memory bytes for the current frame.
Returns an empty Vec when cfg.enable_memory is false or memory is empty.
Sourcepub fn read_storage_for_trace(&mut self, opcode: u8) -> Option<(H256, H256)>
pub fn read_storage_for_trace(&mut self, opcode: u8) -> Option<(H256, H256)>
Pre-reads the storage key/value for the current SLOAD or SSTORE opcode.
Returns None when:
cfg.disable_storageis set, oropcodeis not SLOAD (0x54) or SSTORE (0x55), or- the stack is empty (guard against underflow before the handler runs), or
- the storage read fails for any reason (including
AccountNotFound— the trace omits the entry rather than emitting an ambiguous zero).
For SLOAD: key = stack.top; value = the current stored value read from the DB.
For SSTORE: key = stack.top, value = stack[top-1] (the new value being written).
Auto Trait Implementations§
impl<'a> !Freeze for VM<'a>
impl<'a> !RefUnwindSafe for VM<'a>
impl<'a> !Send for VM<'a>
impl<'a> !Sync for VM<'a>
impl<'a> !UnwindSafe for VM<'a>
impl<'a> Unpin for VM<'a>
impl<'a> UnsafeUnpin for VM<'a>
Blanket Implementations§
Source§impl<T> ArchivePointee for T
impl<T> ArchivePointee for T
Source§type ArchivedMetadata = ()
type ArchivedMetadata = ()
Source§fn pointer_metadata(
_: &<T as ArchivePointee>::ArchivedMetadata,
) -> <T as Pointee>::Metadata
fn pointer_metadata( _: &<T as ArchivePointee>::ArchivedMetadata, ) -> <T as Pointee>::Metadata
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> LayoutRaw for T
impl<T> LayoutRaw for T
Source§fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>
fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>
Source§impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
Source§unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool
unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool
Source§fn resolve_niched(out: Place<NichedOption<T, N1>>)
fn resolve_niched(out: Place<NichedOption<T, N1>>)
out indicating that a T is niched.