alloy_evm/
tracing.rs

1//! Helpers for tracing.
2
3use crate::{Evm, IntoTxEnv};
4use core::{fmt::Debug, iter::Peekable};
5use revm::{
6    context::result::{ExecutionResult, ResultAndState},
7    state::EvmState,
8    DatabaseCommit,
9};
10
11/// A helper type for tracing transactions.
12#[derive(Debug, Clone)]
13pub struct TxTracer<E: Evm> {
14    evm: E,
15    fused_inspector: E::Inspector,
16}
17
18/// Container type for context exposed in [`TxTracer`].
19#[derive(Debug)]
20pub struct TracingCtx<'a, T, E: Evm> {
21    /// The transaction that was just executed.
22    pub tx: T,
23    /// Result of transaction execution.
24    pub result: ExecutionResult<E::HaltReason>,
25    /// State changes after transaction.
26    pub state: &'a EvmState,
27    /// Inspector state after transaction.
28    pub inspector: &'a mut E::Inspector,
29    /// Database used when executing the transaction, _before_ committing the state changes.
30    pub db: &'a mut E::DB,
31    /// Fused inspector.
32    fused_inspector: &'a E::Inspector,
33    /// Whether the inspector was fused.
34    was_fused: &'a mut bool,
35}
36
37impl<'a, T, E: Evm<Inspector: Clone>> TracingCtx<'a, T, E> {
38    /// Fuses the inspector and returns the current inspector state.
39    pub fn take_inspector(&mut self) -> E::Inspector {
40        *self.was_fused = true;
41        core::mem::replace(self.inspector, self.fused_inspector.clone())
42    }
43}
44
45impl<E: Evm<Inspector: Clone, DB: DatabaseCommit>> TxTracer<E> {
46    /// Creates a new [`TxTracer`] instance.
47    pub fn new(mut evm: E) -> Self {
48        Self { fused_inspector: evm.inspector_mut().clone(), evm }
49    }
50
51    fn fuse_inspector(&mut self) -> E::Inspector {
52        core::mem::replace(self.evm.inspector_mut(), self.fused_inspector.clone())
53    }
54
55    /// Executes a transaction, and returns its outcome along with the inspector state.
56    pub fn trace(
57        &mut self,
58        tx: impl IntoTxEnv<E::Tx>,
59    ) -> Result<TraceOutput<E::HaltReason, E::Inspector>, E::Error> {
60        let result = self.evm.transact_commit(tx);
61        let inspector = self.fuse_inspector();
62        Ok(TraceOutput { result: result?, inspector })
63    }
64
65    /// Executes multiple transactions, applies the closure to each transaction result, and returns
66    /// the outcomes.
67    #[expect(clippy::type_complexity)]
68    pub fn trace_many<Txs, T, F, O>(
69        &mut self,
70        txs: Txs,
71        mut f: F,
72    ) -> TracerIter<'_, E, Txs::IntoIter, impl FnMut(TracingCtx<'_, T, E>) -> Result<O, E::Error>>
73    where
74        T: IntoTxEnv<E::Tx> + Clone,
75        Txs: IntoIterator<Item = T>,
76        F: FnMut(TracingCtx<'_, Txs::Item, E>) -> O,
77    {
78        self.try_trace_many(txs, move |ctx| Ok(f(ctx)))
79    }
80
81    /// Same as [`TxTracer::trace_many`], but operates on closures returning [`Result`]s.
82    pub fn try_trace_many<Txs, T, F, O, Err>(
83        &mut self,
84        txs: Txs,
85        hook: F,
86    ) -> TracerIter<'_, E, Txs::IntoIter, F>
87    where
88        T: IntoTxEnv<E::Tx> + Clone,
89        Txs: IntoIterator<Item = T>,
90        F: FnMut(TracingCtx<'_, T, E>) -> Result<O, Err>,
91        Err: From<E::Error>,
92    {
93        TracerIter {
94            inner: self,
95            txs: txs.into_iter().peekable(),
96            hook,
97            skip_last_commit: true,
98            fuse: true,
99        }
100    }
101}
102
103/// Output of tracing a transaction.
104#[derive(Debug, Clone)]
105pub struct TraceOutput<H, I> {
106    /// Inner EVM output.
107    pub result: ExecutionResult<H>,
108    /// Inspector state at the end of the execution.
109    pub inspector: I,
110}
111
112/// Iterator used by tracer.
113#[derive(derive_more::Debug)]
114#[debug(bound(E::Inspector: Debug))]
115pub struct TracerIter<'a, E: Evm, Txs: Iterator, F> {
116    inner: &'a mut TxTracer<E>,
117    txs: Peekable<Txs>,
118    hook: F,
119    skip_last_commit: bool,
120    fuse: bool,
121}
122
123impl<E: Evm, Txs: Iterator, F> TracerIter<'_, E, Txs, F> {
124    /// Flips the `skip_last_commit` flag thus making sure all transaction are committed.
125    ///
126    /// We are skipping last commit by default as it's expected that when tracing users are mostly
127    /// interested in tracer output rather than in a state after it.
128    pub const fn commit_last_tx(mut self) -> Self {
129        self.skip_last_commit = false;
130        self
131    }
132
133    /// Disables inspector fusing on every transaction and expects user to fuse it manually.
134    pub const fn no_fuse(mut self) -> Self {
135        self.fuse = false;
136        self
137    }
138}
139
140impl<E, T, Txs, F, O, Err> Iterator for TracerIter<'_, E, Txs, F>
141where
142    E: Evm<DB: DatabaseCommit, Inspector: Clone>,
143    T: IntoTxEnv<E::Tx> + Clone,
144    Txs: Iterator<Item = T>,
145    Err: From<E::Error>,
146    F: FnMut(TracingCtx<'_, T, E>) -> Result<O, Err>,
147{
148    type Item = Result<O, Err>;
149
150    fn next(&mut self) -> Option<Self::Item> {
151        let tx = self.txs.next()?;
152        let result = self.inner.evm.transact(tx.clone());
153
154        let TxTracer { evm, fused_inspector } = self.inner;
155        let (db, inspector, _) = evm.components_mut();
156
157        let Ok(ResultAndState { result, state }) = result else {
158            return None;
159        };
160        let mut was_fused = false;
161        let output = (self.hook)(TracingCtx {
162            tx,
163            result,
164            state: &state,
165            inspector,
166            db,
167            fused_inspector: &*fused_inspector,
168            was_fused: &mut was_fused,
169        });
170
171        // Only commit next transaction if `skip_last_commit` is disabled or there is a next
172        // transaction.
173        if !self.skip_last_commit || self.txs.peek().is_some() {
174            db.commit(state);
175        }
176
177        if self.fuse && !was_fused {
178            self.inner.fuse_inspector();
179        }
180
181        Some(output)
182    }
183}