Skip to main content

revm_handler/
handler.rs

1use crate::{
2    evm::FrameTr,
3    execution,
4    post_execution::{self, build_result_gas},
5    pre_execution::{self, apply_eip7702_auth_list},
6    validation, EvmTr, FrameResult, ItemOrResult,
7};
8use context::{
9    result::{ExecutionResult, FromStringError},
10    LocalContextTr,
11};
12use context_interface::{
13    cfg::gas_params,
14    context::{take_error, ContextError},
15    result::{HaltReasonTr, InvalidHeader, InvalidTransaction, ResultGas},
16    Cfg, ContextTr, Database, JournalTr, Transaction,
17};
18use interpreter::{interpreter_action::FrameInit, Gas, InitialAndFloorGas, SharedMemory};
19use primitives::U256;
20
21/// Trait for errors that can occur during EVM execution.
22///
23/// This trait represents the minimal error requirements for EVM execution,
24/// ensuring that all necessary error types can be converted into the handler's error type.
25pub trait EvmTrError<EVM: EvmTr>:
26    From<InvalidTransaction>
27    + From<InvalidHeader>
28    + From<<<EVM::Context as ContextTr>::Db as Database>::Error>
29    + From<ContextError<<<EVM::Context as ContextTr>::Db as Database>::Error>>
30    + FromStringError
31{
32}
33
34impl<
35        EVM: EvmTr,
36        T: From<InvalidTransaction>
37            + From<InvalidHeader>
38            + From<<<EVM::Context as ContextTr>::Db as Database>::Error>
39            + From<ContextError<<<EVM::Context as ContextTr>::Db as Database>::Error>>
40            + FromStringError,
41    > EvmTrError<EVM> for T
42{
43}
44
45/// The main implementation of Ethereum Mainnet transaction execution.
46///
47/// The [`Handler::run`] method serves as the entry point for execution and provides
48/// out-of-the-box support for executing Ethereum mainnet transactions.
49///
50/// This trait allows EVM variants to customize execution logic by implementing
51/// their own method implementations.
52///
53/// The handler logic consists of four phases:
54///   * Validation - Validates tx/block/config fields and loads caller account and validates initial gas requirements and
55///     balance checks.
56///   * Pre-execution - Loads and warms accounts, deducts initial gas
57///   * Execution - Executes the main frame loop, delegating to [`EvmTr`] for creating and running call frames.
58///   * Post-execution - Calculates final refunds, validates gas floor, reimburses caller,
59///     and rewards beneficiary
60///
61///
62/// The [`Handler::catch_error`] method handles cleanup of intermediate state if an error
63/// occurs during execution.
64///
65/// # Returns
66///
67/// Returns execution status, error, gas spend and logs. State change is not returned and it is
68/// contained inside Context Journal. This setup allows multiple transactions to be chain executed.
69///
70/// To finalize the execution and obtain changed state, call [`JournalTr::finalize`] function.
71pub trait Handler {
72    /// The EVM type containing Context, Instruction, and Precompiles implementations.
73    type Evm: EvmTr<
74        Context: ContextTr<Journal: JournalTr, Local: LocalContextTr>,
75        Frame: FrameTr<FrameInit = FrameInit, FrameResult = FrameResult>,
76    >;
77    /// The error type returned by this handler.
78    type Error: EvmTrError<Self::Evm>;
79    /// The halt reason type included in the output
80    type HaltReason: HaltReasonTr;
81
82    /// The main entry point for transaction execution.
83    ///
84    /// This method calls [`Handler::run_without_catch_error`] and if it returns an error,
85    /// calls [`Handler::catch_error`] to handle the error and cleanup.
86    ///
87    /// The [`Handler::catch_error`] method ensures intermediate state is properly cleared.
88    ///
89    /// # Error handling
90    ///
91    /// In case of error, the journal can be in an inconsistent state and should be cleared by calling
92    /// [`JournalTr::discard_tx`] method or dropped.
93    ///
94    /// # Returns
95    ///
96    /// Returns execution result, error, gas spend and logs.
97    #[inline]
98    fn run(
99        &mut self,
100        evm: &mut Self::Evm,
101    ) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
102        // Run inner handler and catch all errors to handle cleanup.
103        match self.run_without_catch_error(evm) {
104            Ok(output) => Ok(output),
105            Err(e) => self.catch_error(evm, e),
106        }
107    }
108
109    /// Runs the system call.
110    ///
111    /// System call is a special transaction where caller is a [`crate::SYSTEM_ADDRESS`]
112    ///
113    /// It is used to call a system contracts and it skips all the `validation` and `pre-execution` and most of `post-execution` phases.
114    /// For example it will not deduct the caller or reward the beneficiary.
115    ///
116    /// State changs can be obtained by calling [`JournalTr::finalize`] method from the [`EvmTr::Context`].
117    ///
118    /// # Error handling
119    ///
120    /// By design system call should not fail and should always succeed.
121    /// In case of an error (If fetching account/storage on rpc fails), the journal can be in an inconsistent
122    /// state and should be cleared by calling [`JournalTr::discard_tx`] method or dropped.
123    #[inline]
124    fn run_system_call(
125        &mut self,
126        evm: &mut Self::Evm,
127    ) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
128        // dummy values that are not used.
129        let init_and_floor_gas = InitialAndFloorGas::new(0, 0);
130        // call execution and than output.
131        match self
132            .execution(evm, &init_and_floor_gas)
133            .and_then(|exec_result| {
134                // System calls have no intrinsic gas; build ResultGas from frame result.
135                let gas = exec_result.gas();
136                let result_gas = build_result_gas(false, gas, init_and_floor_gas);
137                self.execution_result(evm, exec_result, result_gas)
138            }) {
139            out @ Ok(_) => out,
140            Err(e) => self.catch_error(evm, e),
141        }
142    }
143
144    /// Called by [`Handler::run`] to execute the core handler logic.
145    ///
146    /// Executes the four phases in sequence: [Handler::validate],
147    /// [Handler::pre_execution], [Handler::execution], [Handler::post_execution].
148    ///
149    /// Returns any errors without catching them or calling [`Handler::catch_error`].
150    #[inline]
151    fn run_without_catch_error(
152        &mut self,
153        evm: &mut Self::Evm,
154    ) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
155        let mut init_and_floor_gas = self.validate(evm)?;
156        let eip7702_refund = self.pre_execution(evm, &mut init_and_floor_gas)?;
157        // Regular refund is returned from pre_execution after state gas split is applied
158        let eip7702_regular_refund = eip7702_refund as i64;
159
160        let mut exec_result = self.execution(evm, &init_and_floor_gas)?;
161        let result_gas = self.post_execution(
162            evm,
163            &mut exec_result,
164            init_and_floor_gas,
165            eip7702_regular_refund,
166        )?;
167
168        // Prepare the output
169        self.execution_result(evm, exec_result, result_gas)
170    }
171
172    /// Validates the execution environment and transaction parameters.
173    ///
174    /// Calculates initial and floor gas requirements and verifies they are covered by the gas limit.
175    ///
176    /// Validation against state is done later in pre-execution phase in deduct_caller function.
177    #[inline]
178    fn validate(&self, evm: &mut Self::Evm) -> Result<InitialAndFloorGas, Self::Error> {
179        self.validate_env(evm)?;
180        self.validate_initial_tx_gas(evm)
181    }
182
183    /// Prepares the EVM state for execution.
184    ///
185    /// Loads the beneficiary account (EIP-3651: Warm COINBASE) and all accounts/storage from the access list (EIP-2929).
186    ///
187    /// Deducts the maximum possible fee from the caller's balance.
188    ///
189    /// For EIP-7702 transactions, applies the authorization list and delegates successful authorizations.
190    /// Returns the gas refund amount from EIP-7702. Authorizations are applied before execution begins.
191    #[inline]
192    fn pre_execution(
193        &self,
194        evm: &mut Self::Evm,
195        init_and_floor_gas: &mut InitialAndFloorGas,
196    ) -> Result<u64, Self::Error> {
197        self.validate_against_state_and_deduct_caller(evm, init_and_floor_gas)?;
198        self.load_accounts(evm)?;
199
200        let gas = self.apply_eip7702_auth_list(evm, init_and_floor_gas)?;
201        Ok(gas)
202    }
203
204    /// Creates and executes the initial frame, then processes the execution loop.
205    ///
206    /// Always calls [Handler::last_frame_result] to handle returned gas from the call.
207    #[inline]
208    fn execution(
209        &mut self,
210        evm: &mut Self::Evm,
211        init_and_floor_gas: &InitialAndFloorGas,
212    ) -> Result<FrameResult, Self::Error> {
213        // Compute the regular gas budget and EIP-8037 reservoir for the first frame.
214        let (gas_limit, reservoir) = init_and_floor_gas.initial_gas_and_reservoir(
215            evm.ctx().tx().gas_limit(),
216            evm.ctx().cfg().tx_gas_limit_cap(),
217        );
218
219        // Create first frame action
220        // Note: first_frame_input now handles state gas deduction from the reservoir
221        let first_frame_input = self.first_frame_input(evm, gas_limit, reservoir)?;
222
223        // Run execution loop
224        let mut frame_result = self.run_exec_loop(evm, first_frame_input)?;
225
226        // Handle last frame result
227        self.last_frame_result(evm, &mut frame_result)?;
228        Ok(frame_result)
229    }
230
231    /// Handles the final steps of transaction execution.
232    ///
233    /// Calculates final refunds and validates the gas floor (EIP-7623) to ensure minimum gas is spent.
234    /// After EIP-7623, at least floor gas must be consumed.
235    ///
236    /// Reimburses unused gas to the caller and rewards the beneficiary with transaction fees.
237    /// The effective gas price determines rewards, with the base fee being burned.
238    ///
239    /// Finally, finalizes output by returning the journal state and clearing internal state
240    /// for the next execution.
241    #[inline]
242    fn post_execution(
243        &self,
244        evm: &mut Self::Evm,
245        exec_result: &mut FrameResult,
246        init_and_floor_gas: InitialAndFloorGas,
247        eip7702_gas_refund: i64,
248    ) -> Result<ResultGas, Self::Error> {
249        // Calculate final refund and add EIP-7702 refund to gas.
250        self.refund(evm, exec_result, eip7702_gas_refund)?;
251
252        // Build ResultGas from the final gas state
253        // This includes all necessary fields and gas values.
254        let result_gas = post_execution::build_result_gas(
255            exec_result.instruction_result().is_halt(),
256            exec_result.gas(),
257            init_and_floor_gas,
258        );
259
260        // Ensure gas floor is met and minimum floor gas is spent.
261        // if `cfg.is_eip7623_disabled` is true, floor gas will be set to zero
262        self.eip7623_check_gas_floor(evm, exec_result, init_and_floor_gas);
263        // Return unused gas to caller
264        self.reimburse_caller(evm, exec_result)?;
265        // Pay transaction fees to beneficiary
266        self.reward_beneficiary(evm, exec_result)?;
267        // Build ResultGas from the final gas state
268        Ok(result_gas)
269    }
270
271    /* VALIDATION */
272
273    /// Validates block, transaction and configuration fields.
274    ///
275    /// Performs all validation checks that can be done without loading state.
276    /// For example, verifies transaction gas limit is below block gas limit.
277    #[inline]
278    fn validate_env(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> {
279        validation::validate_env(evm.ctx())
280    }
281
282    /// Calculates initial gas costs based on transaction type and input data.
283    ///
284    /// Includes additional costs for access list and authorization list.
285    ///
286    /// Verifies the initial cost does not exceed the transaction gas limit.
287    #[inline]
288    fn validate_initial_tx_gas(
289        &self,
290        evm: &mut Self::Evm,
291    ) -> Result<InitialAndFloorGas, Self::Error> {
292        let ctx = evm.ctx_ref();
293        let is_amsterdam_eip2780_enabled = ctx.cfg().is_amsterdam_eip2780_enabled();
294        let tx = ctx.tx();
295        let eip2780 = is_amsterdam_eip2780_enabled.then(|| {
296            // Self-transfer: a `Call` whose recipient is the sender itself.
297            let is_self_transfer = tx.kind().to() == Some(&tx.caller());
298            gas_params::Eip2780TxInfo {
299                value: tx.value(),
300                is_self_transfer,
301            }
302        });
303        let gas = validation::validate_initial_tx_gas_with_gas_params(
304            tx,
305            ctx.cfg().spec().into(),
306            ctx.cfg().gas_params(),
307            ctx.cfg().is_eip7623_disabled(),
308            ctx.cfg().is_amsterdam_eip8037_enabled(),
309            ctx.cfg().tx_gas_limit_cap(),
310            eip2780,
311        )?;
312
313        Ok(gas)
314    }
315
316    /* PRE EXECUTION */
317
318    /// Loads access list and beneficiary account, marking them as warm in the [`context::Journal`].
319    #[inline]
320    fn load_accounts(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> {
321        pre_execution::load_accounts(evm)
322    }
323
324    /// Processes the authorization list, validating authority signatures, nonces and chain IDs.
325    /// Applies valid authorizations to accounts.
326    ///
327    /// Returns the gas refund amount specified by EIP-7702.
328    #[inline]
329    fn apply_eip7702_auth_list(
330        &self,
331        evm: &mut Self::Evm,
332        init_and_floor_gas: &mut InitialAndFloorGas,
333    ) -> Result<u64, Self::Error> {
334        apply_eip7702_auth_list(evm.ctx_mut(), init_and_floor_gas)
335    }
336
337    /// Deducts the maximum possible fee from caller's balance.
338    ///
339    /// If cfg.is_balance_check_disabled, this method will add back enough funds to ensure that
340    /// the caller's balance is at least tx.value() before returning. Note that the amount of funds
341    /// added back in this case may exceed the maximum fee.
342    ///
343    /// Unused fees are returned to caller after execution completes.
344    #[inline]
345    fn validate_against_state_and_deduct_caller(
346        &self,
347        evm: &mut Self::Evm,
348        _init_and_floor_gas: &mut InitialAndFloorGas,
349    ) -> Result<(), Self::Error> {
350        pre_execution::validate_against_state_and_deduct_caller(evm.ctx())
351    }
352
353    /* EXECUTION */
354
355    /// Creates initial frame input using transaction parameters, gas limit and configuration.
356    #[inline]
357    fn first_frame_input(
358        &mut self,
359        evm: &mut Self::Evm,
360        gas_limit: u64,
361        reservoir: u64,
362    ) -> Result<FrameInit, Self::Error> {
363        let ctx = evm.ctx_mut();
364        let mut memory = SharedMemory::new_with_buffer(ctx.local().shared_memory_buffer().clone());
365        memory.set_memory_limit(ctx.cfg().memory_limit());
366
367        let frame_input = execution::create_init_frame(ctx, gas_limit, reservoir)?;
368
369        Ok(FrameInit {
370            depth: 0,
371            memory,
372            frame_input,
373        })
374    }
375
376    /// Processes the result of the initial call and handles returned gas.
377    #[inline]
378    fn last_frame_result(
379        &mut self,
380        evm: &mut Self::Evm,
381        frame_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
382    ) -> Result<(), Self::Error> {
383        let instruction_result = frame_result.interpreter_result().result;
384
385        // Detect a top-level CREATE that creates no new account leaf — either it
386        // failed, or it succeeded at a pre-existing alive (balance-only) target —
387        // so the intrinsic `create_state_gas` charged at tx entry can be unwound
388        // below. Mirrors the condition used in `EthFrame::return_result` for
389        // nested creates.
390        let create_refunds_state_gas = match &frame_result {
391            FrameResult::Create(outcome) => !instruction_result.is_ok() || outcome.target_was_alive,
392            _ => false,
393        };
394
395        let gas = frame_result.gas_mut();
396
397        // Settle the top frame's own gas (mirrors `handle_reservoir_remaining_gas`'s
398        // child settle). A failing frame rolls its state-gas charges back in LIFO
399        // order — crediting the spilled portion back to `remaining` and restoring
400        // the reservoir to its pre-tx value — and drops the refund counter; an
401        // exceptional halt additionally consumes the regular gas.
402        if !instruction_result.is_ok() {
403            gas.rollback_state_gas();
404            gas.set_refunded(0);
405        }
406        if !instruction_result.is_ok_or_revert() {
407            gas.spend_all();
408        }
409
410        let remaining = gas.remaining();
411        let refunded = gas.refunded();
412        let reservoir = gas.reservoir();
413        let state_gas_spent = gas.state_gas_spent();
414
415        // Spend the gas limit. Gas is reimbursed when the tx returns successfully.
416        *gas = Gas::new_spent_with_reservoir(evm.ctx().tx().gas_limit(), reservoir);
417
418        if instruction_result.is_ok_or_revert() {
419            // Return unused regular gas (including any spill credited back by the
420            // rollback above on revert). The reservoir was restored separately.
421            gas.erase_cost(remaining);
422        }
423
424        if instruction_result.is_ok() {
425            gas.record_refund(refunded);
426            gas.set_state_gas_spent(state_gas_spent);
427        }
428
429        // EIP-8037: for a failed top-level CREATE (or one that self-destructs
430        // in init code, see EIP-6780), refund the intrinsic `create_state_gas`
431        // to the reservoir. The nested-create equivalent is
432        // `EthFrame::return_result`'s `refill_reservoir(create_state_gas)`; at
433        // the top level the same charge is deducted in
434        // `initial_gas_and_reservoir` rather than via `record_state_cost`, so
435        // it would otherwise stay consumed when the deployment is rolled back
436        // or erased.
437        if create_refunds_state_gas && evm.ctx().cfg().is_amsterdam_eip8037_enabled() {
438            let ctx = evm.ctx();
439            let state_gas_charged = ctx.cfg().gas_params().create_state_gas();
440            gas.refill_reservoir(state_gas_charged);
441        }
442
443        Ok(())
444    }
445
446    /* FRAMES */
447
448    /// Executes the main frame processing loop.
449    ///
450    /// This loop manages the frame stack, processing each frame until execution completes.
451    /// For each iteration:
452    /// 1. Calls the current frame
453    /// 2. Handles the returned frame input or result
454    /// 3. Creates new frames or propagates results as needed
455    #[inline]
456    fn run_exec_loop(
457        &mut self,
458        evm: &mut Self::Evm,
459        first_frame_input: <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameInit,
460    ) -> Result<FrameResult, Self::Error> {
461        let res = evm.frame_init(first_frame_input)?;
462
463        if let ItemOrResult::Result(frame_result) = res {
464            return Ok(frame_result);
465        }
466
467        loop {
468            let call_or_result = evm.frame_run()?;
469
470            let result = match call_or_result {
471                ItemOrResult::Item(init) => {
472                    match evm.frame_init(init)? {
473                        ItemOrResult::Item(_) => {
474                            continue;
475                        }
476                        // Do not pop the frame since no new frame was created
477                        ItemOrResult::Result(result) => result,
478                    }
479                }
480                ItemOrResult::Result(result) => result,
481            };
482
483            if let Some(result) = evm.frame_return_result(result)? {
484                return Ok(result);
485            }
486        }
487    }
488
489    /* POST EXECUTION */
490
491    /// Validates that the minimum gas floor requirements are satisfied.
492    ///
493    /// Ensures that at least the floor gas amount has been consumed during execution.
494    #[inline]
495    fn eip7623_check_gas_floor(
496        &self,
497        _evm: &mut Self::Evm,
498        exec_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
499        init_and_floor_gas: InitialAndFloorGas,
500    ) {
501        post_execution::eip7623_check_gas_floor(exec_result.gas_mut(), init_and_floor_gas)
502    }
503
504    /// Calculates the final gas refund amount, including any EIP-7702 refunds.
505    #[inline]
506    fn refund(
507        &self,
508        evm: &mut Self::Evm,
509        exec_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
510        eip7702_refund: i64,
511    ) -> Result<(), Self::Error> {
512        post_execution::refund(
513            evm.ctx().cfg().gas_params(),
514            exec_result.gas_mut(),
515            eip7702_refund,
516        );
517
518        Ok(())
519    }
520
521    /// Returns unused gas costs to the transaction sender's account.
522    #[inline]
523    fn reimburse_caller(
524        &self,
525        evm: &mut Self::Evm,
526        exec_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
527    ) -> Result<(), Self::Error> {
528        post_execution::reimburse_caller(evm.ctx(), exec_result.gas(), U256::ZERO)
529            .map_err(From::from)
530    }
531
532    /// Transfers transaction fees to the block beneficiary's account.
533    #[inline]
534    fn reward_beneficiary(
535        &self,
536        evm: &mut Self::Evm,
537        exec_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
538    ) -> Result<(), Self::Error> {
539        post_execution::reward_beneficiary(evm.ctx(), exec_result.gas()).map_err(From::from)
540    }
541
542    /// Processes the final execution output.
543    ///
544    /// This method, retrieves the final state from the journal, converts internal results to the external output format.
545    /// Internal state is cleared and EVM is prepared for the next transaction.
546    #[inline]
547    fn execution_result(
548        &mut self,
549        evm: &mut Self::Evm,
550        result: <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
551        result_gas: ResultGas,
552    ) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
553        take_error::<Self::Error, _>(evm.ctx().error())?;
554
555        let exec_result = post_execution::output(evm.ctx(), result, result_gas);
556
557        // commit transaction
558        evm.ctx().journal_mut().commit_tx();
559        evm.ctx().local_mut().clear();
560        evm.frame_stack().clear();
561
562        Ok(exec_result)
563    }
564
565    /// Handles cleanup when an error occurs during execution.
566    ///
567    /// Ensures the journal state is properly cleared before propagating the error.
568    /// On happy path journal is cleared in [`Handler::execution_result`] method.
569    #[inline]
570    fn catch_error(
571        &self,
572        evm: &mut Self::Evm,
573        error: Self::Error,
574    ) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
575        // clean up local context. Initcode cache needs to be discarded.
576        evm.ctx().local_mut().clear();
577        evm.ctx().journal_mut().discard_tx();
578        evm.frame_stack().clear();
579        Err(error)
580    }
581}