cast/cmd/
trace.rs

1use alloy_eips::Encodable2718;
2use alloy_network::AnyRpcTransaction;
3use alloy_primitives::hex;
4use alloy_provider::ext::TraceApi;
5use clap::Parser;
6use eyre::Result;
7use foundry_cli::{
8    opts::RpcOpts,
9    utils::{self, LoadConfig},
10};
11use foundry_common::stdin;
12
13/// CLI arguments for `cast trace`.
14#[derive(Debug, Parser)]
15pub struct TraceArgs {
16    /// Transaction hash (for trace_transaction) or raw tx hex/JSON (for trace_rawTransaction
17    /// with --raw)
18    tx: Option<String>,
19
20    /// Use trace_rawTransaction instead of trace_transaction.
21    /// Required when passing raw transaction hex or JSON instead of a tx hash.
22    #[arg(long)]
23    raw: bool,
24
25    /// Include the basic trace of the transaction.
26    #[arg(long, requires = "raw")]
27    trace: bool,
28
29    /// Include the full trace of the virtual machine's state during transaction execution
30    #[arg(long, requires = "raw")]
31    vm_trace: bool,
32
33    /// Include state changes caused by the transaction (requires --raw).
34    #[arg(long, requires = "raw")]
35    state_diff: bool,
36
37    #[command(flatten)]
38    rpc: RpcOpts,
39}
40
41impl TraceArgs {
42    pub async fn run(self) -> Result<()> {
43        let config = self.rpc.load_config()?;
44        let provider = utils::get_provider(&config)?;
45        let input = stdin::unwrap_line(self.tx)?;
46
47        let trimmed = input.trim();
48        let is_json = trimmed.starts_with('{');
49        let is_raw_hex = trimmed.starts_with("0x") && trimmed.len() > 66;
50
51        let result = if self.raw {
52            // trace_rawTransaction: accepts raw hex OR JSON tx
53            let raw_bytes = if is_raw_hex {
54                hex::decode(trimmed.strip_prefix("0x").unwrap_or(trimmed))?
55            } else if is_json {
56                let tx: AnyRpcTransaction = serde_json::from_str(trimmed)?;
57                tx.inner.inner.encoded_2718().to_vec()
58            } else {
59                hex::decode(trimmed)?
60            };
61
62            let mut trace_builder = provider.trace_raw_transaction(&raw_bytes);
63
64            if self.trace {
65                trace_builder = trace_builder.trace();
66            }
67            if self.vm_trace {
68                trace_builder = trace_builder.vm_trace();
69            }
70            if self.state_diff {
71                trace_builder = trace_builder.state_diff();
72            }
73
74            if trace_builder.get_trace_types().map(|t| t.is_empty()).unwrap_or(true) {
75                eyre::bail!("No trace type specified. Use --trace, --vm-trace, or --state-diff");
76            }
77
78            serde_json::to_string_pretty(&trace_builder.await?)?
79        } else {
80            // trace_transaction: use tx hash directly
81            let hash = input.parse()?;
82            let traces = provider.trace_transaction(hash).await?;
83            serde_json::to_string_pretty(&traces)?
84        };
85
86        sh_println!("{}", result)?;
87        Ok(())
88    }
89}