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