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(false, 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, init_and_floor_gas)?;
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 // Compute the regular gas budget and EIP-8037 reservoir for the first frame.
213 let (gas_limit, reservoir) = init_and_floor_gas.initial_gas_and_reservoir(
214 evm.ctx().tx().gas_limit(),
215 evm.ctx().cfg().tx_gas_limit_cap(),
216 );
217
218 // Create first frame action
219 // Note: first_frame_input now handles state gas deduction from the reservoir
220 let first_frame_input = self.first_frame_input(evm, gas_limit, reservoir)?;
221
222 // Run execution loop
223 let mut frame_result = self.run_exec_loop(evm, first_frame_input)?;
224
225 // Handle last frame result
226 self.last_frame_result(evm, reservoir, &mut frame_result)?;
227 Ok(frame_result)
228 }
229
230 /// Handles the final steps of transaction execution.
231 ///
232 /// Calculates final refunds and validates the gas floor (EIP-7623) to ensure minimum gas is spent.
233 /// After EIP-7623, at least floor gas must be consumed.
234 ///
235 /// Reimburses unused gas to the caller and rewards the beneficiary with transaction fees.
236 /// The effective gas price determines rewards, with the base fee being burned.
237 ///
238 /// Finally, finalizes output by returning the journal state and clearing internal state
239 /// for the next execution.
240 #[inline]
241 fn post_execution(
242 &self,
243 evm: &mut Self::Evm,
244 exec_result: &mut FrameResult,
245 init_and_floor_gas: InitialAndFloorGas,
246 eip7702_gas_refund: i64,
247 ) -> Result<ResultGas, Self::Error> {
248 // Calculate final refund and add EIP-7702 refund to gas.
249 self.refund(evm, exec_result, eip7702_gas_refund);
250
251 // Build ResultGas from the final gas state
252 // This includes all necessary fields and gas values.
253 let result_gas = post_execution::build_result_gas(
254 exec_result.instruction_result().is_halt(),
255 exec_result.gas(),
256 init_and_floor_gas,
257 );
258
259 // Ensure gas floor is met and minimum floor gas is spent.
260 // if `cfg.is_eip7623_disabled` is true, floor gas will be set to zero
261 self.eip7623_check_gas_floor(evm, exec_result, init_and_floor_gas);
262 // Return unused gas to caller
263 self.reimburse_caller(evm, exec_result)?;
264 // Pay transaction fees to beneficiary
265 self.reward_beneficiary(evm, exec_result)?;
266 // Build ResultGas from the final gas state
267 Ok(result_gas)
268 }
269
270 /* VALIDATION */
271
272 /// Validates block, transaction and configuration fields.
273 ///
274 /// Performs all validation checks that can be done without loading state.
275 /// For example, verifies transaction gas limit is below block gas limit.
276 #[inline]
277 fn validate_env(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> {
278 validation::validate_env(evm.ctx())
279 }
280
281 /// Calculates initial gas costs based on transaction type and input data.
282 ///
283 /// Includes additional costs for access list and authorization list.
284 ///
285 /// Verifies the initial cost does not exceed the transaction gas limit.
286 #[inline]
287 fn validate_initial_tx_gas(
288 &self,
289 evm: &mut Self::Evm,
290 ) -> Result<InitialAndFloorGas, Self::Error> {
291 let ctx = evm.ctx_ref();
292 let gas = validation::validate_initial_tx_gas_with_gas_params(
293 ctx.tx(),
294 ctx.cfg().spec().into(),
295 ctx.cfg().gas_params(),
296 ctx.cfg().is_eip7623_disabled(),
297 ctx.cfg().is_amsterdam_eip8037_enabled(),
298 ctx.cfg().tx_gas_limit_cap(),
299 )?;
300
301 Ok(gas)
302 }
303
304 /* PRE EXECUTION */
305
306 /// Loads access list and beneficiary account, marking them as warm in the [`context::Journal`].
307 #[inline]
308 fn load_accounts(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> {
309 pre_execution::load_accounts(evm)
310 }
311
312 /// Processes the authorization list, validating authority signatures, nonces and chain IDs.
313 /// Applies valid authorizations to accounts.
314 ///
315 /// Returns the gas refund amount specified by EIP-7702.
316 #[inline]
317 fn apply_eip7702_auth_list(
318 &self,
319 evm: &mut Self::Evm,
320 init_and_floor_gas: &mut InitialAndFloorGas,
321 ) -> Result<u64, Self::Error> {
322 apply_eip7702_auth_list(evm.ctx_mut(), init_and_floor_gas)
323 }
324
325 /// Deducts the maximum possible fee from caller's balance.
326 ///
327 /// If cfg.is_balance_check_disabled, this method will add back enough funds to ensure that
328 /// the caller's balance is at least tx.value() before returning. Note that the amount of funds
329 /// added back in this case may exceed the maximum fee.
330 ///
331 /// Unused fees are returned to caller after execution completes.
332 #[inline]
333 fn validate_against_state_and_deduct_caller(
334 &self,
335 evm: &mut Self::Evm,
336 _init_and_floor_gas: &mut InitialAndFloorGas,
337 ) -> Result<(), Self::Error> {
338 pre_execution::validate_against_state_and_deduct_caller(evm.ctx())
339 }
340
341 /* EXECUTION */
342
343 /// Creates initial frame input using transaction parameters, gas limit and configuration.
344 #[inline]
345 fn first_frame_input(
346 &mut self,
347 evm: &mut Self::Evm,
348 gas_limit: u64,
349 reservoir: u64,
350 ) -> Result<FrameInit, Self::Error> {
351 let ctx = evm.ctx_mut();
352 let mut memory = SharedMemory::new_with_buffer(ctx.local().shared_memory_buffer().clone());
353 memory.set_memory_limit(ctx.cfg().memory_limit());
354
355 let frame_input = execution::create_init_frame(ctx, gas_limit, reservoir)?;
356
357 Ok(FrameInit {
358 depth: 0,
359 memory,
360 frame_input,
361 })
362 }
363
364 /// Processes the result of the initial call and handles returned gas.
365 #[inline]
366 fn last_frame_result(
367 &mut self,
368 evm: &mut Self::Evm,
369 _original_reservoir: u64,
370 frame_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
371 ) -> Result<(), Self::Error> {
372 let instruction_result = frame_result.interpreter_result().result;
373
374 // Detect a failed top-level CREATE so the intrinsic `create_state_gas`
375 // charged at tx entry can be unwound below. Mirrors the `create_failed`
376 // condition used in `EthFrame::return_result` for nested creates.
377 let create_failed =
378 matches!(frame_result, FrameResult::Create(_)) && !instruction_result.is_ok();
379
380 let gas = frame_result.gas_mut();
381 let remaining = gas.remaining();
382 let refunded = gas.refunded();
383 let reservoir = gas.reservoir();
384 let state_gas_spent = gas.state_gas_spent();
385
386 // Spend the gas limit. Gas is reimbursed when the tx returns successfully.
387 *gas = Gas::new_spent_with_reservoir(evm.ctx().tx().gas_limit(), reservoir);
388
389 if instruction_result.is_ok_or_revert() {
390 // Return unused regular gas. Reservoir is handled separately via state_gas_spent.
391 gas.erase_cost(remaining);
392 }
393
394 if instruction_result.is_ok() {
395 gas.record_refund(refunded);
396 }
397
398 // return zero state gas on halt/revert.
399 if instruction_result.is_ok() {
400 gas.set_state_gas_spent(state_gas_spent);
401 } else {
402 gas.set_state_gas_spent(0);
403 }
404
405 // state gas
406 if !instruction_result.is_ok() {
407 // State changes rolled back (revert or halt). Apply the same
408 // invariant used by `handle_reservoir_remaining_gas` to recover
409 // the pre-call reservoir value: signed `reservoir + state_gas_spent`.
410 //
411 // record_state_cost increments state_gas_spent and decrements
412 // reservoir by the same amount; refill_reservoir does the inverse.
413 // Their sum is conserved, so adding the (possibly negative)
414 // state_gas_spent back to the final reservoir recovers the
415 // pre-call (here: pre-tx) value. The negative branch unwinds any
416 // 0→x→0 refill inflation propagated up from descendants — the
417 // grandchild-leak fix at the frame level applied to the top frame.
418 gas.set_reservoir(reservoir.saturating_add_signed(state_gas_spent));
419 }
420
421 // EIP-8037: for a failed top-level CREATE (or one that self-destructs
422 // in init code, see EIP-6780), refund the intrinsic `create_state_gas`
423 // to the reservoir. The nested-create equivalent is
424 // `EthFrame::return_result`'s `refill_reservoir(create_state_gas)`; at
425 // the top level the same charge is deducted in
426 // `initial_gas_and_reservoir` rather than via `record_state_cost`, so
427 // it would otherwise stay consumed when the deployment is rolled back
428 // or erased.
429 if create_failed && evm.ctx().cfg().is_amsterdam_eip8037_enabled() {
430 let ctx = evm.ctx();
431 let state_gas_charged = ctx.cfg().gas_params().create_state_gas();
432 gas.refill_reservoir(state_gas_charged);
433 }
434
435 Ok(())
436 }
437
438 /* FRAMES */
439
440 /// Executes the main frame processing loop.
441 ///
442 /// This loop manages the frame stack, processing each frame until execution completes.
443 /// For each iteration:
444 /// 1. Calls the current frame
445 /// 2. Handles the returned frame input or result
446 /// 3. Creates new frames or propagates results as needed
447 #[inline]
448 fn run_exec_loop(
449 &mut self,
450 evm: &mut Self::Evm,
451 first_frame_input: <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameInit,
452 ) -> Result<FrameResult, Self::Error> {
453 let res = evm.frame_init(first_frame_input)?;
454
455 if let ItemOrResult::Result(frame_result) = res {
456 return Ok(frame_result);
457 }
458
459 loop {
460 let call_or_result = evm.frame_run()?;
461
462 let result = match call_or_result {
463 ItemOrResult::Item(init) => {
464 match evm.frame_init(init)? {
465 ItemOrResult::Item(_) => {
466 continue;
467 }
468 // Do not pop the frame since no new frame was created
469 ItemOrResult::Result(result) => result,
470 }
471 }
472 ItemOrResult::Result(result) => result,
473 };
474
475 if let Some(result) = evm.frame_return_result(result)? {
476 return Ok(result);
477 }
478 }
479 }
480
481 /* POST EXECUTION */
482
483 /// Validates that the minimum gas floor requirements are satisfied.
484 ///
485 /// Ensures that at least the floor gas amount has been consumed during execution.
486 #[inline]
487 fn eip7623_check_gas_floor(
488 &self,
489 _evm: &mut Self::Evm,
490 exec_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
491 init_and_floor_gas: InitialAndFloorGas,
492 ) {
493 post_execution::eip7623_check_gas_floor(exec_result.gas_mut(), init_and_floor_gas)
494 }
495
496 /// Calculates the final gas refund amount, including any EIP-7702 refunds.
497 #[inline]
498 fn refund(
499 &self,
500 evm: &mut Self::Evm,
501 exec_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
502 eip7702_refund: i64,
503 ) {
504 let spec = evm.ctx().cfg().spec().into();
505 post_execution::refund(spec, exec_result.gas_mut(), eip7702_refund)
506 }
507
508 /// Returns unused gas costs to the transaction sender's account.
509 #[inline]
510 fn reimburse_caller(
511 &self,
512 evm: &mut Self::Evm,
513 exec_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
514 ) -> Result<(), Self::Error> {
515 post_execution::reimburse_caller(evm.ctx(), exec_result.gas(), U256::ZERO)
516 .map_err(From::from)
517 }
518
519 /// Transfers transaction fees to the block beneficiary's account.
520 #[inline]
521 fn reward_beneficiary(
522 &self,
523 evm: &mut Self::Evm,
524 exec_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
525 ) -> Result<(), Self::Error> {
526 post_execution::reward_beneficiary(evm.ctx(), exec_result.gas()).map_err(From::from)
527 }
528
529 /// Processes the final execution output.
530 ///
531 /// This method, retrieves the final state from the journal, converts internal results to the external output format.
532 /// Internal state is cleared and EVM is prepared for the next transaction.
533 #[inline]
534 fn execution_result(
535 &mut self,
536 evm: &mut Self::Evm,
537 result: <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
538 result_gas: ResultGas,
539 ) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
540 take_error::<Self::Error, _>(evm.ctx().error())?;
541
542 let exec_result = post_execution::output(evm.ctx(), result, result_gas);
543
544 // commit transaction
545 evm.ctx().journal_mut().commit_tx();
546 evm.ctx().local_mut().clear();
547 evm.frame_stack().clear();
548
549 Ok(exec_result)
550 }
551
552 /// Handles cleanup when an error occurs during execution.
553 ///
554 /// Ensures the journal state is properly cleared before propagating the error.
555 /// On happy path journal is cleared in [`Handler::execution_result`] method.
556 #[inline]
557 fn catch_error(
558 &self,
559 evm: &mut Self::Evm,
560 error: Self::Error,
561 ) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
562 // clean up local context. Initcode cache needs to be discarded.
563 evm.ctx().local_mut().clear();
564 evm.ctx().journal_mut().discard_tx();
565 evm.frame_stack().clear();
566 Err(error)
567 }
568}