Skip to main content

revm_inspector/
handler.rs

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