Skip to main content

anvil/eth/backend/mem/
inspector.rs

1//! Anvil specific [`revm::Inspector`] implementation
2
3use crate::eth::macros::node_info;
4use alloy_primitives::{Address, B256, Log, LogData, U256};
5use alloy_sol_types::SolValue;
6use foundry_evm::{
7    call_inspectors,
8    decode::decode_console_logs,
9    inspectors::{LogCollector, TracingInspector},
10    traces::{
11        CallTraceDecoder, CallTraceNode, SparsedTraceArena, TracingInspectorConfig,
12        render_trace_arena_inner,
13    },
14};
15use revm::{
16    Inspector,
17    context::{ContextTr, JournalTr},
18    inspector::JournalExt,
19    interpreter::{
20        CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, CreateScheme,
21        Interpreter, interpreter::EthInterpreter,
22    },
23};
24use revm_inspectors::transfer::{TRANSFER_EVENT_TOPIC, TRANSFER_LOG_EMITTER, TransferInspector};
25use std::sync::Arc;
26
27/// The [`revm::Inspector`] used when transacting in the evm
28#[derive(Clone, Debug, Default)]
29pub struct AnvilInspector {
30    /// Collects all traces
31    pub tracer: Option<TracingInspector>,
32    /// Collects all `console.sol` logs
33    pub log_collector: Option<LogCollector>,
34    /// Collects all internal ETH transfers as ERC20 transfer events.
35    pub transfer: Option<TransferInspector>,
36    /// Collects canonical and synthetic transfer logs for an `eth_simulateV1` response.
37    simulation_logs: Option<SimulationLogCollector>,
38}
39
40#[derive(Clone, Debug)]
41struct SimulationLog {
42    log: Log,
43    index: u64,
44    canonical: bool,
45}
46
47/// Collects simulation response logs without inserting synthetic logs into EVM state.
48#[derive(Clone, Debug, Default)]
49struct SimulationLogCollector {
50    logs: Vec<SimulationLog>,
51    checkpoints: Vec<usize>,
52    next_index: u64,
53    trace_transfers: bool,
54    journal_log_count: usize,
55}
56
57impl SimulationLogCollector {
58    fn push_log(&mut self, log: Log, canonical: bool) {
59        self.logs.push(SimulationLog { log, index: self.next_index, canonical });
60        self.next_index += 1;
61    }
62
63    fn push_canonical_log(&mut self, log: Log, journal_log_count: usize) {
64        self.push_log(log, true);
65        self.journal_log_count = journal_log_count;
66    }
67
68    fn sync_journal_logs(&mut self, logs: &[Log]) {
69        self.journal_log_count = self.journal_log_count.min(logs.len());
70        for log in &logs[self.journal_log_count..] {
71            self.push_log(log.clone(), true);
72        }
73        self.journal_log_count = logs.len();
74    }
75
76    fn push_transfer(&mut self, from: Address, to: Address, value: U256) {
77        if !self.trace_transfers || value.is_zero() {
78            return;
79        }
80        self.push_log(
81            Log {
82                address: TRANSFER_LOG_EMITTER,
83                data: LogData::new_unchecked(
84                    vec![
85                        TRANSFER_EVENT_TOPIC,
86                        B256::from_slice(&from.abi_encode()),
87                        B256::from_slice(&to.abi_encode()),
88                    ],
89                    value.abi_encode().into(),
90                ),
91            },
92            false,
93        );
94    }
95
96    fn frame_start(&mut self) {
97        self.checkpoints.push(self.logs.len());
98    }
99
100    fn frame_end(&mut self, success: bool, journal_log_count: usize) {
101        let checkpoint = self.checkpoints.pop().expect("execution frame checkpoint exists");
102        if !success {
103            self.logs.truncate(checkpoint);
104        }
105        self.journal_log_count = journal_log_count;
106    }
107
108    fn append_remaining_canonical_logs(&mut self, canonical_logs: &[Log]) {
109        let mut canonical_logs = canonical_logs.iter();
110        for collected in self.logs.iter().filter(|log| log.canonical) {
111            let canonical =
112                canonical_logs.next().expect("collected canonical log exists in result");
113            assert_eq!(&collected.log, canonical, "collected canonical logs preserve ordering");
114        }
115        for log in canonical_logs {
116            self.push_log(log.clone(), true);
117        }
118    }
119}
120
121/// Configuration for per-transaction inspector lifecycle.
122#[derive(Clone, Debug)]
123pub struct InspectorTxConfig {
124    /// Whether to print traces to stdout.
125    pub print_traces: bool,
126    /// Whether to print logs to stdout.
127    pub print_logs: bool,
128    /// Whether to enable step-level tracing (with state diffs).
129    pub enable_steps_tracing: bool,
130    /// Decoder for populating trace labels.
131    pub call_trace_decoder: Arc<CallTraceDecoder>,
132}
133
134impl AnvilInspector {
135    /// Finish a transaction: print traces/logs, drain the tracer, and reset for the next tx.
136    ///
137    /// Returns the collected call trace nodes from the finished transaction.
138    pub fn finish_transaction(&mut self, config: &InspectorTxConfig) -> Vec<CallTraceNode> {
139        // Print before draining so the tracer is still populated.
140        if config.print_traces {
141            self.print_traces(config.call_trace_decoder.clone());
142        }
143        self.print_logs();
144
145        let traces = self.tracer.take().map(|t| t.into_traces().into_nodes()).unwrap_or_default();
146
147        self.reset_transaction(config);
148
149        traces
150    }
151
152    /// Discards a transaction's traces/logs and resets the inspector without printing them.
153    pub fn discard_transaction(&mut self, config: &InspectorTxConfig) {
154        self.reset_transaction(config);
155    }
156
157    /// Resets per-transaction collectors for the next transaction.
158    fn reset_transaction(&mut self, config: &InspectorTxConfig) {
159        // Reinstall tracer for next tx.
160        let tracing_config = if config.enable_steps_tracing {
161            TracingInspectorConfig::all().with_state_diffs()
162        } else {
163            TracingInspectorConfig::all().set_steps(false)
164        };
165        self.tracer = Some(TracingInspector::new(tracing_config));
166
167        // Reset log collector for next tx.
168        self.log_collector = config.print_logs.then(|| LogCollector::Capture { logs: Vec::new() });
169    }
170
171    /// Called after the inspecting the evm
172    ///
173    /// This will log all `console.sol` logs
174    pub fn print_logs(&self) {
175        if let Some(LogCollector::Capture { logs }) = &self.log_collector {
176            print_logs(logs);
177        }
178    }
179
180    /// Consumes the type and prints the traces.
181    pub fn into_print_traces(mut self, decoder: Arc<CallTraceDecoder>) {
182        if let Some(a) = self.tracer.take() {
183            print_traces(a, decoder);
184        }
185    }
186
187    /// Called after the inspecting the evm
188    /// This will log all traces
189    pub fn print_traces(&self, decoder: Arc<CallTraceDecoder>) {
190        if let Some(a) = self.tracer.clone() {
191            print_traces(a, decoder);
192        }
193    }
194
195    /// Configures the `Tracer` [`revm::Inspector`]
196    pub fn with_tracing(mut self) -> Self {
197        self.tracer = Some(TracingInspector::new(TracingInspectorConfig::all().set_steps(false)));
198        self
199    }
200
201    /// Configures the `TracingInspector` [`revm::Inspector`]
202    pub fn with_tracing_config(mut self, config: TracingInspectorConfig) -> Self {
203        self.tracer = Some(TracingInspector::new(config));
204        self
205    }
206
207    /// Enables steps recording for `Tracer`.
208    pub fn with_steps_tracing(mut self) -> Self {
209        self.tracer = Some(TracingInspector::new(TracingInspectorConfig::all().with_state_diffs()));
210        self
211    }
212
213    /// Configures the `Tracer` [`revm::Inspector`] with a log collector
214    pub fn with_log_collector(mut self) -> Self {
215        self.log_collector = Some(LogCollector::Capture { logs: Vec::new() });
216        self
217    }
218
219    /// Configures the `Tracer` [`revm::Inspector`] with a transfer event collector
220    pub fn with_transfers(mut self) -> Self {
221        self.transfer = Some(TransferInspector::new(false).with_logs(true));
222        self
223    }
224
225    /// Collects canonical and synthetic transfer logs for an `eth_simulateV1` response.
226    pub fn with_simulation_logs(mut self, trace_transfers: bool) -> Self {
227        self.simulation_logs =
228            Some(SimulationLogCollector { trace_transfers, ..Default::default() });
229        self
230    }
231
232    /// Takes the collected `eth_simulateV1` response logs and attempted log count.
233    pub fn take_simulation_logs(
234        &mut self,
235        canonical_logs: &[Log],
236        success: bool,
237    ) -> Option<(Vec<(u64, Log)>, u64)> {
238        self.simulation_logs.take().map(|mut collector| {
239            if success {
240                collector.append_remaining_canonical_logs(canonical_logs);
241            } else {
242                // A top-level revert can discard logs without producing an enclosing call frame
243                // callback. Preserve the attempted count for subsequent log indices.
244                collector.logs.clear();
245            }
246            (
247                collector.logs.into_iter().map(|log| (log.index, log.log)).collect(),
248                collector.next_index,
249            )
250        })
251    }
252
253    /// Configures the `Tracer` [`revm::Inspector`] with a trace printer
254    pub fn with_trace_printer(mut self) -> Self {
255        self.tracer = Some(TracingInspector::new(TracingInspectorConfig::all().with_state_diffs()));
256        self
257    }
258}
259
260/// Prints the traces for the inspector
261///
262/// Caution: This blocks on call trace decoding
263///
264/// # Panics
265///
266/// If called outside tokio runtime
267fn print_traces(tracer: TracingInspector, decoder: Arc<CallTraceDecoder>) {
268    let arena = tokio::task::block_in_place(move || {
269        tokio::runtime::Handle::current().block_on(async move {
270            let mut arena = tracer.into_traces();
271            decoder.populate_traces(arena.nodes_mut()).await;
272            arena
273        })
274    });
275
276    let traces =
277        SparsedTraceArena { arena, ignored: Default::default(), diagnostics: Default::default() };
278    let trace = render_trace_arena_inner(&traces, false, true);
279    node_info!(Traces = %format!("\n{}", trace));
280}
281
282impl<CTX> Inspector<CTX, EthInterpreter> for AnvilInspector
283where
284    CTX: ContextTr<Journal: JournalExt>,
285{
286    fn initialize_interp(&mut self, interp: &mut Interpreter, ecx: &mut CTX) {
287        if let Some(collector) = &mut self.simulation_logs {
288            collector.sync_journal_logs(ecx.journal().logs());
289        }
290        call_inspectors!([&mut self.tracer], |inspector| {
291            inspector.initialize_interp(interp, ecx);
292        });
293    }
294
295    fn step(&mut self, interp: &mut Interpreter, ecx: &mut CTX) {
296        if let Some(collector) = &mut self.simulation_logs {
297            collector.sync_journal_logs(ecx.journal().logs());
298        }
299        call_inspectors!([&mut self.tracer], |inspector| {
300            inspector.step(interp, ecx);
301        });
302    }
303
304    fn step_end(&mut self, interp: &mut Interpreter, ecx: &mut CTX) {
305        call_inspectors!([&mut self.tracer], |inspector| {
306            inspector.step_end(interp, ecx);
307        });
308        if let Some(collector) = &mut self.simulation_logs {
309            collector.sync_journal_logs(ecx.journal().logs());
310        }
311    }
312
313    #[allow(clippy::redundant_clone)]
314    fn log(&mut self, ecx: &mut CTX, log: Log) {
315        call_inspectors!([&mut self.tracer, &mut self.log_collector], |inspector| {
316            inspector.log(ecx, log.clone());
317        });
318        if let Some(collector) = &mut self.simulation_logs {
319            collector.push_canonical_log(log, ecx.journal().logs().len());
320        }
321    }
322
323    #[allow(clippy::redundant_clone)]
324    fn log_full(&mut self, interp: &mut Interpreter, ecx: &mut CTX, log: Log) {
325        call_inspectors!([&mut self.tracer, &mut self.log_collector], |inspector| {
326            inspector.log_full(interp, ecx, log.clone());
327        });
328        if let Some(collector) = &mut self.simulation_logs {
329            collector.push_canonical_log(log, ecx.journal().logs().len());
330        }
331    }
332
333    fn call(&mut self, ecx: &mut CTX, inputs: &mut CallInputs) -> Option<CallOutcome> {
334        if let Some(collector) = &mut self.simulation_logs {
335            collector.sync_journal_logs(ecx.journal().logs());
336            collector.frame_start();
337            if matches!(inputs.scheme, CallScheme::Call)
338                && let Some(value) = inputs.transfer_value()
339            {
340                collector.push_transfer(inputs.transfer_from(), inputs.transfer_to(), value);
341            }
342        }
343        call_inspectors!(
344            #[ret]
345            [&mut self.tracer, &mut self.log_collector, &mut self.transfer],
346            |inspector| inspector.call(ecx, inputs).map(Some),
347        );
348        None
349    }
350
351    fn call_end(&mut self, ecx: &mut CTX, inputs: &CallInputs, outcome: &mut CallOutcome) {
352        if let Some(tracer) = &mut self.tracer {
353            tracer.call_end(ecx, inputs, outcome);
354        }
355        if let Some(collector) = &mut self.simulation_logs {
356            collector.sync_journal_logs(ecx.journal().logs());
357            collector.frame_end(outcome.instruction_result().is_ok(), ecx.journal().logs().len());
358        }
359    }
360
361    fn create(&mut self, ecx: &mut CTX, inputs: &mut CreateInputs) -> Option<CreateOutcome> {
362        if let Some(collector) = &mut self.simulation_logs {
363            collector.sync_journal_logs(ecx.journal().logs());
364            collector.frame_start();
365            if matches!(inputs.scheme(), CreateScheme::Create | CreateScheme::Create2 { .. })
366                && let Ok(account) = ecx.journal_mut().load_account(inputs.caller())
367            {
368                let address = inputs.created_address(account.data.info.nonce);
369                collector.push_transfer(inputs.caller(), address, inputs.value());
370            }
371        }
372        call_inspectors!(
373            #[ret]
374            [&mut self.tracer, &mut self.transfer],
375            |inspector| inspector.create(ecx, inputs).map(Some),
376        );
377        None
378    }
379
380    fn create_end(&mut self, ecx: &mut CTX, inputs: &CreateInputs, outcome: &mut CreateOutcome) {
381        if let Some(tracer) = &mut self.tracer {
382            tracer.create_end(ecx, inputs, outcome);
383        }
384        if let Some(collector) = &mut self.simulation_logs {
385            collector.sync_journal_logs(ecx.journal().logs());
386            collector.frame_end(
387                outcome.instruction_result().is_ok() && outcome.address.is_some(),
388                ecx.journal().logs().len(),
389            );
390        }
391    }
392
393    fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) {
394        call_inspectors!([&mut self.tracer, &mut self.transfer], |inspector| {
395            Inspector::<CTX, EthInterpreter>::selfdestruct(inspector, contract, target, value)
396        });
397        if let Some(collector) = &mut self.simulation_logs {
398            collector.push_transfer(contract, target, value);
399        }
400    }
401}
402
403/// Prints all the logs
404pub fn print_logs(logs: &[Log]) {
405    for log in decode_console_logs(logs) {
406        tracing::info!(target: crate::logging::EVM_CONSOLE_LOG_TARGET, "{}", log);
407    }
408}