Skip to main content

revm_inspector/
handler.rs

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