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