Skip to main content

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}