revm_handler/handler.rs
1use crate::{
2 evm::FrameTr,
3 execution,
4 frame::handle_reservoir_remaining_gas,
5 post_execution::{self, build_result_gas},
6 pre_execution::{self, apply_eip7702_auth_list, PreExecutionOutput},
7 system_call::SYSTEM_CALL_REGULAR_GAS_LIMIT,
8 validation, EvmTr, FrameResult, ItemOrResult,
9};
10use context::{
11 result::{ExecutionResult, FromStringError},
12 LocalContextTr,
13};
14use context_interface::{
15 cfg::gas_params,
16 context::{take_error, ContextError},
17 journaled_state::JournalCheckpoint,
18 result::{HaltReasonTr, InvalidHeader, InvalidTransaction, ResultGas},
19 Cfg, ContextTr, Database, JournalTr, Transaction,
20};
21use interpreter::{interpreter_action::FrameInit, GasTracker, InitialAndFloorGas, SharedMemory};
22use primitives::{TxKind, U256};
23
24/// Trait for errors that can occur during EVM execution.
25///
26/// This trait represents the minimal error requirements for EVM execution,
27/// ensuring that all necessary error types can be converted into the handler's error type.
28pub trait EvmTrError<EVM: EvmTr>:
29 From<InvalidTransaction>
30 + From<InvalidHeader>
31 + From<<<EVM::Context as ContextTr>::Db as Database>::Error>
32 + From<ContextError<<<EVM::Context as ContextTr>::Db as Database>::Error>>
33 + FromStringError
34{
35}
36
37impl<
38 EVM: EvmTr,
39 T: From<InvalidTransaction>
40 + From<InvalidHeader>
41 + From<<<EVM::Context as ContextTr>::Db as Database>::Error>
42 + From<ContextError<<<EVM::Context as ContextTr>::Db as Database>::Error>>
43 + FromStringError,
44 > EvmTrError<EVM> for T
45{
46}
47
48/// The main implementation of Ethereum Mainnet transaction execution.
49///
50/// The [`Handler::run`] method serves as the entry point for execution and provides
51/// out-of-the-box support for executing Ethereum mainnet transactions.
52///
53/// This trait allows EVM variants to customize execution logic by implementing
54/// their own method implementations.
55///
56/// The handler logic consists of four phases:
57/// * Validation - Validates tx/block/config fields and loads caller account and validates initial gas requirements and
58/// balance checks.
59/// * Pre-execution - Loads and warms accounts, deducts initial gas
60/// * Execution - Executes the main frame loop, delegating to [`EvmTr`] for creating and running call frames.
61/// * Post-execution - Calculates final refunds, validates gas floor, reimburses caller,
62/// and rewards beneficiary
63///
64///
65/// The [`Handler::catch_error`] method handles cleanup of intermediate state if an error
66/// occurs during execution.
67///
68/// # Returns
69///
70/// Returns execution status, error, gas spend and logs. State change is not returned and it is
71/// contained inside Context Journal. This setup allows multiple transactions to be chain executed.
72///
73/// To finalize the execution and obtain changed state, call [`JournalTr::finalize`] function.
74pub trait Handler {
75 /// The EVM type containing Context, Instruction, and Precompiles implementations.
76 type Evm: EvmTr<
77 Context: ContextTr<Journal: JournalTr, Local: LocalContextTr>,
78 Frame: FrameTr<FrameInit = FrameInit, FrameResult = FrameResult>,
79 >;
80 /// The error type returned by this handler.
81 type Error: EvmTrError<Self::Evm>;
82 /// The halt reason type included in the output
83 type HaltReason: HaltReasonTr;
84
85 /// The main entry point for transaction execution.
86 ///
87 /// This method calls [`Handler::run_without_catch_error`] and if it returns an error,
88 /// calls [`Handler::catch_error`] to handle the error and cleanup.
89 ///
90 /// The [`Handler::catch_error`] method ensures intermediate state is properly cleared.
91 ///
92 /// # Error handling
93 ///
94 /// In case of error, the journal can be in an inconsistent state and should be cleared by calling
95 /// [`JournalTr::discard_tx`] method or dropped.
96 ///
97 /// # Returns
98 ///
99 /// Returns execution result, error, gas spend and logs.
100 #[inline]
101 fn run(
102 &mut self,
103 evm: &mut Self::Evm,
104 ) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
105 // Run inner handler and catch all errors to handle cleanup.
106 match self.run_without_catch_error(evm) {
107 Ok(output) => Ok(output),
108 Err(e) => self.catch_error(evm, e),
109 }
110 }
111
112 /// Runs the system call.
113 ///
114 /// System call is a special transaction where caller is a [`crate::SYSTEM_ADDRESS`]
115 ///
116 /// It is used to call a system contracts and it skips all the `validation` and `pre-execution` and most of `post-execution` phases.
117 /// For example it will not deduct the caller or reward the beneficiary.
118 ///
119 /// State changs can be obtained by calling [`JournalTr::finalize`] method from the [`EvmTr::Context`].
120 ///
121 /// # Error handling
122 ///
123 /// By design system call should not fail and should always succeed.
124 /// In case of an error (If fetching account/storage on rpc fails), the journal can be in an inconsistent
125 /// state and should be cleared by calling [`JournalTr::discard_tx`] method or dropped.
126 #[inline]
127 fn run_system_call(
128 &mut self,
129 evm: &mut Self::Evm,
130 ) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
131 // dummy values that are not used.
132 let init_and_floor_gas = InitialAndFloorGas::new(0, 0);
133 let mut gas = self.system_call_gas(evm);
134 // System calls skip pre-execution, so the checkpoint that
135 // [`Handler::execution`] settles is opened here.
136 let checkpoint = evm.ctx().journal_mut().checkpoint();
137 // call execution and than output.
138 match self
139 .execution(evm, checkpoint, &mut gas)
140 .and_then(|exec_result| {
141 let exec_result = match exec_result {
142 Some(exec_result) => exec_result,
143 // Unreachable in practice: system calls carry no value and
144 // target non-delegated system contracts, so no runtime
145 // charges apply.
146 None => self.runtime_oog_result(evm, &init_and_floor_gas, &mut gas)?,
147 };
148 // System calls have no intrinsic gas; build ResultGas from frame result.
149 let gas = exec_result.gas();
150 let result_gas = build_result_gas(false, gas, init_and_floor_gas);
151 self.execution_result(evm, exec_result, result_gas)
152 }) {
153 out @ Ok(_) => out,
154 Err(e) => self.catch_error(evm, e),
155 }
156 }
157
158 /// Called by [`Handler::run`] to execute the core handler logic.
159 ///
160 /// Executes the four phases in sequence: [Handler::validate],
161 /// [Handler::pre_execution], [Handler::execution], [Handler::post_execution].
162 ///
163 /// Returns any errors without catching them or calling [`Handler::catch_error`].
164 #[inline]
165 fn run_without_catch_error(
166 &mut self,
167 evm: &mut Self::Evm,
168 ) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
169 let init_and_floor_gas = self.validate(evm)?;
170 // Create the transaction-level gas tracker from the validated
171 // intrinsic gas, mirroring how frames create their gas at frame init.
172 // All later phases charge and settle against it.
173 let mut gas = self.tx_gas(evm, &init_and_floor_gas);
174 // Pre-execution returns the EIP-7702 refund and the EIP-2780 runtime
175 // gas phase checkpoint. `None` — from pre-execution or execution —
176 // means the runtime gas phase ran out of gas: the transaction is
177 // included as an out-of-gas halt without entering execution.
178 let pre_execution = self.pre_execution(evm, &mut gas)?;
179
180 let refund = pre_execution.map(|pe| pe.eip7702_refund).unwrap_or(0) as i64;
181
182 let mut exec_result = None;
183 if let Some(pre_execution) = pre_execution {
184 exec_result = self.execution(evm, pre_execution.checkpoint, &mut gas)?;
185 }
186 let mut exec_result = match exec_result {
187 Some(exec_result) => exec_result,
188 None => self.runtime_oog_result(evm, &init_and_floor_gas, &mut gas)?,
189 };
190
191 let result_gas = self.post_execution(evm, &mut exec_result, init_and_floor_gas, refund)?;
192
193 // Prepare the output
194 self.execution_result(evm, exec_result, result_gas)
195 }
196
197 /// Validates the execution environment and transaction parameters.
198 ///
199 /// Calculates initial and floor gas requirements, verifies they are covered by the gas limit,
200 /// validates the transaction against state, and deducts the caller.
201 #[inline]
202 fn validate(&self, evm: &mut Self::Evm) -> Result<InitialAndFloorGas, Self::Error> {
203 self.validate_env(evm)?;
204 let mut init_and_floor_gas = self.validate_initial_tx_gas(evm)?;
205 self.validate_against_state_and_deduct_caller(evm, &mut init_and_floor_gas)?;
206 Ok(init_and_floor_gas)
207 }
208
209 /// Creates the transaction-level [`GasTracker`] from the validated initial gas.
210 ///
211 /// The gas limit is the transaction gas limit, `remaining` is the regular
212 /// gas budget left after the intrinsic gas (constrained by the EIP-8037
213 /// `TX_MAX_GAS_LIMIT` cap) and `reservoir` is the state gas pool, so the
214 /// intrinsic gas is accounted as already spent.
215 #[inline]
216 fn tx_gas(&self, evm: &mut Self::Evm, init_and_floor_gas: &InitialAndFloorGas) -> GasTracker {
217 let ctx = evm.ctx_ref();
218 let tx_gas_limit = ctx.tx().gas_limit();
219 let (remaining, reservoir) = init_and_floor_gas
220 .initial_gas_and_reservoir(tx_gas_limit, ctx.cfg().tx_gas_limit_cap());
221 GasTracker::new(tx_gas_limit, remaining, reservoir)
222 }
223
224 /// Creates the transaction-level [`GasTracker`] for a system call.
225 ///
226 /// System calls have no intrinsic gas and are not subject to the EIP-7825
227 /// `TX_MAX_GAS_LIMIT` cap. Under EIP-8037 the base
228 /// [`SYSTEM_CALL_REGULAR_GAS_LIMIT`] is the regular budget (`gas_left`) and
229 /// everything above it, the margin sized for [`SYSTEM_MAX_SSTORES_PER_CALL`]
230 /// fresh storage writes, is placed in the state-gas reservoir, so `GAS`
231 /// inside a system contract reports the regular budget only. Without
232 /// EIP-8037 the whole gas limit is regular gas.
233 ///
234 /// [`SYSTEM_MAX_SSTORES_PER_CALL`]: crate::system_call::SYSTEM_MAX_SSTORES_PER_CALL
235 #[inline]
236 fn system_call_gas(&self, evm: &mut Self::Evm) -> GasTracker {
237 let ctx = evm.ctx_ref();
238 let gas_limit = ctx.tx().gas_limit();
239 let reservoir = if ctx.cfg().is_amsterdam_eip8037_enabled() {
240 gas_limit.saturating_sub(SYSTEM_CALL_REGULAR_GAS_LIMIT)
241 } else {
242 0
243 };
244 GasTracker::new(gas_limit, gas_limit - reservoir, reservoir)
245 }
246
247 /// Prepares the EVM state for execution.
248 ///
249 /// Loads the beneficiary account (EIP-3651: Warm COINBASE) and all accounts/storage from the access list (EIP-2929).
250 ///
251 /// For EIP-7702 transactions, applies the authorization list and delegates successful authorizations.
252 /// Authorizations are applied before execution begins.
253 ///
254 /// Returns the pre-execution gas decisions ([`PreExecutionOutput`]): the
255 /// EIP-7702 gas refund and the still-open EIP-2780 runtime gas phase
256 /// checkpoint, which [`Handler::execution`] settles. Returns `None` when
257 /// the EIP-2780 authorization charges ran out of gas: the transaction
258 /// stays valid but must skip execution and be included as an out-of-gas
259 /// halt ([`Handler::runtime_oog_result`]).
260 #[inline]
261 fn pre_execution(
262 &self,
263 evm: &mut Self::Evm,
264 gas: &mut GasTracker,
265 ) -> Result<Option<PreExecutionOutput>, Self::Error> {
266 self.load_accounts(evm)?;
267
268 // EIP-2780: the checkpoint spans the whole runtime gas phase.
269 let checkpoint = evm.ctx().journal_mut().checkpoint();
270
271 let Some(eip7702_refund) = self.apply_eip7702_auth_list(evm, gas)? else {
272 // Out-of-gas while processing the authorizations: revert the
273 // applied delegations; the transaction is included as an
274 // out-of-gas halt. (An EIP-7702 transaction is always a call, so
275 // no create nonce bump is needed here.)
276 evm.ctx().journal_mut().checkpoint_revert(checkpoint);
277 return Ok(None);
278 };
279
280 Ok(Some(PreExecutionOutput {
281 eip7702_refund,
282 checkpoint,
283 }))
284 }
285
286 /// Creates and executes the initial frame, then processes the execution loop.
287 ///
288 /// First-frame creation completes the EIP-2780 runtime gas phase: it
289 /// charges the recipient/create-target costs on the transaction-level
290 /// gas, and `checkpoint` (opened at pre-execution around the applied
291 /// authorizations) is committed here — or reverted when those charges run
292 /// out of gas, in which case `None` is returned and the caller includes
293 /// the transaction as an out-of-gas halt
294 /// ([`Handler::runtime_oog_result`]).
295 ///
296 /// Always calls [Handler::last_frame_result] to handle returned gas from the call.
297 #[inline]
298 fn execution(
299 &mut self,
300 evm: &mut Self::Evm,
301 checkpoint: JournalCheckpoint,
302 gas: &mut GasTracker,
303 ) -> Result<Option<FrameResult>, Self::Error> {
304 // Create the first frame action from the transaction-level gas. Like a
305 // frame forwarding gas to a child, the first frame receives all
306 // remaining regular gas and the reservoir. The EIP-2780 refundable
307 // first-frame charges travel on the frame inputs like the `charged_*`
308 // flags of the CALL/CREATE opcodes.
309 let Some(first_frame_input) = self.first_frame_input(evm, gas)? else {
310 execution::runtime_oog_unwind(evm.ctx(), checkpoint)?;
311 return Ok(None);
312 };
313 // The runtime gas phase is complete: commit its state changes.
314 evm.ctx().journal_mut().checkpoint_commit();
315
316 // Run execution loop
317 let mut frame_result = self.run_exec_loop(evm, first_frame_input)?;
318
319 // Handle last frame result
320 self.last_frame_result(evm, &mut frame_result, gas)?;
321 Ok(Some(frame_result))
322 }
323
324 /// Builds the result for a transaction whose EIP-2780 runtime gas phase
325 /// ran out of gas ([`Handler::pre_execution`] or [`Handler::execution`]
326 /// returned `None`).
327 ///
328 /// The transaction is valid but its gas cannot cover the state-dependent
329 /// runtime charges. It is included as an out-of-gas halt: execution is
330 /// skipped, all regular gas is consumed (the reservoir is returned), and
331 /// the runtime state changes were already reverted when the phase bailed
332 /// out.
333 #[inline]
334 fn runtime_oog_result(
335 &mut self,
336 evm: &mut Self::Evm,
337 init_and_floor_gas: &InitialAndFloorGas,
338 gas: &mut GasTracker,
339 ) -> Result<FrameResult, Self::Error> {
340 // The runtime gas phase's partial charges are dropped: rebuild the
341 // pristine transaction-level gas. Execution is skipped and the
342 // untouched reservoir is carried by the synthetic out-of-gas frame
343 // result; the settle below consumes all regular gas and returns the
344 // reservoir.
345 *gas = self.tx_gas(evm, init_and_floor_gas);
346 let tx_gas_limit = evm.ctx().tx().gas_limit();
347 let reservoir = gas.reservoir();
348 let mut frame_result = match evm.ctx().tx().kind() {
349 TxKind::Call(_) => FrameResult::new_call_oog(tx_gas_limit, 0..0, reservoir),
350 TxKind::Create => FrameResult::new_create_oog(tx_gas_limit, reservoir),
351 };
352 self.last_frame_result(evm, &mut frame_result, gas)?;
353 Ok(frame_result)
354 }
355
356 /// Handles the final steps of transaction execution.
357 ///
358 /// Calculates final refunds and validates the gas floor (EIP-7623) to ensure minimum gas is spent.
359 /// After EIP-7623, at least floor gas must be consumed.
360 ///
361 /// Reimburses unused gas to the caller and rewards the beneficiary with transaction fees.
362 /// The effective gas price determines rewards, with the base fee being burned.
363 ///
364 /// Finally, finalizes output by returning the journal state and clearing internal state
365 /// for the next execution.
366 #[inline]
367 fn post_execution(
368 &self,
369 evm: &mut Self::Evm,
370 exec_result: &mut FrameResult,
371 init_and_floor_gas: InitialAndFloorGas,
372 eip7702_gas_refund: i64,
373 ) -> Result<ResultGas, Self::Error> {
374 // Calculate final refund and add EIP-7702 refund to gas.
375 self.refund(evm, exec_result, eip7702_gas_refund)?;
376
377 // Build ResultGas from the final gas state
378 // This includes all necessary fields and gas values.
379 let result_gas = post_execution::build_result_gas(
380 exec_result.instruction_result().is_halt(),
381 exec_result.gas(),
382 init_and_floor_gas,
383 );
384
385 // Ensure gas floor is met and minimum floor gas is spent.
386 // if `cfg.is_eip7623_disabled` is true, floor gas will be set to zero
387 self.eip7623_check_gas_floor(evm, exec_result, init_and_floor_gas);
388 // Return unused gas to caller
389 self.reimburse_caller(evm, exec_result)?;
390 // Pay transaction fees to beneficiary
391 self.reward_beneficiary(evm, exec_result)?;
392 // Build ResultGas from the final gas state
393 Ok(result_gas)
394 }
395
396 /* VALIDATION */
397
398 /// Validates block, transaction and configuration fields.
399 ///
400 /// Performs all validation checks that can be done without loading state.
401 /// For example, verifies transaction gas limit is below block gas limit.
402 #[inline]
403 fn validate_env(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> {
404 validation::validate_env(evm.ctx())
405 }
406
407 /// Calculates initial gas costs based on transaction type and input data.
408 ///
409 /// Includes additional costs for access list and authorization list.
410 ///
411 /// Verifies the initial cost does not exceed the transaction gas limit.
412 #[inline]
413 fn validate_initial_tx_gas(
414 &self,
415 evm: &mut Self::Evm,
416 ) -> Result<InitialAndFloorGas, Self::Error> {
417 let ctx = evm.ctx_ref();
418 let is_amsterdam_eip2780_enabled = ctx.cfg().is_amsterdam_eip2780_enabled();
419 let tx = ctx.tx();
420 let eip2780 = is_amsterdam_eip2780_enabled.then(|| {
421 // Self-transfer: a `Call` whose recipient is the sender itself.
422 let is_self_transfer = tx.kind().to() == Some(&tx.caller());
423 gas_params::Eip2780TxInfo {
424 value: tx.value(),
425 is_self_transfer,
426 }
427 });
428 let gas = validation::validate_initial_tx_gas_with_gas_params(
429 tx,
430 ctx.cfg().spec().into(),
431 ctx.cfg().gas_params(),
432 ctx.cfg().is_eip7623_disabled(),
433 ctx.cfg().is_amsterdam_eip8037_enabled(),
434 ctx.cfg().tx_gas_limit_cap(),
435 eip2780,
436 )?;
437
438 Ok(gas)
439 }
440
441 /* PRE EXECUTION */
442
443 /// Loads access list and beneficiary account, marking them as warm in the [`context::Journal`].
444 #[inline]
445 fn load_accounts(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> {
446 pre_execution::load_accounts(evm)
447 }
448
449 /// Processes the authorization list, validating authority signatures, nonces and chain IDs.
450 /// Applies valid authorizations to accounts.
451 ///
452 /// Returns the EIP-7702 gas refund, or `None` when the EIP-2780
453 /// authorization charges ran out of gas — [`Handler::pre_execution`] owns
454 /// the runtime gas phase checkpoint and reverts it in that case.
455 #[inline]
456 fn apply_eip7702_auth_list(
457 &self,
458 evm: &mut Self::Evm,
459 gas: &mut GasTracker,
460 ) -> Result<Option<u64>, Self::Error> {
461 apply_eip7702_auth_list(evm.ctx_mut(), gas)
462 }
463
464 /// Deducts the maximum possible fee from caller's balance.
465 ///
466 /// If cfg.is_balance_check_disabled, this method will add back enough funds to ensure that
467 /// the caller's balance is at least tx.value() before returning. Note that the amount of funds
468 /// added back in this case may exceed the maximum fee.
469 ///
470 /// Unused fees are returned to caller after execution completes.
471 #[inline]
472 fn validate_against_state_and_deduct_caller(
473 &self,
474 evm: &mut Self::Evm,
475 _init_and_floor_gas: &mut InitialAndFloorGas,
476 ) -> Result<(), Self::Error> {
477 pre_execution::validate_against_state_and_deduct_caller(evm.ctx())
478 }
479
480 /* EXECUTION */
481
482 /// Creates initial frame input from the transaction parameters and the
483 /// transaction-level gas, forwarding all remaining regular gas and the
484 /// reservoir to the frame.
485 ///
486 /// Under EIP-2780 the target loading also records the runtime
487 /// recipient/create-target charges on `gas` and marks the refundable ones
488 /// on the frame inputs' `charged_*` flags (see
489 /// [`execution::create_init_frame`]), so [`Handler::last_frame_result`]
490 /// can refund them from the outcome.
491 ///
492 /// Returns `None` when those charges run out of gas.
493 #[inline]
494 fn first_frame_input(
495 &mut self,
496 evm: &mut Self::Evm,
497 gas: &mut GasTracker,
498 ) -> Result<Option<FrameInit>, Self::Error> {
499 let ctx = evm.ctx_mut();
500 let mut memory = SharedMemory::new_with_buffer(ctx.local().shared_memory_buffer().clone());
501 memory.set_memory_limit(ctx.cfg().memory_limit());
502
503 let Some(frame_input) = execution::create_init_frame(ctx, gas)? else {
504 return Ok(None);
505 };
506
507 Ok(Some(FrameInit {
508 depth: 0,
509 memory,
510 frame_input,
511 }))
512 }
513
514 /// Processes the result of the initial call and settles it into the
515 /// transaction-level gas.
516 ///
517 /// Emulates how a parent frame settles a returning child
518 /// ([`handle_reservoir_remaining_gas`]): a failing frame rolls its
519 /// state-gas charges back in LIFO order and drops its refund counter, an
520 /// exceptional halt additionally consumes its regular gas; unused regular
521 /// gas then returns to the transaction-level gas and the reservoir is
522 /// adopted from the frame. The EIP-2780 refundable first-frame charge is
523 /// refunded from the outcome's `charged_*` flags, mirroring
524 /// `EthFrame::return_result`.
525 ///
526 /// The settled transaction-level gas is written back to the frame result,
527 /// which carries it into the post-execution phase.
528 #[inline]
529 fn last_frame_result(
530 &mut self,
531 evm: &mut Self::Evm,
532 frame_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
533 parent_gas: &mut GasTracker,
534 ) -> Result<(), Self::Error> {
535 let instruction_result = frame_result.instruction_result();
536
537 // All regular gas was forwarded to the first frame: consume it on the
538 // transaction-level gas; the settle below returns the frame's unused
539 // part.
540 parent_gas.spend_all();
541
542 // Settle the frame into the transaction-level gas like a parent frame.
543 handle_reservoir_remaining_gas(
544 instruction_result,
545 parent_gas,
546 frame_result.gas_mut().tracker_mut(),
547 );
548
549 // Refund the EIP-2780 refundable first-frame charge when no account
550 // leaf was created, exactly like `EthFrame::return_result` refunds
551 // the upfront CALL/CREATE state charges of inner frames.
552 if let Some(charge) = frame_result.refundable_state_gas(evm.ctx().cfg().gas_params()) {
553 parent_gas.refill_reservoir(charge);
554 // Unlike an inner frame's caller, the transaction ends here: an
555 // exceptional halt consumes all regular gas, including the
556 // spilled portion the refill just credited back to `remaining`.
557 if instruction_result.is_halt() {
558 parent_gas.spend_all();
559 }
560 }
561
562 // The frame result carries the transaction-level gas onward to the
563 // post-execution phase.
564 *frame_result.gas_mut().tracker_mut() = *parent_gas;
565
566 Ok(())
567 }
568
569 /* FRAMES */
570
571 /// Executes the main frame processing loop.
572 ///
573 /// This loop manages the frame stack, processing each frame until execution completes.
574 /// For each iteration:
575 /// 1. Calls the current frame
576 /// 2. Handles the returned frame input or result
577 /// 3. Creates new frames or propagates results as needed
578 #[inline]
579 fn run_exec_loop(
580 &mut self,
581 evm: &mut Self::Evm,
582 first_frame_input: <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameInit,
583 ) -> Result<FrameResult, Self::Error> {
584 let res = evm.frame_init(first_frame_input)?;
585
586 if let ItemOrResult::Result(frame_result) = res {
587 return Ok(frame_result);
588 }
589
590 loop {
591 let call_or_result = evm.frame_run()?;
592
593 let result = match call_or_result {
594 ItemOrResult::Item(init) => {
595 match evm.frame_init(init)? {
596 ItemOrResult::Item(_) => {
597 continue;
598 }
599 // Do not pop the frame since no new frame was created
600 ItemOrResult::Result(result) => result,
601 }
602 }
603 ItemOrResult::Result(result) => result,
604 };
605
606 if let Some(result) = evm.frame_return_result(result)? {
607 return Ok(result);
608 }
609 }
610 }
611
612 /* POST EXECUTION */
613
614 /// Validates that the minimum gas floor requirements are satisfied.
615 ///
616 /// Ensures that at least the floor gas amount has been consumed during execution.
617 #[inline]
618 fn eip7623_check_gas_floor(
619 &self,
620 _evm: &mut Self::Evm,
621 exec_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
622 init_and_floor_gas: InitialAndFloorGas,
623 ) {
624 post_execution::eip7623_check_gas_floor(exec_result.gas_mut(), init_and_floor_gas)
625 }
626
627 /// Calculates the final gas refund amount, including any EIP-7702 refunds.
628 #[inline]
629 fn refund(
630 &self,
631 evm: &mut Self::Evm,
632 exec_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
633 eip7702_refund: i64,
634 ) -> Result<(), Self::Error> {
635 post_execution::refund(
636 evm.ctx().cfg().gas_params(),
637 exec_result.gas_mut(),
638 eip7702_refund,
639 );
640
641 Ok(())
642 }
643
644 /// Returns unused gas costs to the transaction sender's account.
645 #[inline]
646 fn reimburse_caller(
647 &self,
648 evm: &mut Self::Evm,
649 exec_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
650 ) -> Result<(), Self::Error> {
651 post_execution::reimburse_caller(evm.ctx(), exec_result.gas(), U256::ZERO)
652 .map_err(From::from)
653 }
654
655 /// Transfers transaction fees to the block beneficiary's account.
656 #[inline]
657 fn reward_beneficiary(
658 &self,
659 evm: &mut Self::Evm,
660 exec_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
661 ) -> Result<(), Self::Error> {
662 post_execution::reward_beneficiary(evm.ctx(), exec_result.gas()).map_err(From::from)
663 }
664
665 /// Processes the final execution output.
666 ///
667 /// This method, retrieves the final state from the journal, converts internal results to the external output format.
668 /// Internal state is cleared and EVM is prepared for the next transaction.
669 #[inline]
670 fn execution_result(
671 &mut self,
672 evm: &mut Self::Evm,
673 result: <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
674 result_gas: ResultGas,
675 ) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
676 take_error::<Self::Error, _>(evm.ctx().error())?;
677
678 let exec_result = post_execution::output(evm.ctx(), result, result_gas);
679
680 // commit transaction
681 evm.ctx().journal_mut().commit_tx();
682 evm.ctx().local_mut().clear();
683 evm.frame_stack().clear();
684
685 Ok(exec_result)
686 }
687
688 /// Handles cleanup when an error occurs during execution.
689 ///
690 /// Ensures the journal state is properly cleared before propagating the error.
691 /// On happy path journal is cleared in [`Handler::execution_result`] method.
692 #[inline]
693 fn catch_error(
694 &self,
695 evm: &mut Self::Evm,
696 error: Self::Error,
697 ) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
698 // clean up local context. Initcode cache needs to be discarded.
699 evm.ctx().local_mut().clear();
700 evm.ctx().journal_mut().discard_tx();
701 evm.frame_stack().clear();
702 Err(error)
703 }
704}