revm_handler/handler.rs
1use crate::{
2 evm::FrameTr,
3 execution, post_execution,
4 pre_execution::{self, apply_eip7702_auth_list},
5 validation, EvmTr, FrameResult, ItemOrResult,
6};
7use context::{
8 result::{ExecutionResult, FromStringError},
9 LocalContextTr,
10};
11use context_interface::{
12 context::{take_error, ContextError},
13 result::{HaltReasonTr, InvalidHeader, InvalidTransaction},
14 Cfg, ContextTr, Database, JournalTr, Transaction,
15};
16use interpreter::{interpreter_action::FrameInit, Gas, InitialAndFloorGas, SharedMemory};
17use primitives::U256;
18use state::Bytecode;
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| self.execution_result(evm, exec_result))
133 {
134 out @ Ok(_) => out,
135 Err(e) => self.catch_error(evm, e),
136 }
137 }
138
139 /// Called by [`Handler::run`] to execute the core handler logic.
140 ///
141 /// Executes the four phases in sequence: [Handler::validate],
142 /// [Handler::pre_execution], [Handler::execution], [Handler::post_execution].
143 ///
144 /// Returns any errors without catching them or calling [`Handler::catch_error`].
145 #[inline]
146 fn run_without_catch_error(
147 &mut self,
148 evm: &mut Self::Evm,
149 ) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
150 let init_and_floor_gas = self.validate(evm)?;
151 let eip7702_refund = self.pre_execution(evm)? as i64;
152 let mut exec_result = self.execution(evm, &init_and_floor_gas)?;
153 self.post_execution(evm, &mut exec_result, init_and_floor_gas, eip7702_refund)?;
154
155 // Prepare the output
156 self.execution_result(evm, exec_result)
157 }
158
159 /// Validates the execution environment and transaction parameters.
160 ///
161 /// Calculates initial and floor gas requirements and verifies they are covered by the gas limit.
162 ///
163 /// Validation against state is done later in pre-execution phase in deduct_caller function.
164 #[inline]
165 fn validate(&self, evm: &mut Self::Evm) -> Result<InitialAndFloorGas, Self::Error> {
166 self.validate_env(evm)?;
167 self.validate_initial_tx_gas(evm)
168 }
169
170 /// Prepares the EVM state for execution.
171 ///
172 /// Loads the beneficiary account (EIP-3651: Warm COINBASE) and all accounts/storage from the access list (EIP-2929).
173 ///
174 /// Deducts the maximum possible fee from the caller's balance.
175 ///
176 /// For EIP-7702 transactions, applies the authorization list and delegates successful authorizations.
177 /// Returns the gas refund amount from EIP-7702. Authorizations are applied before execution begins.
178 #[inline]
179 fn pre_execution(&self, evm: &mut Self::Evm) -> Result<u64, Self::Error> {
180 self.validate_against_state_and_deduct_caller(evm)?;
181 self.load_accounts(evm)?;
182
183 let gas = self.apply_eip7702_auth_list(evm)?;
184 Ok(gas)
185 }
186
187 /// Creates and executes the initial frame, then processes the execution loop.
188 ///
189 /// Always calls [Handler::last_frame_result] to handle returned gas from the call.
190 #[inline]
191 fn execution(
192 &mut self,
193 evm: &mut Self::Evm,
194 init_and_floor_gas: &InitialAndFloorGas,
195 ) -> Result<FrameResult, Self::Error> {
196 let gas_limit = evm.ctx().tx().gas_limit() - init_and_floor_gas.initial_gas;
197 // Create first frame action
198 let first_frame_input = self.first_frame_input(evm, gas_limit)?;
199
200 // Run execution loop
201 let mut frame_result = self.run_exec_loop(evm, first_frame_input)?;
202
203 // Handle last frame result
204 self.last_frame_result(evm, &mut frame_result)?;
205 Ok(frame_result)
206 }
207
208 /// Handles the final steps of transaction execution.
209 ///
210 /// Calculates final refunds and validates the gas floor (EIP-7623) to ensure minimum gas is spent.
211 /// After EIP-7623, at least floor gas must be consumed.
212 ///
213 /// Reimburses unused gas to the caller and rewards the beneficiary with transaction fees.
214 /// The effective gas price determines rewards, with the base fee being burned.
215 ///
216 /// Finally, finalizes output by returning the journal state and clearing internal state
217 /// for the next execution.
218 #[inline]
219 fn post_execution(
220 &self,
221 evm: &mut Self::Evm,
222 exec_result: &mut FrameResult,
223 init_and_floor_gas: InitialAndFloorGas,
224 eip7702_gas_refund: i64,
225 ) -> Result<(), Self::Error> {
226 // Calculate final refund and add EIP-7702 refund to gas.
227 self.refund(evm, exec_result, eip7702_gas_refund);
228 // Ensure gas floor is met and minimum floor gas is spent.
229 // if `cfg.is_eip7623_disabled` is true, floor gas will be set to zero
230 self.eip7623_check_gas_floor(evm, exec_result, init_and_floor_gas);
231 // Return unused gas to caller
232 self.reimburse_caller(evm, exec_result)?;
233 // Pay transaction fees to beneficiary
234 self.reward_beneficiary(evm, exec_result)?;
235 Ok(())
236 }
237
238 /* VALIDATION */
239
240 /// Validates block, transaction and configuration fields.
241 ///
242 /// Performs all validation checks that can be done without loading state.
243 /// For example, verifies transaction gas limit is below block gas limit.
244 #[inline]
245 fn validate_env(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> {
246 validation::validate_env(evm.ctx())
247 }
248
249 /// Calculates initial gas costs based on transaction type and input data.
250 ///
251 /// Includes additional costs for access list and authorization list.
252 ///
253 /// Verifies the initial cost does not exceed the transaction gas limit.
254 #[inline]
255 fn validate_initial_tx_gas(
256 &self,
257 evm: &mut Self::Evm,
258 ) -> Result<InitialAndFloorGas, Self::Error> {
259 let ctx = evm.ctx_ref();
260 validation::validate_initial_tx_gas(
261 ctx.tx(),
262 ctx.cfg().spec().into(),
263 ctx.cfg().is_eip7623_disabled(),
264 )
265 .map_err(From::from)
266 }
267
268 /* PRE EXECUTION */
269
270 /// Loads access list and beneficiary account, marking them as warm in the [`context::Journal`].
271 #[inline]
272 fn load_accounts(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> {
273 pre_execution::load_accounts(evm)
274 }
275
276 /// Processes the authorization list, validating authority signatures, nonces and chain IDs.
277 /// Applies valid authorizations to accounts.
278 ///
279 /// Returns the gas refund amount specified by EIP-7702.
280 #[inline]
281 fn apply_eip7702_auth_list(&self, evm: &mut Self::Evm) -> Result<u64, Self::Error> {
282 apply_eip7702_auth_list(evm.ctx_mut())
283 }
284
285 /// Deducts the maximum possible fee from caller's balance.
286 ///
287 /// If cfg.is_balance_check_disabled, this method will add back enough funds to ensure that
288 /// the caller's balance is at least tx.value() before returning. Note that the amount of funds
289 /// added back in this case may exceed the maximum fee.
290 ///
291 /// Unused fees are returned to caller after execution completes.
292 #[inline]
293 fn validate_against_state_and_deduct_caller(
294 &self,
295 evm: &mut Self::Evm,
296 ) -> Result<(), Self::Error> {
297 pre_execution::validate_against_state_and_deduct_caller(evm.ctx())
298 }
299
300 /* EXECUTION */
301
302 /// Creates initial frame input using transaction parameters, gas limit and configuration.
303 #[inline]
304 fn first_frame_input(
305 &mut self,
306 evm: &mut Self::Evm,
307 gas_limit: u64,
308 ) -> Result<FrameInit, Self::Error> {
309 let ctx = evm.ctx_mut();
310 let mut memory = SharedMemory::new_with_buffer(ctx.local().shared_memory_buffer().clone());
311 memory.set_memory_limit(ctx.cfg().memory_limit());
312
313 let (tx, journal) = ctx.tx_journal_mut();
314 let bytecode = if let Some(&to) = tx.kind().to() {
315 let account = &journal.load_account_with_code(to)?.info;
316
317 if let Some(delegated_address) =
318 account.code.as_ref().and_then(Bytecode::eip7702_address)
319 {
320 let account = &journal.load_account_with_code(delegated_address)?.info;
321 Some((
322 account.code.clone().unwrap_or_default(),
323 account.code_hash(),
324 ))
325 } else {
326 Some((
327 account.code.clone().unwrap_or_default(),
328 account.code_hash(),
329 ))
330 }
331 } else {
332 None
333 };
334
335 Ok(FrameInit {
336 depth: 0,
337 memory,
338 frame_input: execution::create_init_frame(tx, bytecode, gas_limit),
339 })
340 }
341
342 /// Processes the result of the initial call and handles returned gas.
343 #[inline]
344 fn last_frame_result(
345 &mut self,
346 evm: &mut Self::Evm,
347 frame_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
348 ) -> Result<(), Self::Error> {
349 let instruction_result = frame_result.interpreter_result().result;
350 let gas = frame_result.gas_mut();
351 let remaining = gas.remaining();
352 let refunded = gas.refunded();
353
354 // Spend the gas limit. Gas is reimbursed when the tx returns successfully.
355 *gas = Gas::new_spent(evm.ctx().tx().gas_limit());
356
357 if instruction_result.is_ok_or_revert() {
358 gas.erase_cost(remaining);
359 }
360
361 if instruction_result.is_ok() {
362 gas.record_refund(refunded);
363 }
364 Ok(())
365 }
366
367 /* FRAMES */
368
369 /// Executes the main frame processing loop.
370 ///
371 /// This loop manages the frame stack, processing each frame until execution completes.
372 /// For each iteration:
373 /// 1. Calls the current frame
374 /// 2. Handles the returned frame input or result
375 /// 3. Creates new frames or propagates results as needed
376 #[inline]
377 fn run_exec_loop(
378 &mut self,
379 evm: &mut Self::Evm,
380 first_frame_input: <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameInit,
381 ) -> Result<FrameResult, Self::Error> {
382 let res = evm.frame_init(first_frame_input)?;
383
384 if let ItemOrResult::Result(frame_result) = res {
385 return Ok(frame_result);
386 }
387
388 loop {
389 let call_or_result = evm.frame_run()?;
390
391 let result = match call_or_result {
392 ItemOrResult::Item(init) => {
393 match evm.frame_init(init)? {
394 ItemOrResult::Item(_) => {
395 continue;
396 }
397 // Do not pop the frame since no new frame was created
398 ItemOrResult::Result(result) => result,
399 }
400 }
401 ItemOrResult::Result(result) => result,
402 };
403
404 if let Some(result) = evm.frame_return_result(result)? {
405 return Ok(result);
406 }
407 }
408 }
409
410 /* POST EXECUTION */
411
412 /// Validates that the minimum gas floor requirements are satisfied.
413 ///
414 /// Ensures that at least the floor gas amount has been consumed during execution.
415 #[inline]
416 fn eip7623_check_gas_floor(
417 &self,
418 _evm: &mut Self::Evm,
419 exec_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
420 init_and_floor_gas: InitialAndFloorGas,
421 ) {
422 post_execution::eip7623_check_gas_floor(exec_result.gas_mut(), init_and_floor_gas)
423 }
424
425 /// Calculates the final gas refund amount, including any EIP-7702 refunds.
426 #[inline]
427 fn refund(
428 &self,
429 evm: &mut Self::Evm,
430 exec_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
431 eip7702_refund: i64,
432 ) {
433 let spec = evm.ctx().cfg().spec().into();
434 post_execution::refund(spec, exec_result.gas_mut(), eip7702_refund)
435 }
436
437 /// Returns unused gas costs to the transaction sender's account.
438 #[inline]
439 fn reimburse_caller(
440 &self,
441 evm: &mut Self::Evm,
442 exec_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
443 ) -> Result<(), Self::Error> {
444 post_execution::reimburse_caller(evm.ctx(), exec_result.gas(), U256::ZERO)
445 .map_err(From::from)
446 }
447
448 /// Transfers transaction fees to the block beneficiary's account.
449 #[inline]
450 fn reward_beneficiary(
451 &self,
452 evm: &mut Self::Evm,
453 exec_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
454 ) -> Result<(), Self::Error> {
455 post_execution::reward_beneficiary(evm.ctx(), exec_result.gas()).map_err(From::from)
456 }
457
458 /// Processes the final execution output.
459 ///
460 /// This method, retrieves the final state from the journal, converts internal results to the external output format.
461 /// Internal state is cleared and EVM is prepared for the next transaction.
462 #[inline]
463 fn execution_result(
464 &mut self,
465 evm: &mut Self::Evm,
466 result: <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
467 ) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
468 take_error::<Self::Error, _>(evm.ctx().error())?;
469
470 let exec_result = post_execution::output(evm.ctx(), result);
471
472 // commit transaction
473 evm.ctx().journal_mut().commit_tx();
474 evm.ctx().local_mut().clear();
475 evm.frame_stack().clear();
476
477 Ok(exec_result)
478 }
479
480 /// Handles cleanup when an error occurs during execution.
481 ///
482 /// Ensures the journal state is properly cleared before propagating the error.
483 /// On happy path journal is cleared in [`Handler::execution_result`] method.
484 #[inline]
485 fn catch_error(
486 &self,
487 evm: &mut Self::Evm,
488 error: Self::Error,
489 ) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
490 // clean up local context. Initcode cache needs to be discarded.
491 evm.ctx().local_mut().clear();
492 evm.ctx().journal_mut().discard_tx();
493 evm.frame_stack().clear();
494 Err(error)
495 }
496}