diff --git a/crates/evm/src/evm.rs b/crates/evm/src/evm.rs index 104f15d2..4982c5db 100644 --- a/crates/evm/src/evm.rs +++ b/crates/evm/src/evm.rs @@ -2,7 +2,7 @@ use crate::{tracing::TxTracer, EvmEnv, EvmError, IntoTxEnv}; use alloy_primitives::{Address, Bytes}; -use core::{error::Error, fmt::Debug, hash::Hash}; +use core::{convert::Infallible, error::Error, fmt::Debug, hash::Hash}; use revm::{ context::{result::ExecutionResult, BlockEnv}, context_interface::{ @@ -244,7 +244,7 @@ pub trait EvmFactoryExt: EvmFactory { db: DB, input: EvmEnv, fused_inspector: I, - ) -> TxTracer> + ) -> Result>, Infallible> where DB: Database + DatabaseCommit, I: Inspector> + Clone, diff --git a/crates/evm/src/tracing.rs b/crates/evm/src/tracing.rs index 428a00e6..5571d813 100644 --- a/crates/evm/src/tracing.rs +++ b/crates/evm/src/tracing.rs @@ -1,23 +1,68 @@ //! Helpers for tracing. use crate::{Evm, IntoTxEnv}; -use core::{fmt::Debug, iter::Peekable}; +use core::{convert::Infallible, fmt::Debug, iter::Peekable}; use revm::{ context::result::{ExecutionResult, ResultAndState}, state::EvmState, DatabaseCommit, }; +/// Trait for inspector initialization. +pub trait InspectorInitializer { + /// Associated inspector initialization error type. + type Error; + + /// Create a new inspector instance. + fn initialize(&mut self) -> Result; +} + +/// A simple initializer that clones the inspector. +#[derive(Debug, Clone)] +pub struct CloneInitializer(T); + +impl CloneInitializer { + /// Create a new clone initializer. + pub fn new(value: T) -> Self { + Self(value) + } +} + +impl InspectorInitializer for CloneInitializer { + type Error = Infallible; + + fn initialize(&mut self) -> Result { + Ok(self.0.clone()) + } +} + +/// Implementation for closures. +impl Result> InspectorInitializer for F { + type Error = E; + + fn initialize(&mut self) -> Result { + self() + } +} + /// A helper type for tracing transactions. #[derive(Debug, Clone)] -pub struct TxTracer { +pub struct TxTracer< + E: Evm, + Init: InspectorInitializer = CloneInitializer<::Inspector>, +> { evm: E, - fused_inspector: E::Inspector, + initializer: Init, } /// Container type for context exposed in [`TxTracer`]. #[derive(Debug)] -pub struct TracingCtx<'a, T, E: Evm> { +pub struct TracingCtx< + 'a, + T, + E: Evm, + Init: InspectorInitializer = CloneInitializer<::Inspector>, +> { /// The transaction that was just executed. pub tx: T, /// Result of transaction execution. @@ -28,37 +73,47 @@ pub struct TracingCtx<'a, T, E: Evm> { pub inspector: &'a mut E::Inspector, /// Database used when executing the transaction, _before_ committing the state changes. pub db: &'a mut E::DB, - /// Fused inspector. - fused_inspector: &'a E::Inspector, /// Whether the inspector was fused. was_fused: &'a mut bool, + /// Reference to the initializer. + initializer: &'a mut Init, } -impl<'a, T, E: Evm> TracingCtx<'a, T, E> { - /// Fuses the inspector and returns the current inspector state. - pub fn take_inspector(&mut self) -> E::Inspector { +impl<'a, T, E: Evm, Init: InspectorInitializer> TracingCtx<'a, T, E, Init> { + /// Takes the current inspector and replaces it with a fresh one. + /// + /// This is useful when you want to keep the current inspector state but continue + /// tracing with a new one. + pub fn take_inspector(&mut self) -> Result + where + E::Inspector: Clone, + { *self.was_fused = true; - core::mem::replace(self.inspector, self.fused_inspector.clone()) + Ok(core::mem::replace(self.inspector, self.initializer.initialize()?)) } } -impl> TxTracer { - /// Creates a new [`TxTracer`] instance. - pub fn new(mut evm: E) -> Self { - Self { fused_inspector: evm.inspector_mut().clone(), evm } +impl, Init: InspectorInitializer> TxTracer { + /// Creates a new [`TxTracer`] instance with a custom inspector initializer. + pub fn new_with_initializer(evm: E, initializer: Init) -> Result { + Ok(Self { evm, initializer }) } - fn fuse_inspector(&mut self) -> E::Inspector { - core::mem::replace(self.evm.inspector_mut(), self.fused_inspector.clone()) + fn fuse_inspector(&mut self) -> Result { + let fresh = self.initializer.initialize()?; + Ok(core::mem::replace(self.evm.inspector_mut(), fresh)) } /// Executes a transaction, and returns its outcome along with the inspector state. pub fn trace( &mut self, tx: impl IntoTxEnv, - ) -> Result, E::Error> { + ) -> Result, Init::Error> + where + >::Error: From, + { let result = self.evm.transact_commit(tx); - let inspector = self.fuse_inspector(); + let inspector = self.fuse_inspector()?; Ok(TraceOutput { result: result?, inspector }) } @@ -69,11 +124,17 @@ impl> TxTracer { &mut self, txs: Txs, mut f: F, - ) -> TracerIter<'_, E, Txs::IntoIter, impl FnMut(TracingCtx<'_, T, E>) -> Result> + ) -> TracerIter< + '_, + E, + Init, + Txs::IntoIter, + impl FnMut(TracingCtx<'_, T, E, Init>) -> Result, + > where T: IntoTxEnv + Clone, Txs: IntoIterator, - F: FnMut(TracingCtx<'_, Txs::Item, E>) -> O, + F: FnMut(TracingCtx<'_, Txs::Item, E, Init>) -> O, { self.try_trace_many(txs, move |ctx| Ok(f(ctx))) } @@ -83,11 +144,11 @@ impl> TxTracer { &mut self, txs: Txs, hook: F, - ) -> TracerIter<'_, E, Txs::IntoIter, F> + ) -> TracerIter<'_, E, Init, Txs::IntoIter, F> where T: IntoTxEnv + Clone, Txs: IntoIterator, - F: FnMut(TracingCtx<'_, T, E>) -> Result, + F: FnMut(TracingCtx<'_, T, E, Init>) -> Result, Err: From, { TracerIter { @@ -100,6 +161,17 @@ impl> TxTracer { } } +impl> TxTracer> +where + E::Inspector: Clone, +{ + /// Creates a new [`TxTracer`] instance with a cloneable inspector. + pub fn new(mut evm: E) -> Result { + let inspector = evm.inspector_mut().clone(); + Self::new_with_initializer(evm, CloneInitializer::new(inspector)) + } +} + /// Output of tracing a transaction. #[derive(Debug, Clone)] pub struct TraceOutput { @@ -112,15 +184,17 @@ pub struct TraceOutput { /// Iterator used by tracer. #[derive(derive_more::Debug)] #[debug(bound(E::Inspector: Debug))] -pub struct TracerIter<'a, E: Evm, Txs: Iterator, F> { - inner: &'a mut TxTracer, +pub struct TracerIter<'a, E: Evm, Init: InspectorInitializer, Txs: Iterator, F> { + inner: &'a mut TxTracer, txs: Peekable, hook: F, skip_last_commit: bool, fuse: bool, } -impl TracerIter<'_, E, Txs, F> { +impl, Txs: Iterator, F> + TracerIter<'_, E, Init, Txs, F> +{ /// Flips the `skip_last_commit` flag thus making sure all transaction are committed. /// /// We are skipping last commit by default as it's expected that when tracing users are mostly @@ -137,13 +211,14 @@ impl TracerIter<'_, E, Txs, F> { } } -impl Iterator for TracerIter<'_, E, Txs, F> +impl Iterator for TracerIter<'_, E, Init, Txs, F> where - E: Evm, + E: Evm, + Init: InspectorInitializer, T: IntoTxEnv + Clone, Txs: Iterator, Err: From, - F: FnMut(TracingCtx<'_, T, E>) -> Result, + F: FnMut(TracingCtx<'_, T, E, Init>) -> Result, { type Item = Result; @@ -151,7 +226,7 @@ where let tx = self.txs.next()?; let result = self.inner.evm.transact(tx.clone()); - let TxTracer { evm, fused_inspector } = self.inner; + let TxTracer { evm, initializer } = self.inner; let (db, inspector, _) = evm.components_mut(); let Ok(ResultAndState { result, state }) = result else { @@ -164,8 +239,8 @@ where state: &state, inspector, db, - fused_inspector: &*fused_inspector, was_fused: &mut was_fused, + initializer, }); // Only commit next transaction if `skip_last_commit` is disabled or there is a next @@ -175,7 +250,7 @@ where } if self.fuse && !was_fused { - self.inner.fuse_inspector(); + let _ = self.inner.fuse_inspector(); } Some(output)