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