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
404 // Spend the gas limit. Gas is reimbursed when the tx returns successfully.
405 *gas = Gas::new_spent_with_reservoir(evm.ctx().tx().gas_limit(), reservoir);
406
407 if instruction_result.is_ok_or_revert() {
408 // Return unused regular gas. Reservoir is handled separately via state_gas_spent.
409 gas.erase_cost(remaining);
410 }
411
412 if instruction_result.is_ok() {
413 gas.record_refund(refunded);
414 }
415
416 // return zero state gas on halt/revert.
417 if instruction_result.is_ok() {
418 gas.set_state_gas_spent(state_gas_spent);
419 } else {
420 gas.set_state_gas_spent(0);
421 }
422
423 // state gas
424 if !instruction_result.is_ok() {
425 // State changes rolled back (revert or halt). Apply the same
426 // invariant used by `handle_reservoir_remaining_gas` to recover
427 // the pre-call reservoir value: signed `reservoir + state_gas_spent`.
428 //
429 // record_state_cost increments state_gas_spent and decrements
430 // reservoir by the same amount; refill_reservoir does the inverse.
431 // Their sum is conserved, so adding the (possibly negative)
432 // state_gas_spent back to the final reservoir recovers the
433 // pre-call (here: pre-tx) value. The negative branch unwinds any
434 // 0→x→0 refill inflation propagated up from descendants — the
435 // grandchild-leak fix at the frame level applied to the top frame.
436 gas.set_reservoir(reservoir.saturating_add_signed(state_gas_spent));
437 }
438
439 // EIP-8037: for a failed top-level CREATE (or one that self-destructs
440 // in init code, see EIP-6780), refund the intrinsic `create_state_gas`
441 // to the reservoir. The nested-create equivalent is
442 // `EthFrame::return_result`'s `refill_reservoir(create_state_gas)`; at
443 // the top level the same charge is deducted in
444 // `initial_gas_and_reservoir` rather than via `record_state_cost`, so
445 // it would otherwise stay consumed when the deployment is rolled back
446 // or erased.
447 if create_failed && evm.ctx().cfg().is_amsterdam_eip8037_enabled() {
448 let ctx = evm.ctx();
449 let state_gas_charged = ctx.cfg().gas_params().create_state_gas(ctx.local().cpsb());
450 gas.refill_reservoir(state_gas_charged);
451 }
452
453 Ok(())
454 }
455
456 /* FRAMES */
457
458 /// Executes the main frame processing loop.
459 ///
460 /// This loop manages the frame stack, processing each frame until execution completes.
461 /// For each iteration:
462 /// 1. Calls the current frame
463 /// 2. Handles the returned frame input or result
464 /// 3. Creates new frames or propagates results as needed
465 #[inline]
466 fn run_exec_loop(
467 &mut self,
468 evm: &mut Self::Evm,
469 first_frame_input: <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameInit,
470 ) -> Result<FrameResult, Self::Error> {
471 let res = evm.frame_init(first_frame_input)?;
472
473 if let ItemOrResult::Result(frame_result) = res {
474 return Ok(frame_result);
475 }
476
477 loop {
478 let call_or_result = evm.frame_run()?;
479
480 let result = match call_or_result {
481 ItemOrResult::Item(init) => {
482 match evm.frame_init(init)? {
483 ItemOrResult::Item(_) => {
484 continue;
485 }
486 // Do not pop the frame since no new frame was created
487 ItemOrResult::Result(result) => result,
488 }
489 }
490 ItemOrResult::Result(result) => result,
491 };
492
493 if let Some(result) = evm.frame_return_result(result)? {
494 return Ok(result);
495 }
496 }
497 }
498
499 /* POST EXECUTION */
500
501 /// Validates that the minimum gas floor requirements are satisfied.
502 ///
503 /// Ensures that at least the floor gas amount has been consumed during execution.
504 #[inline]
505 fn eip7623_check_gas_floor(
506 &self,
507 _evm: &mut Self::Evm,
508 exec_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
509 init_and_floor_gas: InitialAndFloorGas,
510 ) {
511 post_execution::eip7623_check_gas_floor(exec_result.gas_mut(), init_and_floor_gas)
512 }
513
514 /// Calculates the final gas refund amount, including any EIP-7702 refunds.
515 #[inline]
516 fn refund(
517 &self,
518 evm: &mut Self::Evm,
519 exec_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
520 eip7702_refund: i64,
521 ) {
522 let spec = evm.ctx().cfg().spec().into();
523 post_execution::refund(spec, exec_result.gas_mut(), eip7702_refund)
524 }
525
526 /// Returns unused gas costs to the transaction sender's account.
527 #[inline]
528 fn reimburse_caller(
529 &self,
530 evm: &mut Self::Evm,
531 exec_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
532 ) -> Result<(), Self::Error> {
533 post_execution::reimburse_caller(evm.ctx(), exec_result.gas(), U256::ZERO)
534 .map_err(From::from)
535 }
536
537 /// Transfers transaction fees to the block beneficiary's account.
538 #[inline]
539 fn reward_beneficiary(
540 &self,
541 evm: &mut Self::Evm,
542 exec_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
543 ) -> Result<(), Self::Error> {
544 post_execution::reward_beneficiary(evm.ctx(), exec_result.gas()).map_err(From::from)
545 }
546
547 /// Processes the final execution output.
548 ///
549 /// This method, retrieves the final state from the journal, converts internal results to the external output format.
550 /// Internal state is cleared and EVM is prepared for the next transaction.
551 #[inline]
552 fn execution_result(
553 &mut self,
554 evm: &mut Self::Evm,
555 result: <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
556 result_gas: ResultGas,
557 ) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
558 take_error::<Self::Error, _>(evm.ctx().error())?;
559
560 let exec_result = post_execution::output(evm.ctx(), result, result_gas);
561
562 // commit transaction
563 evm.ctx().journal_mut().commit_tx();
564 evm.ctx().local_mut().clear();
565 evm.frame_stack().clear();
566
567 Ok(exec_result)
568 }
569
570 /// Handles cleanup when an error occurs during execution.
571 ///
572 /// Ensures the journal state is properly cleared before propagating the error.
573 /// On happy path journal is cleared in [`Handler::execution_result`] method.
574 #[inline]
575 fn catch_error(
576 &self,
577 evm: &mut Self::Evm,
578 error: Self::Error,
579 ) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
580 // clean up local context. Initcode cache needs to be discarded.
581 evm.ctx().local_mut().clear();
582 evm.ctx().journal_mut().discard_tx();
583 evm.frame_stack().clear();
584 Err(error)
585 }
586}