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