Skip to main content

foundry_evm/executors/
trace.rs

1use crate::executors::{Executor, ExecutorBuilder};
2use alloy_primitives::{Address, ChainId, U256, map::HashMap};
3use alloy_rpc_types::state::StateOverride;
4use eyre::{Context, ContextCompat};
5use foundry_compilers::artifacts::EvmVersion;
6use foundry_config::{Chain, Config, evm_spec_id};
7use foundry_evm_core::{
8    backend::Backend,
9    evm::{EvmEnvFor, FoundryEvmNetwork, SpecFor, TxEnvFor},
10    opts::{EvmOpts, ExecutionSpecContext, resolve_execution_spec},
11};
12use foundry_evm_hardforks::FoundryHardfork;
13use foundry_evm_networks::{
14    NetworkConfigs,
15    celo::transfer::{CELO_TRANSFER_ADDRESS, CELO_TRANSFER_LABEL},
16    resolved_precompile_labels,
17};
18use foundry_evm_traces::{TraceContext, TraceRequirements};
19use revm::state::Bytecode;
20use std::ops::{Deref, DerefMut};
21
22/// A default executor with tracing enabled
23pub struct TracingExecutor<FEN: FoundryEvmNetwork> {
24    executor: Executor<FEN>,
25}
26
27/// Fork state and network context used to construct a tracing executor.
28pub struct TracingFork<FEN: FoundryEvmNetwork> {
29    pub evm_env: EvmEnvFor<FEN>,
30    pub tx_env: TxEnvFor<FEN>,
31    fork: Backend<FEN>,
32    context: TraceContext,
33}
34
35impl<FEN: FoundryEvmNetwork> TracingFork<FEN> {
36    pub const fn context(&self) -> TraceContext {
37        self.context
38    }
39
40    /// Resolves the execution spec and carries it into the trace decoding context.
41    pub fn resolve_spec(&mut self, config: &Config, evm_version: Option<EvmVersion>) {
42        let hardfork = TracingExecutor::<FEN>::resolve_spec_for_chain(
43            config,
44            self.context.chain().id(),
45            self.context.hardfork(),
46            &mut self.evm_env,
47            evm_version,
48        );
49        self.context = self.context.with_hardfork(hardfork);
50    }
51
52    /// Adds labels for precompiles active in this trace context.
53    pub fn extend_precompile_labels(&self, config: &mut Config) {
54        TracingExecutor::<FEN>::extend_precompile_labels(
55            config,
56            self.context.networks(),
57            self.context.hardfork(),
58        );
59    }
60
61    /// Builds a tracing executor from this resolved fork.
62    pub fn into_executor(
63        self,
64        builder: ExecutorBuilder<FEN>,
65        trace_requirements: TraceRequirements,
66        create2_deployer: Address,
67        state_overrides: Option<StateOverride>,
68    ) -> eyre::Result<TracingExecutor<FEN>> {
69        TracingExecutor::new(
70            builder,
71            (self.evm_env, self.tx_env),
72            self.fork,
73            None,
74            trace_requirements,
75            self.context.networks(),
76            create2_deployer,
77            state_overrides,
78        )
79    }
80
81    fn into_parts(
82        self,
83    ) -> (EvmEnvFor<FEN>, TxEnvFor<FEN>, Backend<FEN>, Chain, NetworkConfigs, Option<FoundryHardfork>)
84    {
85        (
86            self.evm_env,
87            self.tx_env,
88            self.fork,
89            self.context.chain(),
90            self.context.networks(),
91            self.context.hardfork(),
92        )
93    }
94}
95
96impl<FEN: FoundryEvmNetwork> TracingExecutor<FEN> {
97    /// Creates a tracing executor from tooling resolved by concrete network dispatch.
98    #[allow(clippy::too_many_arguments)]
99    pub fn new(
100        builder: ExecutorBuilder<FEN>,
101        env: (EvmEnvFor<FEN>, TxEnvFor<FEN>),
102        fork: Backend<FEN>,
103        version: Option<EvmVersion>,
104        trace_requirements: TraceRequirements,
105        networks: NetworkConfigs,
106        create2_deployer: Address,
107        state_overrides: Option<StateOverride>,
108    ) -> eyre::Result<Self> {
109        let db = fork;
110        // configures a bare version of the evm executor: no cheatcode and log_collector inspector
111        // is enabled, tracing will be enabled only for the targeted transaction
112        let mut executor = builder
113            .inspectors(|stack| {
114                stack.trace_requirements(trace_requirements).create2_deployer(create2_deployer)
115            })
116            .spec_id_opt(version.map(evm_spec_id::<SpecFor<FEN>>))
117            .build(env.0, env.1, db, networks);
118
119        if let Some(state_overrides) = state_overrides {
120            apply_state_overrides(&mut executor, state_overrides)?;
121        }
122
123        Ok(Self { executor })
124    }
125
126    /// Returns the spec id of the executor
127    pub const fn spec_id(&self) -> SpecFor<FEN> {
128        self.executor.spec_id()
129    }
130
131    /// Resolves and applies the execution spec using the source chain's hardfork schedule.
132    pub fn resolve_spec_for_chain(
133        config: &Config,
134        source_chain_id: ChainId,
135        endpoint_hardfork: Option<FoundryHardfork>,
136        evm_env: &mut EvmEnvFor<FEN>,
137        evm_version: Option<EvmVersion>,
138    ) -> Option<FoundryHardfork> {
139        resolve_execution_spec(
140            config.evm_version,
141            config.hardfork,
142            evm_env,
143            ExecutionSpecContext::historical(source_chain_id, endpoint_hardfork),
144            evm_version.map(evm_spec_id::<SpecFor<FEN>>),
145        )
146    }
147
148    /// Extends trace labels with the precompiles active at the resolved execution hardfork.
149    pub fn extend_precompile_labels(
150        config: &mut Config,
151        networks: NetworkConfigs,
152        resolved_hardfork: Option<FoundryHardfork>,
153    ) {
154        config.labels.extend(resolved_precompile_labels(resolved_hardfork));
155        // Celo shares the Ethereum spec and remains a separate inspector configuration.
156        if networks.is_celo() {
157            config.labels.insert(CELO_TRANSFER_ADDRESS, CELO_TRANSFER_LABEL.to_string());
158        }
159    }
160
161    /// Resolves the fork state and trace context using the fork block number from the config.
162    pub async fn get_fork(
163        config: &mut Config,
164        mut evm_opts: EvmOpts,
165    ) -> eyre::Result<TracingFork<FEN>> {
166        evm_opts.fork_url = Some(config.get_rpc_url_or_localhost_http()?.into_owned());
167        evm_opts.fork_block_number = config.fork_block_number;
168        // Tracing replays a specific chain history and must keep hash-addressed state.
169        evm_opts.fork_state_by_number = false;
170        evm_opts.infer_network_from_fork().await?;
171        let networks = evm_opts.networks;
172        let fork = Backend::<FEN>::spawn(evm_opts.get_fork(
173            config,
174            evm_opts.env.chain_id.unwrap_or_default(),
175            None,
176        ))?;
177        let (evm_env, tx_env) = fork.env(&evm_opts).await?;
178        let fork_context =
179            fork.fork()?.context("fork context is missing for tracing executor")?.context();
180
181        let chain = fork_context.source_chain_id.into();
182        Ok(TracingFork {
183            evm_env,
184            tx_env,
185            fork,
186            context: TraceContext::new(chain, networks, fork_context.hardfork),
187        })
188    }
189
190    /// Returns the tracing fork as its legacy tuple representation.
191    pub async fn get_fork_material(
192        config: &mut Config,
193        evm_opts: EvmOpts,
194    ) -> eyre::Result<(
195        EvmEnvFor<FEN>,
196        TxEnvFor<FEN>,
197        Backend<FEN>,
198        Chain,
199        NetworkConfigs,
200        Option<FoundryHardfork>,
201    )> {
202        Ok(Self::get_fork(config, evm_opts).await?.into_parts())
203    }
204}
205
206fn apply_state_overrides<FEN: FoundryEvmNetwork>(
207    executor: &mut Executor<FEN>,
208    state_overrides: StateOverride,
209) -> eyre::Result<()> {
210    for (address, overrides) in state_overrides {
211        if let Some(balance) = overrides.balance {
212            executor.set_balance(address, balance)?;
213        }
214        if let Some(nonce) = overrides.nonce {
215            executor.set_account_nonce(address, nonce)?;
216        }
217        if let Some(code) = overrides.code {
218            let bytecode =
219                Bytecode::new_raw_checked(code).wrap_err("invalid bytecode in state override")?;
220            executor.set_code(address, bytecode)?;
221        }
222        if let Some(state) = overrides.state {
223            let state: HashMap<U256, U256> =
224                state.into_iter().map(|(slot, value)| (slot.into(), value.into())).collect();
225            executor.set_storage(address, state)?;
226        }
227        if let Some(state_diff) = overrides.state_diff {
228            for (slot, value) in state_diff {
229                executor.set_storage_slot(address, slot.into(), value.into())?;
230            }
231        }
232    }
233    Ok(())
234}
235
236impl<FEN: FoundryEvmNetwork> Deref for TracingExecutor<FEN> {
237    type Target = Executor<FEN>;
238
239    fn deref(&self) -> &Self::Target {
240        &self.executor
241    }
242}
243
244impl<FEN: FoundryEvmNetwork> DerefMut for TracingExecutor<FEN> {
245    fn deref_mut(&mut self) -> &mut Self::Target {
246        &mut self.executor
247    }
248}
249
250#[cfg(test)]
251mod tests {
252    use super::*;
253    use alloy_evm::EvmEnv;
254    use alloy_rpc_types::state::AccountOverride;
255    use foundry_evm_core::{FoundryTransaction, evm::EthEvmNetwork};
256    use revm::context::{Transaction, TxEnv};
257
258    #[cfg(feature = "base")]
259    use foundry_evm_core::evm::BaseEvmNetwork;
260    #[cfg(feature = "base")]
261    use foundry_evm_hardforks::{BaseSpecId, BaseUpgrade};
262
263    fn assert_trace_spec_authority<FEN>(
264        networks: NetworkConfigs,
265        configured: FoundryHardfork,
266        evm_version: EvmVersion,
267        expected_spec: SpecFor<FEN>,
268        expected_hardfork: Option<FoundryHardfork>,
269    ) where
270        FEN: FoundryEvmNetwork,
271        SpecFor<FEN>: std::fmt::Debug + PartialEq,
272    {
273        let mut config = Config { networks, hardfork: Some(configured), ..Default::default() };
274        let mut env = EvmEnvFor::<FEN>::default();
275        env.cfg_env.chain_id = 999_999;
276        let hardfork = TracingExecutor::<FEN>::resolve_spec_for_chain(
277            &config,
278            1,
279            Some(configured),
280            &mut env,
281            Some(evm_version),
282        );
283        assert_eq!(env.cfg_env.spec, expected_spec);
284        assert_eq!(hardfork, expected_hardfork);
285        assert_eq!(env.cfg_env.chain_id, 999_999);
286
287        TracingExecutor::<FEN>::extend_precompile_labels(&mut config, networks, hardfork);
288        assert_eq!(config.labels, resolved_precompile_labels(expected_hardfork));
289        let context = TraceContext::new(Chain::from_id(1), networks, hardfork);
290        let decoder = foundry_evm_traces::CallTraceDecoderBuilder::new()
291            .with_networks(context.networks())
292            .with_hardfork(context.hardfork())
293            .build();
294        assert_eq!(decoder.hardfork(), expected_hardfork);
295    }
296
297    #[test]
298    fn trace_spec_ethereum_override_preserves_absent_metadata() {
299        assert_trace_spec_authority::<EthEvmNetwork>(
300            NetworkConfigs::default(),
301            "ethereum:shanghai".parse().unwrap(),
302            EvmVersion::Cancun,
303            revm::primitives::hardfork::SpecId::CANCUN,
304            None,
305        );
306    }
307
308    #[test]
309    fn trace_spec_tempo_override_reports_executed_hardfork() {
310        let spec = evm_spec_id::<foundry_evm_hardforks::TempoHardfork>(EvmVersion::Cancun);
311        assert_trace_spec_authority::<foundry_evm_core::evm::TempoEvmNetwork>(
312            NetworkConfigs::with_tempo(),
313            "tempo:T3".parse().unwrap(),
314            EvmVersion::Cancun,
315            spec,
316            Some(spec.into()),
317        );
318    }
319
320    #[cfg(feature = "base")]
321    #[test]
322    fn trace_spec_base_override_reports_executed_hardfork() {
323        assert_trace_spec_authority::<BaseEvmNetwork>(
324            NetworkConfigs::with_base(),
325            FoundryHardfork::Base(BaseUpgrade::Cobalt),
326            EvmVersion::Cancun,
327            BaseSpecId::new(BaseUpgrade::Ecotone),
328            Some(FoundryHardfork::Base(BaseUpgrade::Ecotone)),
329        );
330    }
331
332    #[test]
333    fn state_override_nonce_does_not_modify_transaction_nonce() {
334        let sender = Address::repeat_byte(0x11);
335        let mut tx_env = TxEnv::default();
336        tx_env.set_caller(sender);
337        tx_env.set_nonce(7);
338        let backend = Backend::<EthEvmNetwork>::spawn(None).unwrap();
339        let mut evm_env = EvmEnv::default();
340        evm_env.cfg_env.disable_nonce_check = true;
341        let mut executor =
342            ExecutorBuilder::default().build(evm_env, tx_env, backend, NetworkConfigs::default());
343        executor.set_gas_limit(1_000_000);
344        executor.set_account_nonce(sender, 7).unwrap();
345
346        let overridden = Address::repeat_byte(0x42);
347        let mut state_overrides = StateOverride::default();
348        state_overrides.insert(sender, AccountOverride { nonce: Some(100), ..Default::default() });
349        state_overrides
350            .insert(overridden, AccountOverride { nonce: Some(200), ..Default::default() });
351
352        apply_state_overrides(&mut executor, state_overrides).unwrap();
353
354        assert_eq!(executor.get_nonce(sender).unwrap(), 100);
355        assert_eq!(executor.get_nonce(overridden).unwrap(), 200);
356        assert_eq!(executor.tx_env().caller(), sender);
357        assert_eq!(executor.tx_env().nonce(), 7);
358
359        let result =
360            executor.transact_raw(sender, overridden, Default::default(), U256::ZERO).unwrap();
361        assert_eq!(result.tx_env.nonce(), 7);
362    }
363}