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
53impl TracingArgs {
54    /// Resolves CLI overrides against the configured trace rendering settings.
55    pub fn resolve(&self, config: &TracingConfig, verbosity: u8) -> TracingConfig {
56        let mut tracing = config.clone();
57        tracing.verbosity = tracing.verbosity.max(verbosity);
58        tracing.labels.extend(self.parsed_labels());
59        tracing.disable_labels |= self.disable_labels;
60        tracing.compact_labels |= self.compact_labels;
61        tracing.trace_depth = self.trace_depth.or(tracing.trace_depth);
62        tracing.decode_internal |= self.decode_internal;
63        tracing
64    }
65
66    /// Resolves trace rendering settings for an RPC call-tracer response.
67    pub fn resolve_call_tracer(&self, config: &TracingConfig, verbosity: u8) -> TracingConfig {
68        let mut tracing = self.resolve(config, verbosity);
69        tracing.decode_internal = false;
70        tracing
71    }
72
73    fn parsed_labels(&self) -> AddressHashMap<String> {
74        self.labels
75            .iter()
76            .filter_map(|label| {
77                let (address, label) = label.split_once(':')?;
78                let address = Address::from_str(address).ok()?;
79                Some((address, label.to_string()))
80            })
81            .collect()
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88    use alloy_primitives::address;
89
90    #[test]
91    fn resolve_merges_cli_overrides() {
92        let address = address!("0x0000000000000000000000000000000000000001");
93        let config = TracingConfig {
94            verbosity: 2,
95            labels: AddressHashMap::from_iter([(address, "config".to_string())]),
96            disable_labels: false,
97            compact_labels: false,
98            trace_depth: Some(1),
99            decode_internal: false,
100        };
101        let args = TracingArgs {
102            decode_internal: true,
103            trace_depth: Some(2),
104            disable_labels: true,
105            compact_labels: true,
106            labels: vec![format!("{address}:cli")],
107        };
108
109        let tracing = args.resolve(&config, 3);
110        assert_eq!(tracing.verbosity, 3);
111        assert_eq!(tracing.labels.get(&address), Some(&"cli".to_string()));
112        assert!(tracing.disable_labels);
113        assert!(tracing.compact_labels);
114        assert_eq!(tracing.trace_depth, Some(2));
115        assert!(tracing.decode_internal);
116    }
117
118    #[test]
119    fn tracing_verbosity_is_independent_from_global_verbosity() {
120        let config = TracingConfig { verbosity: 5, ..Default::default() };
121
122        assert_eq!(TracingArgs::default().resolve(&config, 1).verbosity, 5);
123        assert_eq!(TracingArgs::default().resolve(&config, 0).verbosity, 5);
124        assert_eq!(TracingArgs::default().resolve(&config, 6).verbosity, 6);
125    }
126
127    #[test]
128    fn call_tracer_does_not_decode_internal_functions() {
129        let config = TracingConfig { decode_internal: true, ..Default::default() };
130        let args = TracingArgs { decode_internal: true, ..Default::default() };
131
132        assert!(!args.resolve_call_tracer(&config, 0).decode_internal);
133    }
134}