Skip to main content

revm_inspector/
handler.rs

1use crate::{Inspector, InspectorEvmTr, JournalExt};
2use context::{result::ExecutionResult, Cfg, ContextTr, JournalEntry, JournalTr, Transaction};
3use handler::{
4    evm::FrameTr, post_execution::build_result_gas, EvmTr, FrameResult, Handler, ItemOrResult,
5};
6use interpreter::{
7    instructions::{GasTable, InstructionTable},
8    interpreter_types::{Jumps, LoopControl},
9    FrameInput, Host, InitialAndFloorGas, InstructionResult, Interpreter, InterpreterAction,
10    InterpreterTypes,
11};
12use primitives::hints_util::cold_path;
13use state::bytecode::opcode;
14
15/// Trait that extends [`Handler`] with inspection functionality.
16///
17/// Similar how [`Handler::run`] method serves as the entry point,
18/// [`InspectorHandler::inspect_run`] method serves as the entry point for inspection.
19/// For system calls, [`InspectorHandler::inspect_run_system_call`] provides inspection
20/// support similar to [`Handler::run_system_call`].
21///
22/// Notice that when inspection is run it skips few functions from handler, this can be
23/// a problem if custom EVM is implemented and some of skipped functions have changed logic.
24/// For custom EVM, those changed functions would need to be also changed in [`InspectorHandler`].
25///
26/// List of functions that are skipped in [`InspectorHandler`]:
27/// * [`Handler::run`] replaced with [`InspectorHandler::inspect_run`]
28/// * [`Handler::run_without_catch_error`] replaced with [`InspectorHandler::inspect_run_without_catch_error`]
29/// * [`Handler::execution`] replaced with [`InspectorHandler::inspect_execution`]
30/// * [`Handler::run_exec_loop`] replaced with [`InspectorHandler::inspect_run_exec_loop`]
31///   * `run_exec_loop` calls `inspect_frame_init` and `inspect_frame_run` that call inspector inside.
32/// * [`Handler::run_system_call`] replaced with [`InspectorHandler::inspect_run_system_call`]
33pub trait InspectorHandler: Handler
34where
35    Self::Evm:
36        InspectorEvmTr<Inspector: Inspector<<<Self as Handler>::Evm as EvmTr>::Context, Self::IT>>,
37{
38    /// The interpreter types used by this handler.
39    type IT: InterpreterTypes;
40
41    /// Entry point for inspection.
42    ///
43    /// This method is acts as [`Handler::run`] method for inspection.
44    fn inspect_run(
45        &mut self,
46        evm: &mut Self::Evm,
47    ) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
48        match self.inspect_run_without_catch_error(evm) {
49            Ok(output) => Ok(output),
50            Err(e) => self.catch_error(evm, e),
51        }
52    }
53
54    /// Run inspection without catching error.
55    ///
56    /// This method is acts as [`Handler::run_without_catch_error`] method for inspection.
57    fn inspect_run_without_catch_error(
58        &mut self,
59        evm: &mut Self::Evm,
60    ) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
61        let mut init_and_floor_gas = self.validate(evm)?;
62        // pre_execution now applies the EIP-7702 state gas refund split to init_and_floor_gas
63        // and returns the regular refund portion
64        let eip7702_regular_refund = self.pre_execution(evm, &mut init_and_floor_gas)? as i64;
65
66        let mut frame_result = self.inspect_execution(evm, &init_and_floor_gas)?;
67        let result_gas = self.post_execution(
68            evm,
69            &mut frame_result,
70            init_and_floor_gas,
71            eip7702_regular_refund,
72        )?;
73        self.execution_result(evm, frame_result, result_gas)
74    }
75
76    /// Run execution loop with inspection support
77    ///
78    /// This method acts as [`Handler::execution`] method for inspection.
79    fn inspect_execution(
80        &mut self,
81        evm: &mut Self::Evm,
82        init_and_floor_gas: &InitialAndFloorGas,
83    ) -> Result<FrameResult, Self::Error> {
84        // Compute the regular gas budget and EIP-8037 reservoir for the first frame.
85        let (gas_limit, reservoir) = init_and_floor_gas.initial_gas_and_reservoir(
86            evm.ctx().tx().gas_limit(),
87            evm.ctx().cfg().tx_gas_limit_cap(),
88        );
89        let first_frame_input = self.first_frame_input(evm, gas_limit, reservoir)?;
90
91        // Run execution loop
92        let mut frame_result = self.inspect_run_exec_loop(evm, first_frame_input)?;
93
94        // Handle last frame result
95        self.last_frame_result(evm, reservoir, &mut frame_result)?;
96        Ok(frame_result)
97    }
98
99    /* FRAMES */
100
101    /// Run inspection on execution loop.
102    ///
103    /// This method acts as [`Handler::run_exec_loop`] method for inspection.
104    ///
105    /// It will call:
106    /// * [`Inspector::call`],[`Inspector::create`] to inspect call, create and eofcreate.
107    /// * [`Inspector::call_end`],[`Inspector::create_end`] to inspect call, create and eofcreate end.
108    /// * [`Inspector::initialize_interp`] to inspect initialized interpreter.
109    fn inspect_run_exec_loop(
110        &mut self,
111        evm: &mut Self::Evm,
112        first_frame_input: <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameInit,
113    ) -> Result<FrameResult, Self::Error> {
114        let res = evm.inspect_frame_init(first_frame_input)?;
115
116        if let ItemOrResult::Result(frame_result) = res {
117            return Ok(frame_result);
118        }
119
120        loop {
121            let call_or_result = evm.inspect_frame_run()?;
122
123            let result = match call_or_result {
124                ItemOrResult::Item(init) => {
125                    match evm.inspect_frame_init(init)? {
126                        ItemOrResult::Item(_) => {
127                            continue;
128                        }
129                        // Do not pop the frame since no new frame was created
130                        ItemOrResult::Result(result) => result,
131                    }
132                }
133                ItemOrResult::Result(result) => result,
134            };
135
136            if let Some(result) = evm.frame_return_result(result)? {
137                return Ok(result);
138            }
139        }
140    }
141
142    /// Run system call with inspection support.
143    ///
144    /// This method acts as [`Handler::run_system_call`] method for inspection.
145    /// Similar to [`InspectorHandler::inspect_run`] but skips validation and pre-execution phases,
146    /// going directly to execution with inspection support.
147    fn inspect_run_system_call(
148        &mut self,
149        evm: &mut Self::Evm,
150    ) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
151        // dummy values that are not used.
152        let init_and_floor_gas = InitialAndFloorGas::new(0, 0);
153        // call execution with inspection and then output.
154        match self
155            .inspect_execution(evm, &init_and_floor_gas)
156            .and_then(|exec_result| {
157                // System calls have no intrinsic gas; build ResultGas from frame result.
158                let gas = exec_result.gas();
159                let result_gas = build_result_gas(false, gas, init_and_floor_gas);
160                self.execution_result(evm, exec_result, result_gas)
161            }) {
162            out @ Ok(_) => out,
163            Err(e) => self.catch_error(evm, e),
164        }
165    }
166}
167
168/// Handles the start of a frame by calling the appropriate inspector method.
169pub fn frame_start<CTX, INTR: InterpreterTypes>(
170    context: &mut CTX,
171    inspector: &mut impl Inspector<CTX, INTR, FrameInput, FrameResult>,
172    frame_input: &mut FrameInput,
173) -> Option<FrameResult> {
174    // Generic hook before variant dispatch
175    if let Some(result) = inspector.frame_start(context, frame_input) {
176        return Some(result);
177    }
178    // Variant-specific dispatch
179    match frame_input {
180        FrameInput::Call(i) => {
181            if let Some(output) = inspector.call(context, i) {
182                return Some(FrameResult::Call(output));
183            }
184        }
185        FrameInput::Create(i) => {
186            if let Some(output) = inspector.create(context, i) {
187                return Some(FrameResult::Create(output));
188            }
189        }
190        FrameInput::Empty => unreachable!(),
191    }
192    None
193}
194
195/// Handles the end of a frame by calling the appropriate inspector method.
196pub fn frame_end<CTX, INTR: InterpreterTypes>(
197    context: &mut CTX,
198    inspector: &mut impl Inspector<CTX, INTR, FrameInput, FrameResult>,
199    frame_input: &FrameInput,
200    frame_output: &mut FrameResult,
201) {
202    // Variant-specific dispatch first
203    match frame_output {
204        FrameResult::Call(outcome) => {
205            let FrameInput::Call(i) = frame_input else {
206                panic!("FrameInput::Call expected {frame_input:?}");
207            };
208            inspector.call_end(context, i, outcome);
209        }
210        FrameResult::Create(outcome) => {
211            let FrameInput::Create(i) = frame_input else {
212                panic!("FrameInput::Create expected {frame_input:?}");
213            };
214            inspector.create_end(context, i, outcome);
215        }
216    }
217    // Generic hook after variant dispatch
218    inspector.frame_end(context, frame_input, frame_output);
219}
220
221/// Run Interpreter loop with inspection support.
222///
223/// This function is used to inspect the Interpreter loop.
224/// It will call [`Inspector::step`] and [`Inspector::step_end`] after each instruction.
225/// And [`Inspector::log`],[`Inspector::selfdestruct`] for each log and selfdestruct instruction.
226pub fn inspect_instructions<CTX, IT>(
227    context: &mut CTX,
228    interpreter: &mut Interpreter<IT>,
229    mut inspector: impl Inspector<CTX, IT>,
230    instructions: &InstructionTable<IT, CTX>,
231    gas_table: &GasTable,
232) -> InterpreterAction
233where
234    CTX: ContextTr<Journal: JournalExt> + Host,
235    IT: InterpreterTypes,
236{
237    loop {
238        inspector.step(interpreter, context);
239        if interpreter.bytecode.is_end() {
240            cold_path();
241            break;
242        }
243
244        let opcode = interpreter.bytecode.opcode();
245        if let Err(e) = interpreter.step(instructions, gas_table, context) {
246            cold_path();
247            if interpreter.bytecode.action().is_none() {
248                interpreter.halt(e);
249            }
250        }
251
252        if (opcode::LOG0..=opcode::LOG4).contains(&opcode) {
253            inspect_log(interpreter, context, &mut inspector);
254        }
255
256        inspector.step_end(interpreter, context);
257
258        if interpreter.bytecode.is_end() {
259            cold_path();
260            break;
261        }
262    }
263
264    let next_action = interpreter.take_next_action();
265
266    // Handle selfdestruct.
267    if let InterpreterAction::Return(result) = &next_action {
268        if result.result == InstructionResult::SelfDestruct {
269            inspect_selfdestruct(context, &mut inspector);
270        }
271    }
272
273    next_action
274}
275
276#[inline(never)]
277#[cold]
278fn inspect_log<CTX, IT>(
279    interpreter: &mut Interpreter<IT>,
280    context: &mut CTX,
281    inspector: &mut impl Inspector<CTX, IT>,
282) where
283    CTX: ContextTr<Journal: JournalExt> + Host,
284    IT: InterpreterTypes,
285{
286    // `LOG*` instruction reverted.
287    if interpreter
288        .bytecode
289        .action()
290        .as_ref()
291        .is_some_and(|x| x.is_return())
292    {
293        return;
294    }
295
296    let log = context.journal_mut().logs().last().unwrap().clone();
297    inspector.log_full(interpreter, context, log);
298}
299
300#[inline(never)]
301#[cold]
302fn inspect_selfdestruct<CTX, IT>(context: &mut CTX, inspector: &mut impl Inspector<CTX, IT>)
303where
304    CTX: ContextTr<Journal: JournalExt> + Host,
305    IT: InterpreterTypes,
306{
307    if let Some(
308        JournalEntry::AccountDestroyed {
309            address: contract,
310            target: to,
311            had_balance: balance,
312            ..
313        }
314        | JournalEntry::BalanceTransfer {
315            from: contract,
316            to,
317            balance,
318            ..
319        },
320    ) = context.journal_mut().journal().last()
321    {
322        inspector.selfdestruct(*contract, *to, *balance);
323    }
324}