1use alloy_consensus::Transaction;
2use alloy_network::{AnyNetwork, TransactionResponse};
3use alloy_primitives::{
4 Address, Bytes, U256,
5 map::{HashMap, HashSet},
6};
7use alloy_provider::{Provider, RootProvider};
8use alloy_rpc_types::BlockTransactions;
9use clap::Parser;
10use eyre::{Result, WrapErr};
11use foundry_cli::{
12 opts::{EtherscanOpts, RpcOpts},
13 utils::{TraceResult, get_executor_strategy, handle_traces, init_progress},
14};
15use foundry_common::{SYSTEM_TRANSACTION_TYPE, is_impersonated_tx, is_known_system_sender, shell};
16use foundry_compilers::artifacts::EvmVersion;
17use foundry_config::{
18 Config,
19 figment::{
20 self, Metadata, Profile,
21 value::{Dict, Map},
22 },
23};
24use foundry_evm::{
25 Env,
26 executors::{EvmError, TracingExecutor},
27 opts::EvmOpts,
28 traces::{InternalTraceMode, TraceMode, Traces},
29 utils::configure_tx_env,
30};
31use foundry_evm_core::env::AsEnvMut;
32
33use crate::utils::apply_chain_and_block_specific_env_changes;
34
35#[derive(Clone, Debug, Parser)]
37pub struct RunArgs {
38 tx_hash: String,
40
41 #[arg(long, short)]
43 debug: bool,
44
45 #[arg(long)]
47 decode_internal: bool,
48
49 #[arg(long, short)]
51 trace_printer: bool,
52
53 #[arg(long)]
57 quick: bool,
58
59 #[arg(long, default_value_t = false)]
61 disable_labels: bool,
62
63 #[arg(long, short)]
67 label: Vec<String>,
68
69 #[command(flatten)]
70 etherscan: EtherscanOpts,
71
72 #[command(flatten)]
73 rpc: RpcOpts,
74
75 #[arg(long)]
79 evm_version: Option<EvmVersion>,
80
81 #[arg(long, alias = "cups", value_name = "CUPS")]
87 pub compute_units_per_second: Option<u64>,
88
89 #[arg(long, value_name = "NO_RATE_LIMITS", visible_alias = "no-rpc-rate-limit")]
95 pub no_rate_limit: bool,
96
97 #[arg(long, alias = "alphanet")]
99 pub odyssey: bool,
100
101 #[arg(long, visible_alias = "la")]
103 pub with_local_artifacts: bool,
104
105 #[arg(long)]
107 pub disable_block_gas_limit: bool,
108}
109
110impl RunArgs {
111 pub async fn run(self) -> Result<()> {
117 let figment = self.rpc.clone().into_figment(self.with_local_artifacts).merge(&self);
118 let evm_opts = figment.extract::<EvmOpts>()?;
119 let mut config = Config::from_provider(figment)?.sanitized();
120 let strategy = get_executor_strategy(&config);
121
122 let label = self.label;
123 let with_local_artifacts = self.with_local_artifacts;
124 let debug = self.debug;
125 let decode_internal = self.decode_internal;
126 let disable_labels = self.disable_labels;
127 let compute_units_per_second =
128 if self.no_rate_limit { Some(u64::MAX) } else { self.compute_units_per_second };
129
130 let provider = foundry_cli::utils::get_provider_builder(&config)?
131 .compute_units_per_second_opt(compute_units_per_second)
132 .build()?;
133
134 let tx_hash = self.tx_hash.parse().wrap_err("invalid tx hash")?;
135 let tx = provider
136 .get_transaction_by_hash(tx_hash)
137 .await
138 .wrap_err_with(|| format!("tx not found: {tx_hash:?}"))?
139 .ok_or_else(|| eyre::eyre!("tx not found: {:?}", tx_hash))?;
140
141 if is_known_system_sender(tx.from())
143 || tx.transaction_type() == Some(SYSTEM_TRANSACTION_TYPE)
144 {
145 return Err(eyre::eyre!(
146 "{:?} is a system transaction.\nReplaying system transactions is currently not supported.",
147 tx.tx_hash()
148 ));
149 }
150
151 let tx_block_number =
152 tx.block_number.ok_or_else(|| eyre::eyre!("tx may still be pending: {:?}", tx_hash))?;
153
154 let block = provider.get_block(tx_block_number.into()).full().await?;
156
157 config.fork_block_number = Some(tx_block_number - 1);
159
160 let create2_deployer = evm_opts.create2_deployer;
161 let (mut env, fork, chain, odyssey) =
162 TracingExecutor::get_fork_material(&config, evm_opts).await?;
163 let mut evm_version = self.evm_version;
164
165 env.evm_env.cfg_env.disable_block_gas_limit = self.disable_block_gas_limit;
166 env.evm_env.block_env.number = U256::from(tx_block_number);
167
168 if let Some(block) = &block {
169 env.evm_env.block_env.timestamp = U256::from(block.header.timestamp);
170 env.evm_env.block_env.beneficiary = block.header.beneficiary;
171 env.evm_env.block_env.difficulty = block.header.difficulty;
172 env.evm_env.block_env.prevrandao = Some(block.header.mix_hash.unwrap_or_default());
173 env.evm_env.block_env.basefee = block.header.base_fee_per_gas.unwrap_or_default();
174 env.evm_env.block_env.gas_limit = block.header.gas_limit;
175
176 if evm_version.is_none() {
179 if block.header.excess_blob_gas.is_some() {
181 evm_version = Some(EvmVersion::Cancun);
182 }
183 }
184 apply_chain_and_block_specific_env_changes::<AnyNetwork>(env.as_env_mut(), block);
185 }
186
187 let trace_mode = TraceMode::Call
188 .with_debug(self.debug)
189 .with_decode_internal(if self.decode_internal {
190 InternalTraceMode::Full
191 } else {
192 InternalTraceMode::None
193 })
194 .with_state_changes(shell::verbosity() > 4);
195 let mut executor = TracingExecutor::new(
196 env.clone(),
197 fork,
198 evm_version,
199 trace_mode,
200 odyssey,
201 create2_deployer,
202 None,
203 strategy,
204 )?;
205 let mut env = Env::new_with_spec_id(
206 env.evm_env.cfg_env.clone(),
207 env.evm_env.block_env.clone(),
208 env.tx.clone(),
209 executor.spec_id(),
210 );
211
212 if !self.quick {
214 if !shell::is_json() {
215 sh_println!("Executing previous transactions from the block.")?;
216 }
217
218 if let Some(block) = block {
219 let pb = init_progress(block.transactions.len() as u64, "tx");
220 pb.set_position(0);
221
222 let BlockTransactions::Full(ref txs) = block.transactions else {
223 return Err(eyre::eyre!("Could not get block txs"));
224 };
225
226 for (index, tx) in txs.iter().enumerate() {
227 if is_known_system_sender(tx.from())
231 || tx.transaction_type() == Some(SYSTEM_TRANSACTION_TYPE)
232 {
233 pb.set_position((index + 1) as u64);
234 continue;
235 }
236 if tx.tx_hash() == tx_hash {
237 break;
238 }
239
240 configure_tx_env(&mut env.as_env_mut(), &tx.inner);
241
242 env.evm_env.cfg_env.disable_balance_check = true;
243
244 if let Some(to) = Transaction::to(tx) {
245 trace!(tx=?tx.tx_hash(),?to, "executing previous call transaction");
246 executor.transact_with_env(env.clone()).wrap_err_with(|| {
247 format!(
248 "Failed to execute transaction: {:?} in block {}",
249 tx.tx_hash(),
250 env.evm_env.block_env.number
251 )
252 })?;
253 } else {
254 trace!(tx=?tx.tx_hash(), "executing previous create transaction");
255 if let Err(error) = executor.deploy_with_env(env.clone(), None) {
256 match error {
257 EvmError::Execution(_) => (),
259 error => {
260 return Err(error).wrap_err_with(|| {
261 format!(
262 "Failed to deploy transaction: {:?} in block {}",
263 tx.tx_hash(),
264 env.evm_env.block_env.number
265 )
266 });
267 }
268 }
269 }
270 }
271
272 pb.set_position((index + 1) as u64);
273 }
274 }
275 }
276
277 let result = {
279 executor.set_trace_printer(self.trace_printer);
280
281 configure_tx_env(&mut env.as_env_mut(), &tx.inner);
282 if is_impersonated_tx(tx.inner.inner.inner()) {
283 env.evm_env.cfg_env.disable_balance_check = true;
284 }
285
286 if let Some(to) = Transaction::to(&tx) {
287 trace!(tx=?tx.tx_hash(), to=?to, "executing call transaction");
288 TraceResult::try_from(executor.transact_with_env(env))?
289 } else {
290 trace!(tx=?tx.tx_hash(), "executing create transaction");
291 TraceResult::try_from(executor.deploy_with_env(env, None))?
292 }
293 };
294
295 let contracts_bytecode = fetch_contracts_bytecode_from_trace(&provider, &result).await?;
296 handle_traces(
297 result,
298 &config,
299 chain,
300 &contracts_bytecode,
301 label,
302 with_local_artifacts,
303 debug,
304 decode_internal,
305 disable_labels,
306 )
307 .await?;
308
309 Ok(())
310 }
311}
312
313pub async fn fetch_contracts_bytecode_from_trace(
314 provider: &RootProvider<AnyNetwork>,
315 result: &TraceResult,
316) -> Result<HashMap<Address, Bytes>> {
317 let mut contracts_bytecode = HashMap::default();
318 if let Some(ref traces) = result.traces {
319 let addresses = gather_trace_addresses(traces);
320 let results = futures::future::join_all(addresses.into_iter().map(async |a| {
321 (
322 a,
323 provider.get_code_at(a).await.unwrap_or_else(|e| {
324 sh_warn!("Failed to fetch code for {a:?}: {e:?}").ok();
325 Bytes::new()
326 }),
327 )
328 }))
329 .await;
330 for (address, code) in results {
331 if !code.is_empty() {
332 contracts_bytecode.insert(address, code);
333 }
334 }
335 }
336 Ok(contracts_bytecode)
337}
338
339fn gather_trace_addresses(traces: &Traces) -> HashSet<Address> {
340 let mut addresses = HashSet::default();
341 for (_, trace) in traces {
342 for node in trace.arena.nodes() {
343 if !node.trace.address.is_zero() {
344 addresses.insert(node.trace.address);
345 }
346 if !node.trace.caller.is_zero() {
347 addresses.insert(node.trace.caller);
348 }
349 }
350 }
351 addresses
352}
353
354impl figment::Provider for RunArgs {
355 fn metadata(&self) -> Metadata {
356 Metadata::named("RunArgs")
357 }
358
359 fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
360 let mut map = Map::new();
361
362 if self.odyssey {
363 map.insert("odyssey".into(), self.odyssey.into());
364 }
365
366 if let Some(api_key) = &self.etherscan.key {
367 map.insert("etherscan_api_key".into(), api_key.as_str().into());
368 }
369
370 if let Some(api_version) = &self.etherscan.api_version {
371 map.insert("etherscan_api_version".into(), api_version.to_string().into());
372 }
373
374 if let Some(evm_version) = self.evm_version {
375 map.insert("evm_version".into(), figment::value::Value::serialize(evm_version)?);
376 }
377
378 Ok(Map::from([(Config::selected_profile(), map)]))
379 }
380}