Skip to main content

foundry_cli/opts/
tracing.rs

1//! CLI arguments for configuring trace rendering.
2
3use std::str::FromStr;
4
5use alloy_primitives::{Address, map::AddressHashMap};
6use clap::{Parser, ValueHint};
7use foundry_config::TracingConfig;
8use serde::Serialize;
9
10/// CLI arguments for trace rendering.
11#[derive(Clone, Debug, Default, Serialize, Parser)]
12#[command(about = None, long_about = None)]
13pub struct TracingArgs {
14    /// Identify internal functions in traces.
15    ///
16    /// This will trace internal functions and decode stack parameters.
17    ///
18    /// Parameters stored in memory (such as bytes or arrays) are currently decoded only when a
19    /// single function is matched, similarly to `--debug`, for performance reasons.
20    #[arg(long, help_heading = "Trace options")]
21    #[serde(skip)]
22    pub decode_internal: bool,
23
24    /// Maximum depth of rendered traces.
25    #[arg(long, value_name = "DEPTH", help_heading = "Trace options")]
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub trace_depth: Option<usize>,
28
29    /// Disable labels in traces.
30    #[arg(long, help_heading = "Trace options")]
31    #[serde(skip)]
32    pub disable_labels: bool,
33
34    /// Hide addresses in trace parameters when a label is available.
35    #[arg(long, help_heading = "Trace options")]
36    #[serde(skip)]
37    pub compact_labels: bool,
38
39    /// Label addresses in traces.
40    ///
41    /// Example: 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045:vitalik.eth
42    #[arg(
43        long = "labels",
44        visible_alias = "label",
45        value_name = "ADDRESS:LABEL",
46        value_hint = ValueHint::Other,
47        help_heading = "Trace options"
48    )]
49    #[serde(skip_serializing_if = "Vec::is_empty")]
50    pub labels: Vec<String>,
51
52    /// Disable external trace identification using Sourcify, Etherscan, or OpenChain.
53    #[arg(long, help_heading = "Trace options")]
54    #[serde(skip)]
55    pub disable_external_identification: bool,
56}
57
58impl TracingArgs {
59    /// Resolves CLI overrides against the configured trace rendering settings.
60    pub fn resolve(&self, config: &TracingConfig, verbosity: u8) -> TracingConfig {
61        let mut tracing = config.clone();
62        tracing.verbosity = tracing.verbosity.max(verbosity);
63        tracing.labels.extend(self.parsed_labels());
64        tracing.disable_labels |= self.disable_labels;
65        tracing.compact_labels |= self.compact_labels;
66        tracing.trace_depth = self.trace_depth.or(tracing.trace_depth);
67        tracing.decode_internal |= self.decode_internal;
68        if self.disable_external_identification {
69            tracing.external_identification_timeout = 0;
70        }
71        tracing
72    }
73
74    /// Resolves trace rendering settings for an RPC call-tracer response.
75    pub fn resolve_call_tracer(&self, config: &TracingConfig, verbosity: u8) -> TracingConfig {
76        let mut tracing = self.resolve(config, verbosity);
77        tracing.decode_internal = false;
78        tracing
79    }
80
81    fn parsed_labels(&self) -> AddressHashMap<String> {
82        self.labels
83            .iter()
84            .filter_map(|label| {
85                let (address, label) = label.split_once(':')?;
86                let address = Address::from_str(address).ok()?;
87                Some((address, label.to_string()))
88            })
89            .collect()
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96    use alloy_primitives::address;
97
98    #[test]
99    fn resolve_merges_cli_overrides() {
100        let address = address!("0x0000000000000000000000000000000000000001");
101        let config = TracingConfig {
102            verbosity: 2,
103            labels: AddressHashMap::from_iter([(address, "config".to_string())]),
104            disable_labels: false,
105            compact_labels: false,
106            trace_depth: Some(1),
107            decode_internal: false,
108            external_identification_timeout: 10,
109        };
110        let args = TracingArgs {
111            decode_internal: true,
112            trace_depth: Some(2),
113            disable_labels: true,
114            compact_labels: true,
115            labels: vec![format!("{address}:cli")],
116            disable_external_identification: true,
117        };
118
119        let tracing = args.resolve(&config, 3);
120        assert_eq!(tracing.verbosity, 3);
121        assert_eq!(tracing.labels.get(&address), Some(&"cli".to_string()));
122        assert!(tracing.disable_labels);
123        assert!(tracing.compact_labels);
124        assert_eq!(tracing.trace_depth, Some(2));
125        assert!(tracing.decode_internal);
126        assert_eq!(tracing.external_identification_timeout, 0);
127    }
128
129    #[test]
130    fn resolve_preserves_external_identification_timeout() {
131        let config = TracingConfig { external_identification_timeout: 17, ..Default::default() };
132
133        let tracing = TracingArgs::default().resolve(&config, 0);
134
135        assert_eq!(tracing.external_identification_timeout, 17);
136    }
137
138    #[test]
139    fn tracing_verbosity_is_independent_from_global_verbosity() {
140        let config = TracingConfig { verbosity: 5, ..Default::default() };
141
142        assert_eq!(TracingArgs::default().resolve(&config, 1).verbosity, 5);
143        assert_eq!(TracingArgs::default().resolve(&config, 0).verbosity, 5);
144        assert_eq!(TracingArgs::default().resolve(&config, 6).verbosity, 6);
145    }
146
147    #[test]
148    fn call_tracer_does_not_decode_internal_functions() {
149        let config = TracingConfig { decode_internal: true, ..Default::default() };
150        let args = TracingArgs { decode_internal: true, ..Default::default() };
151
152        assert!(!args.resolve_call_tracer(&config, 0).decode_internal);
153    }
154}