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