1use crate::{
2 debug::handle_traces,
3 utils::{apply_chain_and_block_specific_env_changes, block_env_from_header},
4};
5use alloy_consensus::{BlockHeader, Transaction};
6use alloy_evm::FromRecoveredTx;
7use alloy_network::{AnyNetwork, TransactionResponse};
8use alloy_primitives::{
9 Address, Bytes, U256,
10 map::{AddressSet, HashMap},
11};
12use alloy_provider::Provider;
13use alloy_rpc_types::BlockTransactions;
14use clap::Parser;
15use eyre::{Result, WrapErr};
16use foundry_cli::{
17 opts::{EtherscanOpts, RpcOpts},
18 utils::{TraceResult, init_progress},
19};
20use foundry_common::{SYSTEM_TRANSACTION_TYPE, is_impersonated_tx, is_known_system_sender, shell};
21use foundry_compilers::artifacts::EvmVersion;
22use foundry_config::{
23 Config,
24 figment::{
25 self, Metadata, Profile,
26 value::{Dict, Map},
27 },
28};
29use foundry_evm::{
30 executors::{EvmError, Executor, TracingExecutor},
31 opts::EvmOpts,
32 traces::{InternalTraceMode, TraceMode, Traces},
33};
34use futures::TryFutureExt;
35use revm::{DatabaseRef, context::TxEnv};
36
37#[derive(Clone, Debug, Parser)]
39pub struct RunArgs {
40 tx_hash: String,
42
43 #[arg(long, short)]
45 debug: bool,
46
47 #[arg(long)]
49 decode_internal: bool,
50
51 #[arg(long)]
53 trace_depth: Option<usize>,
54
55 #[arg(long, short)]
57 trace_printer: bool,
58
59 #[arg(long)]
63 quick: bool,
64
65 #[arg(long, alias = "sys")]
67 replay_system_txes: bool,
68
69 #[arg(long, default_value_t = false)]
71 disable_labels: bool,
72
73 #[arg(long, short)]
77 label: Vec<String>,
78
79 #[command(flatten)]
80 etherscan: EtherscanOpts,
81
82 #[command(flatten)]
83 rpc: RpcOpts,
84
85 #[arg(long)]
89 evm_version: Option<EvmVersion>,
90
91 #[arg(long, alias = "cups", value_name = "CUPS")]
97 pub compute_units_per_second: Option<u64>,
98
99 #[arg(long, value_name = "NO_RATE_LIMITS", visible_alias = "no-rpc-rate-limit")]
105 pub no_rate_limit: bool,
106
107 #[arg(long, visible_alias = "la")]
109 pub with_local_artifacts: bool,
110
111 #[arg(long)]
113 pub disable_block_gas_limit: bool,
114
115 #[arg(long)]
117 pub enable_tx_gas_limit: bool,
118}
119
120impl RunArgs {
121 pub async fn run(self) -> Result<()> {
127 let figment = self.rpc.clone().into_figment(self.with_local_artifacts).merge(&self);
128 let evm_opts = figment.extract::<EvmOpts>()?;
129 let mut config = Config::from_provider(figment)?.sanitized();
130
131 let label = self.label;
132 let with_local_artifacts = self.with_local_artifacts;
133 let debug = self.debug;
134 let decode_internal = self.decode_internal;
135 let disable_labels = self.disable_labels;
136 let compute_units_per_second =
137 if self.no_rate_limit { Some(u64::MAX) } else { self.compute_units_per_second };
138
139 let provider = foundry_cli::utils::get_provider_builder(&config)?
140 .compute_units_per_second_opt(compute_units_per_second)
141 .build()?;
142
143 let tx_hash = self.tx_hash.parse().wrap_err("invalid tx hash")?;
144 let tx = provider
145 .get_transaction_by_hash(tx_hash)
146 .await
147 .wrap_err_with(|| format!("tx not found: {tx_hash:?}"))?
148 .ok_or_else(|| eyre::eyre!("tx not found: {:?}", tx_hash))?;
149
150 if !self.replay_system_txes
152 && (is_known_system_sender(tx.from())
153 || tx.transaction_type() == Some(SYSTEM_TRANSACTION_TYPE))
154 {
155 return Err(eyre::eyre!(
156 "{:?} is a system transaction.\nReplaying system transactions is currently not supported.",
157 tx.tx_hash()
158 ));
159 }
160
161 let tx_block_number =
162 tx.block_number.ok_or_else(|| eyre::eyre!("tx may still be pending: {:?}", tx_hash))?;
163
164 config.fork_block_number = Some(tx_block_number - 1);
166
167 let create2_deployer = evm_opts.create2_deployer;
168 let (block, (mut evm_env, tx_env, fork, chain, networks)) = tokio::try_join!(
169 provider.get_block(tx_block_number.into()).full().into_future().map_err(Into::into),
171 TracingExecutor::get_fork_material(&mut config, evm_opts)
172 )?;
173
174 let mut evm_version = self.evm_version;
175
176 evm_env.cfg_env.disable_block_gas_limit = self.disable_block_gas_limit;
177
178 if !self.enable_tx_gas_limit {
181 evm_env.cfg_env.tx_gas_limit_cap = Some(u64::MAX);
182 }
183
184 evm_env.cfg_env.limit_contract_code_size = None;
185 evm_env.block_env.number = U256::from(tx_block_number);
186
187 if let Some(block) = &block {
188 evm_env.block_env = block_env_from_header(&block.header);
189
190 if evm_version.is_none() {
193 if block.header.excess_blob_gas().is_some() {
195 evm_version = Some(EvmVersion::Prague);
196 }
197 }
198 apply_chain_and_block_specific_env_changes::<AnyNetwork>(
199 &mut evm_env,
200 block,
201 config.networks,
202 );
203 }
204
205 let trace_mode = TraceMode::Call
206 .with_debug(self.debug)
207 .with_decode_internal(if self.decode_internal {
208 InternalTraceMode::Full
209 } else {
210 InternalTraceMode::None
211 })
212 .with_state_changes(shell::verbosity() > 4);
213 let mut executor = TracingExecutor::new(
214 (evm_env.clone(), tx_env),
215 fork,
216 evm_version,
217 trace_mode,
218 networks,
219 create2_deployer,
220 None,
221 )?;
222
223 evm_env.cfg_env.set_spec(executor.spec_id());
224
225 if !self.quick {
227 if !shell::is_json() {
228 sh_println!("Executing previous transactions from the block.")?;
229 }
230
231 if let Some(block) = block {
232 let pb = init_progress(block.transactions.len() as u64, "tx");
233 pb.set_position(0);
234
235 let BlockTransactions::Full(ref txs) = block.transactions else {
236 return Err(eyre::eyre!("Could not get block txs"));
237 };
238
239 for (index, tx) in txs.iter().enumerate() {
240 if !self.replay_system_txes
244 && (is_known_system_sender(tx.from())
245 || tx.transaction_type() == Some(SYSTEM_TRANSACTION_TYPE))
246 {
247 pb.set_position((index + 1) as u64);
248 continue;
249 }
250 if tx.tx_hash() == tx_hash {
251 break;
252 }
253
254 let tx_env = tx.as_envelope().map_or(Default::default(), |tx_envelope| {
255 TxEnv::from_recovered_tx(tx_envelope, tx.from())
256 });
257
258 evm_env.cfg_env.disable_balance_check = true;
259
260 if let Some(to) = Transaction::to(tx) {
261 trace!(tx=?tx.tx_hash(),?to, "executing previous call transaction");
262 executor.transact_with_env(evm_env.clone(), tx_env.clone()).wrap_err_with(
263 || {
264 format!(
265 "Failed to execute transaction: {:?} in block {}",
266 tx.tx_hash(),
267 evm_env.block_env.number
268 )
269 },
270 )?;
271 } else {
272 trace!(tx=?tx.tx_hash(), "executing previous create transaction");
273 if let Err(error) =
274 executor.deploy_with_env(evm_env.clone(), tx_env.clone(), None)
275 {
276 match error {
277 EvmError::Execution(_) => (),
279 error => {
280 return Err(error).wrap_err_with(|| {
281 format!(
282 "Failed to deploy transaction: {:?} in block {}",
283 tx.tx_hash(),
284 evm_env.block_env.number
285 )
286 });
287 }
288 }
289 }
290 }
291
292 pb.set_position((index + 1) as u64);
293 }
294 }
295 }
296
297 let result = {
299 executor.set_trace_printer(self.trace_printer);
300
301 let tx_env = tx.as_envelope().map_or(Default::default(), |tx_envelope| {
302 TxEnv::from_recovered_tx(tx_envelope, tx.from())
303 });
304
305 if is_impersonated_tx(tx.as_ref()) {
306 evm_env.cfg_env.disable_balance_check = true;
307 }
308
309 if let Some(to) = Transaction::to(&tx) {
310 trace!(tx=?tx.tx_hash(), to=?to, "executing call transaction");
311 TraceResult::try_from(executor.transact_with_env(evm_env, tx_env))?
312 } else {
313 trace!(tx=?tx.tx_hash(), "executing create transaction");
314 TraceResult::try_from(executor.deploy_with_env(evm_env, tx_env, None))?
315 }
316 };
317
318 let contracts_bytecode = fetch_contracts_bytecode_from_trace(&executor, &result)?;
319 handle_traces(
320 result,
321 &config,
322 chain,
323 &contracts_bytecode,
324 label,
325 with_local_artifacts,
326 debug,
327 decode_internal,
328 disable_labels,
329 self.trace_depth,
330 )
331 .await?;
332
333 Ok(())
334 }
335}
336
337pub fn fetch_contracts_bytecode_from_trace(
338 executor: &Executor,
339 result: &TraceResult,
340) -> Result<HashMap<Address, Bytes>> {
341 let mut contracts_bytecode = HashMap::default();
342 if let Some(ref traces) = result.traces {
343 contracts_bytecode.extend(gather_trace_addresses(traces).filter_map(|addr| {
344 let code = executor
346 .backend()
347 .basic_ref(addr)
348 .inspect_err(|e| _ = sh_warn!("Failed to fetch code for {addr}: {e}"))
349 .ok()??
350 .code?
351 .bytes();
352 if code.is_empty() {
353 return None;
354 }
355 Some((addr, code))
356 }));
357 }
358 Ok(contracts_bytecode)
359}
360
361fn gather_trace_addresses(traces: &Traces) -> impl Iterator<Item = Address> {
362 let mut addresses = AddressSet::default();
363 for (_, trace) in traces {
364 for node in trace.arena.nodes() {
365 if !node.trace.address.is_zero() {
366 addresses.insert(node.trace.address);
367 }
368 if !node.trace.caller.is_zero() {
369 addresses.insert(node.trace.caller);
370 }
371 }
372 }
373 addresses.into_iter()
374}
375
376impl figment::Provider for RunArgs {
377 fn metadata(&self) -> Metadata {
378 Metadata::named("RunArgs")
379 }
380
381 fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
382 let mut map = Map::new();
383
384 if let Some(api_key) = &self.etherscan.key {
385 map.insert("etherscan_api_key".into(), api_key.as_str().into());
386 }
387
388 if let Some(evm_version) = self.evm_version {
389 map.insert("evm_version".into(), figment::value::Value::serialize(evm_version)?);
390 }
391
392 Ok(Map::from([(Config::selected_profile(), map)]))
393 }
394}