foundry_evm_traces/decoder/
mod.rs

1use crate::{
2    debug::DebugTraceIdentifier,
3    identifier::{IdentifiedAddress, LocalTraceIdentifier, SignaturesIdentifier, TraceIdentifier},
4    CallTrace, CallTraceArena, CallTraceNode, DecodedCallData,
5};
6use alloy_dyn_abi::{DecodedEvent, DynSolValue, EventExt, FunctionExt, JsonAbiExt};
7use alloy_json_abi::{Error, Event, Function, JsonAbi};
8use alloy_primitives::{
9    map::{hash_map::Entry, HashMap, HashSet},
10    Address, LogData, Selector, B256,
11};
12use foundry_common::{
13    abi::get_indexed_event, fmt::format_token, get_contract_name, selectors::SelectorKind,
14    ContractsByArtifact, SELECTOR_LEN,
15};
16use foundry_evm_core::{
17    abi::{console, Vm},
18    constants::{
19        CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, HARDHAT_CONSOLE_ADDRESS,
20        TEST_CONTRACT_ADDRESS,
21    },
22    decode::RevertDecoder,
23    precompiles::{
24        BLAKE_2F, EC_ADD, EC_MUL, EC_PAIRING, EC_RECOVER, IDENTITY, MOD_EXP, POINT_EVALUATION,
25        RIPEMD_160, SHA_256,
26    },
27};
28use itertools::Itertools;
29use revm_inspectors::tracing::types::{DecodedCallLog, DecodedCallTrace};
30use std::{collections::BTreeMap, sync::OnceLock};
31
32mod precompiles;
33
34/// Build a new [CallTraceDecoder].
35#[derive(Default)]
36#[must_use = "builders do nothing unless you call `build` on them"]
37pub struct CallTraceDecoderBuilder {
38    decoder: CallTraceDecoder,
39}
40
41impl CallTraceDecoderBuilder {
42    /// Create a new builder.
43    #[inline]
44    pub fn new() -> Self {
45        Self { decoder: CallTraceDecoder::new().clone() }
46    }
47
48    /// Add known labels to the decoder.
49    #[inline]
50    pub fn with_labels(mut self, labels: impl IntoIterator<Item = (Address, String)>) -> Self {
51        self.decoder.labels.extend(labels);
52        self
53    }
54
55    /// Add known errors to the decoder.
56    #[inline]
57    pub fn with_abi(mut self, abi: &JsonAbi) -> Self {
58        self.decoder.collect_abi(abi, None);
59        self
60    }
61
62    /// Add known contracts to the decoder.
63    #[inline]
64    pub fn with_known_contracts(mut self, contracts: &ContractsByArtifact) -> Self {
65        trace!(target: "evm::traces", len=contracts.len(), "collecting known contract ABIs");
66        for contract in contracts.values() {
67            self.decoder.collect_abi(&contract.abi, None);
68        }
69        self
70    }
71
72    /// Add known contracts to the decoder from a `LocalTraceIdentifier`.
73    #[inline]
74    pub fn with_local_identifier_abis(self, identifier: &LocalTraceIdentifier<'_>) -> Self {
75        self.with_known_contracts(identifier.contracts())
76    }
77
78    /// Sets the verbosity level of the decoder.
79    #[inline]
80    pub fn with_verbosity(mut self, level: u8) -> Self {
81        self.decoder.verbosity = level;
82        self
83    }
84
85    /// Sets the signature identifier for events and functions.
86    #[inline]
87    pub fn with_signature_identifier(mut self, identifier: SignaturesIdentifier) -> Self {
88        self.decoder.signature_identifier = Some(identifier);
89        self
90    }
91
92    /// Sets the debug identifier for the decoder.
93    #[inline]
94    pub fn with_debug_identifier(mut self, identifier: DebugTraceIdentifier) -> Self {
95        self.decoder.debug_identifier = Some(identifier);
96        self
97    }
98
99    /// Build the decoder.
100    #[inline]
101    pub fn build(self) -> CallTraceDecoder {
102        self.decoder
103    }
104}
105
106/// The call trace decoder.
107///
108/// The decoder collects address labels and ABIs from any number of [TraceIdentifier]s, which it
109/// then uses to decode the call trace.
110///
111/// Note that a call trace decoder is required for each new set of traces, since addresses in
112/// different sets might overlap.
113#[derive(Clone, Debug, Default)]
114pub struct CallTraceDecoder {
115    /// Addresses identified to be a specific contract.
116    ///
117    /// The values are in the form `"<artifact>:<contract>"`.
118    pub contracts: HashMap<Address, String>,
119    /// Address labels.
120    pub labels: HashMap<Address, String>,
121    /// Contract addresses that have a receive function.
122    pub receive_contracts: HashSet<Address>,
123    /// Contract addresses that have fallback functions, mapped to function selectors of that
124    /// contract.
125    pub fallback_contracts: HashMap<Address, HashSet<Selector>>,
126
127    /// All known functions.
128    pub functions: HashMap<Selector, Vec<Function>>,
129    /// All known events.
130    ///
131    /// Key is: `(topics[0], topics.len() - 1)`.
132    pub events: BTreeMap<(B256, usize), Vec<Event>>,
133    /// Revert decoder. Contains all known custom errors.
134    pub revert_decoder: RevertDecoder,
135
136    /// A signature identifier for events and functions.
137    pub signature_identifier: Option<SignaturesIdentifier>,
138    /// Verbosity level
139    pub verbosity: u8,
140
141    /// Optional identifier of individual trace steps.
142    pub debug_identifier: Option<DebugTraceIdentifier>,
143}
144
145impl CallTraceDecoder {
146    /// Creates a new call trace decoder.
147    ///
148    /// The call trace decoder always knows how to decode calls to the cheatcode address, as well
149    /// as DSTest-style logs.
150    pub fn new() -> &'static Self {
151        // If you want to take arguments in this function, assign them to the fields of the cloned
152        // lazy instead of removing it
153        static INIT: OnceLock<CallTraceDecoder> = OnceLock::new();
154        INIT.get_or_init(Self::init)
155    }
156
157    fn init() -> Self {
158        Self {
159            contracts: Default::default(),
160            labels: HashMap::from_iter([
161                (CHEATCODE_ADDRESS, "VM".to_string()),
162                (HARDHAT_CONSOLE_ADDRESS, "console".to_string()),
163                (DEFAULT_CREATE2_DEPLOYER, "Create2Deployer".to_string()),
164                (CALLER, "DefaultSender".to_string()),
165                (TEST_CONTRACT_ADDRESS, "DefaultTestContract".to_string()),
166                (EC_RECOVER, "ECRecover".to_string()),
167                (SHA_256, "SHA-256".to_string()),
168                (RIPEMD_160, "RIPEMD-160".to_string()),
169                (IDENTITY, "Identity".to_string()),
170                (MOD_EXP, "ModExp".to_string()),
171                (EC_ADD, "ECAdd".to_string()),
172                (EC_MUL, "ECMul".to_string()),
173                (EC_PAIRING, "ECPairing".to_string()),
174                (BLAKE_2F, "Blake2F".to_string()),
175                (POINT_EVALUATION, "PointEvaluation".to_string()),
176            ]),
177            receive_contracts: Default::default(),
178            fallback_contracts: Default::default(),
179
180            functions: console::hh::abi::functions()
181                .into_values()
182                .chain(Vm::abi::functions().into_values())
183                .flatten()
184                .map(|func| (func.selector(), vec![func]))
185                .collect(),
186            events: console::ds::abi::events()
187                .into_values()
188                .flatten()
189                .map(|event| ((event.selector(), indexed_inputs(&event)), vec![event]))
190                .collect(),
191            revert_decoder: Default::default(),
192
193            signature_identifier: None,
194            verbosity: 0,
195
196            debug_identifier: None,
197        }
198    }
199
200    /// Clears all known addresses.
201    pub fn clear_addresses(&mut self) {
202        self.contracts.clear();
203
204        let default_labels = &Self::new().labels;
205        if self.labels.len() > default_labels.len() {
206            self.labels.clone_from(default_labels);
207        }
208
209        self.receive_contracts.clear();
210        self.fallback_contracts.clear();
211    }
212
213    /// Identify unknown addresses in the specified call trace using the specified identifier.
214    ///
215    /// Unknown contracts are contracts that either lack a label or an ABI.
216    pub fn identify(&mut self, arena: &CallTraceArena, identifier: &mut impl TraceIdentifier) {
217        self.collect_identified_addresses(self.identify_addresses(arena, identifier));
218    }
219
220    /// Identify unknown addresses in the specified call trace using the specified identifier.
221    ///
222    /// Unknown contracts are contracts that either lack a label or an ABI.
223    pub fn identify_addresses<'a>(
224        &self,
225        arena: &CallTraceArena,
226        identifier: &'a mut impl TraceIdentifier,
227    ) -> Vec<IdentifiedAddress<'a>> {
228        let nodes = arena.nodes().iter().filter(|node| {
229            let address = &node.trace.address;
230            !self.labels.contains_key(address) || !self.contracts.contains_key(address)
231        });
232        identifier.identify_addresses(&nodes.collect::<Vec<_>>())
233    }
234
235    /// Adds a single event to the decoder.
236    pub fn push_event(&mut self, event: Event) {
237        self.events.entry((event.selector(), indexed_inputs(&event))).or_default().push(event);
238    }
239
240    /// Adds a single function to the decoder.
241    pub fn push_function(&mut self, function: Function) {
242        match self.functions.entry(function.selector()) {
243            Entry::Occupied(entry) => {
244                // This shouldn't happen that often.
245                if entry.get().contains(&function) {
246                    return;
247                }
248                trace!(target: "evm::traces", selector=%entry.key(), new=%function.signature(), "duplicate function selector");
249                entry.into_mut().push(function);
250            }
251            Entry::Vacant(entry) => {
252                entry.insert(vec![function]);
253            }
254        }
255    }
256
257    /// Adds a single error to the decoder.
258    pub fn push_error(&mut self, error: Error) {
259        self.revert_decoder.push_error(error);
260    }
261
262    fn collect_identified_addresses(&mut self, mut addrs: Vec<IdentifiedAddress<'_>>) {
263        addrs.sort_by_key(|identity| identity.address);
264        addrs.dedup_by_key(|identity| identity.address);
265        if addrs.is_empty() {
266            return;
267        }
268
269        trace!(target: "evm::traces", len=addrs.len(), "collecting address identities");
270        for IdentifiedAddress { address, label, contract, abi, artifact_id: _ } in addrs {
271            let _span = trace_span!(target: "evm::traces", "identity", ?contract, ?label).entered();
272
273            if let Some(contract) = contract {
274                self.contracts.entry(address).or_insert(contract);
275            }
276
277            if let Some(label) = label {
278                self.labels.entry(address).or_insert(label);
279            }
280
281            if let Some(abi) = abi {
282                self.collect_abi(&abi, Some(address));
283            }
284        }
285    }
286
287    fn collect_abi(&mut self, abi: &JsonAbi, address: Option<Address>) {
288        let len = abi.len();
289        if len == 0 {
290            return;
291        }
292        trace!(target: "evm::traces", len, ?address, "collecting ABI");
293        for function in abi.functions() {
294            self.push_function(function.clone());
295        }
296        for event in abi.events() {
297            self.push_event(event.clone());
298        }
299        for error in abi.errors() {
300            self.push_error(error.clone());
301        }
302        if let Some(address) = address {
303            if abi.receive.is_some() {
304                self.receive_contracts.insert(address);
305            }
306
307            if abi.fallback.is_some() {
308                self.fallback_contracts
309                    .insert(address, abi.functions().map(|f| f.selector()).collect());
310            }
311        }
312    }
313
314    /// Populates the traces with decoded data by mutating the
315    /// [CallTrace] in place. See [CallTraceDecoder::decode_function] and
316    /// [CallTraceDecoder::decode_event] for more details.
317    pub async fn populate_traces(&self, traces: &mut Vec<CallTraceNode>) {
318        for node in traces {
319            node.trace.decoded = self.decode_function(&node.trace).await;
320            for log in &mut node.logs {
321                log.decoded = self.decode_event(&log.raw_log).await;
322            }
323
324            if let Some(debug) = self.debug_identifier.as_ref() {
325                if let Some(identified) = self.contracts.get(&node.trace.address) {
326                    debug.identify_node_steps(node, get_contract_name(identified))
327                }
328            }
329        }
330    }
331
332    /// Decodes a call trace.
333    pub async fn decode_function(&self, trace: &CallTrace) -> DecodedCallTrace {
334        let label = self.labels.get(&trace.address).cloned();
335
336        if trace.kind.is_any_create() {
337            return DecodedCallTrace { label, ..Default::default() };
338        }
339
340        if let Some(trace) = precompiles::decode(trace, 1) {
341            return trace;
342        }
343
344        let cdata = &trace.data;
345        if trace.address == DEFAULT_CREATE2_DEPLOYER {
346            return DecodedCallTrace {
347                label,
348                call_data: Some(DecodedCallData { signature: "create2".to_string(), args: vec![] }),
349                return_data: self.default_return_data(trace),
350            };
351        }
352
353        if is_abi_call_data(cdata) {
354            let selector = Selector::try_from(&cdata[..SELECTOR_LEN]).unwrap();
355            let mut functions = Vec::new();
356            let functions = match self.functions.get(&selector) {
357                Some(fs) => fs,
358                None => {
359                    if let Some(identifier) = &self.signature_identifier {
360                        if let Some(function) = identifier.identify_function(selector).await {
361                            functions.push(function);
362                        }
363                    }
364                    &functions
365                }
366            };
367            let [func, ..] = &functions[..] else {
368                return DecodedCallTrace {
369                    label,
370                    call_data: self.fallback_call_data(trace),
371                    return_data: self.default_return_data(trace),
372                };
373            };
374
375            // If traced contract is a fallback contract, check if it has the decoded function.
376            // If not, then replace call data signature with `fallback`.
377            let mut call_data = self.decode_function_input(trace, func);
378            if let Some(fallback_functions) = self.fallback_contracts.get(&trace.address) {
379                if !fallback_functions.contains(&selector) {
380                    if let Some(cd) = self.fallback_call_data(trace) {
381                        call_data.signature = cd.signature;
382                    }
383                }
384            }
385
386            DecodedCallTrace {
387                label,
388                call_data: Some(call_data),
389                return_data: self.decode_function_output(trace, functions),
390            }
391        } else {
392            DecodedCallTrace {
393                label,
394                call_data: self.fallback_call_data(trace),
395                return_data: self.default_return_data(trace),
396            }
397        }
398    }
399
400    /// Decodes a function's input into the given trace.
401    fn decode_function_input(&self, trace: &CallTrace, func: &Function) -> DecodedCallData {
402        let mut args = None;
403        if trace.data.len() >= SELECTOR_LEN {
404            if trace.address == CHEATCODE_ADDRESS {
405                // Try to decode cheatcode inputs in a more custom way
406                if let Some(v) = self.decode_cheatcode_inputs(func, &trace.data) {
407                    args = Some(v);
408                }
409            }
410
411            if args.is_none() {
412                if let Ok(v) = func.abi_decode_input(&trace.data[SELECTOR_LEN..], false) {
413                    args = Some(v.iter().map(|value| self.format_value(value)).collect());
414                }
415            }
416        }
417
418        DecodedCallData { signature: func.signature(), args: args.unwrap_or_default() }
419    }
420
421    /// Custom decoding for cheatcode inputs.
422    fn decode_cheatcode_inputs(&self, func: &Function, data: &[u8]) -> Option<Vec<String>> {
423        match func.name.as_str() {
424            "expectRevert" => Some(vec![self.revert_decoder.decode(data, None)]),
425            "addr" | "createWallet" | "deriveKey" | "rememberKey" => {
426                // Redact private key in all cases
427                Some(vec!["<pk>".to_string()])
428            }
429            "broadcast" | "startBroadcast" => {
430                // Redact private key if defined
431                // broadcast(uint256) / startBroadcast(uint256)
432                if !func.inputs.is_empty() && func.inputs[0].ty == "uint256" {
433                    Some(vec!["<pk>".to_string()])
434                } else {
435                    None
436                }
437            }
438            "getNonce" => {
439                // Redact private key if defined
440                // getNonce(Wallet)
441                if !func.inputs.is_empty() && func.inputs[0].ty == "tuple" {
442                    Some(vec!["<pk>".to_string()])
443                } else {
444                    None
445                }
446            }
447            "sign" | "signP256" => {
448                let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..], false).ok()?;
449
450                // Redact private key and replace in trace
451                // sign(uint256,bytes32) / signP256(uint256,bytes32) / sign(Wallet,bytes32)
452                if !decoded.is_empty() &&
453                    (func.inputs[0].ty == "uint256" || func.inputs[0].ty == "tuple")
454                {
455                    decoded[0] = DynSolValue::String("<pk>".to_string());
456                }
457
458                Some(decoded.iter().map(format_token).collect())
459            }
460            "signDelegation" | "signAndAttachDelegation" => {
461                let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..], false).ok()?;
462                // Redact private key and replace in trace for
463                // signAndAttachDelegation(address implementation, uint256 privateKey)
464                // signDelegation(address implementation, uint256 privateKey)
465                decoded[1] = DynSolValue::String("<pk>".to_string());
466                Some(decoded.iter().map(format_token).collect())
467            }
468            "parseJson" |
469            "parseJsonUint" |
470            "parseJsonUintArray" |
471            "parseJsonInt" |
472            "parseJsonIntArray" |
473            "parseJsonString" |
474            "parseJsonStringArray" |
475            "parseJsonAddress" |
476            "parseJsonAddressArray" |
477            "parseJsonBool" |
478            "parseJsonBoolArray" |
479            "parseJsonBytes" |
480            "parseJsonBytesArray" |
481            "parseJsonBytes32" |
482            "parseJsonBytes32Array" |
483            "writeJson" |
484            // `keyExists` is being deprecated in favor of `keyExistsJson`. It will be removed in future versions.
485            "keyExists" | 
486            "keyExistsJson" |
487            "serializeBool" |
488            "serializeUint" |
489            "serializeUintToHex" |
490            "serializeInt" |
491            "serializeAddress" |
492            "serializeBytes32" |
493            "serializeString" |
494            "serializeBytes" => {
495                if self.verbosity >= 5 {
496                    None
497                } else {
498                    let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..], false).ok()?;
499                    let token = if func.name.as_str() == "parseJson" ||
500                        // `keyExists` is being deprecated in favor of `keyExistsJson`. It will be removed in future versions.
501                        func.name.as_str() == "keyExists" || 
502                        func.name.as_str() == "keyExistsJson"
503                    {
504                        "<JSON file>"
505                    } else {
506                        "<stringified JSON>"
507                    };
508                    decoded[0] = DynSolValue::String(token.to_string());
509                    Some(decoded.iter().map(format_token).collect())
510                }
511            }
512            s if s.contains("Toml") => {
513                if self.verbosity >= 5 {
514                    None
515                } else {
516                    let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..], false).ok()?;
517                    let token = if func.name.as_str() == "parseToml" ||
518                        func.name.as_str() == "keyExistsToml"
519                    {
520                        "<TOML file>"
521                    } else {
522                        "<stringified TOML>"
523                    };
524                    decoded[0] = DynSolValue::String(token.to_string());
525                    Some(decoded.iter().map(format_token).collect())
526                }
527            }
528            "createFork" |
529            "createSelectFork" |
530            "rpc" => {
531                let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..], false).ok()?;
532
533                // Redact RPC URL except if referenced by an alias
534                if !decoded.is_empty() && func.inputs[0].ty == "string" {
535                    let url_or_alias = decoded[0].as_str().unwrap_or_default();
536
537                    if url_or_alias.starts_with("http") || url_or_alias.starts_with("ws") {
538                        decoded[0] = DynSolValue::String("<rpc url>".to_string());
539                    }
540                } else {
541                    return None;
542                }
543
544                Some(decoded.iter().map(format_token).collect())
545            }
546            _ => None,
547        }
548    }
549
550    /// Decodes a function's output into the given trace.
551    fn decode_function_output(&self, trace: &CallTrace, funcs: &[Function]) -> Option<String> {
552        if !trace.success {
553            return self.default_return_data(trace);
554        }
555
556        if trace.address == CHEATCODE_ADDRESS {
557            if let Some(decoded) = funcs.iter().find_map(|func| self.decode_cheatcode_outputs(func))
558            {
559                return Some(decoded);
560            }
561        }
562
563        if let Some(values) =
564            funcs.iter().find_map(|func| func.abi_decode_output(&trace.output, false).ok())
565        {
566            // Functions coming from an external database do not have any outputs specified,
567            // and will lead to returning an empty list of values.
568            if values.is_empty() {
569                return None;
570            }
571
572            return Some(
573                values.iter().map(|value| self.format_value(value)).format(", ").to_string(),
574            );
575        }
576
577        None
578    }
579
580    /// Custom decoding for cheatcode outputs.
581    fn decode_cheatcode_outputs(&self, func: &Function) -> Option<String> {
582        match func.name.as_str() {
583            s if s.starts_with("env") => Some("<env var value>"),
584            "createWallet" | "deriveKey" => Some("<pk>"),
585            "promptSecret" | "promptSecretUint" => Some("<secret>"),
586            "parseJson" if self.verbosity < 5 => Some("<encoded JSON value>"),
587            "readFile" if self.verbosity < 5 => Some("<file>"),
588            "rpcUrl" | "rpcUrls" | "rpcUrlStructs" => Some("<rpc url>"),
589            _ => None,
590        }
591        .map(Into::into)
592    }
593
594    #[track_caller]
595    fn fallback_call_data(&self, trace: &CallTrace) -> Option<DecodedCallData> {
596        let cdata = &trace.data;
597        let signature = if cdata.is_empty() && self.receive_contracts.contains(&trace.address) {
598            "receive()"
599        } else if self.fallback_contracts.contains_key(&trace.address) {
600            "fallback()"
601        } else {
602            return None;
603        }
604        .to_string();
605        let args = if cdata.is_empty() { Vec::new() } else { vec![cdata.to_string()] };
606        Some(DecodedCallData { signature, args })
607    }
608
609    /// The default decoded return data for a trace.
610    fn default_return_data(&self, trace: &CallTrace) -> Option<String> {
611        (!trace.success).then(|| self.revert_decoder.decode(&trace.output, Some(trace.status)))
612    }
613
614    /// Decodes an event.
615    pub async fn decode_event(&self, log: &LogData) -> DecodedCallLog {
616        let &[t0, ..] = log.topics() else { return DecodedCallLog { name: None, params: None } };
617
618        let mut events = Vec::new();
619        let events = match self.events.get(&(t0, log.topics().len() - 1)) {
620            Some(es) => es,
621            None => {
622                if let Some(identifier) = &self.signature_identifier {
623                    if let Some(event) = identifier.identify_event(t0).await {
624                        events.push(get_indexed_event(event, log));
625                    }
626                }
627                &events
628            }
629        };
630        for event in events {
631            if let Ok(decoded) = event.decode_log(log, false) {
632                let params = reconstruct_params(event, &decoded);
633                return DecodedCallLog {
634                    name: Some(event.name.clone()),
635                    params: Some(
636                        params
637                            .into_iter()
638                            .zip(event.inputs.iter())
639                            .map(|(param, input)| {
640                                // undo patched names
641                                let name = input.name.clone();
642                                (name, self.format_value(&param))
643                            })
644                            .collect(),
645                    ),
646                };
647            }
648        }
649
650        DecodedCallLog { name: None, params: None }
651    }
652
653    /// Prefetches function and event signatures into the identifier cache
654    pub async fn prefetch_signatures(&self, nodes: &[CallTraceNode]) {
655        let Some(identifier) = &self.signature_identifier else { return };
656        let events = nodes
657            .iter()
658            .flat_map(|node| {
659                node.logs
660                    .iter()
661                    .map(|log| log.raw_log.topics())
662                    .filter(|&topics| {
663                        if let Some(&first) = topics.first() {
664                            if self.events.contains_key(&(first, topics.len() - 1)) {
665                                return false;
666                            }
667                        }
668                        true
669                    })
670                    .filter_map(|topics| topics.first())
671            })
672            .copied();
673        let functions = nodes
674            .iter()
675            .filter(|&n| {
676                // Ignore known addresses.
677                if n.trace.address == DEFAULT_CREATE2_DEPLOYER ||
678                    n.is_precompile() ||
679                    precompiles::is_known_precompile(n.trace.address, 1)
680                {
681                    return false;
682                }
683                // Ignore non-ABI calldata.
684                if n.trace.kind.is_any_create() || !is_abi_call_data(&n.trace.data) {
685                    return false;
686                }
687                true
688            })
689            .filter_map(|n| n.trace.data.first_chunk().map(Selector::from))
690            .filter(|selector| !self.functions.contains_key(selector));
691        let selectors = events
692            .map(SelectorKind::Event)
693            .chain(functions.map(SelectorKind::Function))
694            .unique()
695            .collect::<Vec<_>>();
696        let _ = identifier.identify(&selectors).await;
697    }
698
699    /// Pretty-prints a value.
700    fn format_value(&self, value: &DynSolValue) -> String {
701        if let DynSolValue::Address(addr) = value {
702            if let Some(label) = self.labels.get(addr) {
703                return format!("{label}: [{addr}]");
704            }
705        }
706        format_token(value)
707    }
708}
709
710/// Returns `true` if the given function calldata (including function selector) is ABI-encoded.
711///
712/// This is a simple heuristic to avoid fetching non ABI-encoded selectors.
713fn is_abi_call_data(data: &[u8]) -> bool {
714    match data.len().cmp(&SELECTOR_LEN) {
715        std::cmp::Ordering::Less => false,
716        std::cmp::Ordering::Equal => true,
717        std::cmp::Ordering::Greater => is_abi_data(&data[SELECTOR_LEN..]),
718    }
719}
720
721/// Returns `true` if the given data is ABI-encoded.
722///
723/// See [`is_abi_call_data`] for more details.
724fn is_abi_data(data: &[u8]) -> bool {
725    let rem = data.len() % 32;
726    if rem == 0 || data.is_empty() {
727        return true;
728    }
729    // If the length is not a multiple of 32, also accept when the last remainder bytes are all 0.
730    data[data.len() - rem..].iter().all(|byte| *byte == 0)
731}
732
733/// Restore the order of the params of a decoded event,
734/// as Alloy returns the indexed and unindexed params separately.
735fn reconstruct_params(event: &Event, decoded: &DecodedEvent) -> Vec<DynSolValue> {
736    let mut indexed = 0;
737    let mut unindexed = 0;
738    let mut inputs = vec![];
739    for input in &event.inputs {
740        // Prevent panic of event `Transfer(from, to)` decoded with a signature
741        // `Transfer(address indexed from, address indexed to, uint256 indexed tokenId)` by making
742        // sure the event inputs is not higher than decoded indexed / un-indexed values.
743        if input.indexed && indexed < decoded.indexed.len() {
744            inputs.push(decoded.indexed[indexed].clone());
745            indexed += 1;
746        } else if unindexed < decoded.body.len() {
747            inputs.push(decoded.body[unindexed].clone());
748            unindexed += 1;
749        }
750    }
751
752    inputs
753}
754
755fn indexed_inputs(event: &Event) -> usize {
756    event.inputs.iter().filter(|param| param.indexed).count()
757}
758
759#[cfg(test)]
760mod tests {
761    use super::*;
762    use alloy_primitives::hex;
763
764    #[test]
765    fn test_should_redact() {
766        let decoder = CallTraceDecoder::new();
767
768        // [function_signature, data, expected]
769        let cheatcode_input_test_cases = vec![
770            // Should redact private key from traces in all cases:
771            ("addr(uint256)", vec![], Some(vec!["<pk>".to_string()])),
772            ("createWallet(string)", vec![], Some(vec!["<pk>".to_string()])),
773            ("createWallet(uint256)", vec![], Some(vec!["<pk>".to_string()])),
774            ("deriveKey(string,uint32)", vec![], Some(vec!["<pk>".to_string()])),
775            ("deriveKey(string,string,uint32)", vec![], Some(vec!["<pk>".to_string()])),
776            ("deriveKey(string,uint32,string)", vec![], Some(vec!["<pk>".to_string()])),
777            ("deriveKey(string,string,uint32,string)", vec![], Some(vec!["<pk>".to_string()])),
778            ("rememberKey(uint256)", vec![], Some(vec!["<pk>".to_string()])),
779            //
780            // Should redact private key from traces in specific cases with exceptions:
781            ("broadcast(uint256)", vec![], Some(vec!["<pk>".to_string()])),
782            ("broadcast()", vec![], None), // Ignore: `private key` is not passed.
783            ("startBroadcast(uint256)", vec![], Some(vec!["<pk>".to_string()])),
784            ("startBroadcast()", vec![], None), // Ignore: `private key` is not passed.
785            ("getNonce((address,uint256,uint256,uint256))", vec![], Some(vec!["<pk>".to_string()])),
786            ("getNonce(address)", vec![], None), // Ignore: `address` is public.
787            //
788            // Should redact private key and replace in trace in cases:
789            (
790                "sign(uint256,bytes32)",
791                hex!(
792                    "
793                    e341eaa4
794                    7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6
795                    0000000000000000000000000000000000000000000000000000000000000000
796                "
797                )
798                .to_vec(),
799                Some(vec![
800                    "\"<pk>\"".to_string(),
801                    "0x0000000000000000000000000000000000000000000000000000000000000000"
802                        .to_string(),
803                ]),
804            ),
805            (
806                "signP256(uint256,bytes32)",
807                hex!(
808                    "
809                    83211b40
810                    7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6
811                    0000000000000000000000000000000000000000000000000000000000000000
812                "
813                )
814                .to_vec(),
815                Some(vec![
816                    "\"<pk>\"".to_string(),
817                    "0x0000000000000000000000000000000000000000000000000000000000000000"
818                        .to_string(),
819                ]),
820            ),
821            (
822                // cast calldata "createFork(string)" "https://eth-mainnet.g.alchemy.com/v2/api_key"
823                "createFork(string)",
824                hex!(
825                    "
826                    31ba3498
827                    0000000000000000000000000000000000000000000000000000000000000020
828                    000000000000000000000000000000000000000000000000000000000000002c
829                    68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
830                    6d2f76322f6170695f6b65790000000000000000000000000000000000000000
831                    "
832                )
833                .to_vec(),
834                Some(vec!["\"<rpc url>\"".to_string()]),
835            ),
836            (
837                // cast calldata "createFork(string)" "wss://eth-mainnet.g.alchemy.com/v2/api_key"
838                "createFork(string)",
839                hex!(
840                    "
841                    31ba3498
842                    0000000000000000000000000000000000000000000000000000000000000020
843                    000000000000000000000000000000000000000000000000000000000000002a
844                    7773733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f6d2f
845                    76322f6170695f6b657900000000000000000000000000000000000000000000
846                    "
847                )
848                .to_vec(),
849                Some(vec!["\"<rpc url>\"".to_string()]),
850            ),
851            (
852                // cast calldata "createFork(string)" "mainnet"
853                "createFork(string)",
854                hex!(
855                    "
856                    31ba3498
857                    0000000000000000000000000000000000000000000000000000000000000020
858                    0000000000000000000000000000000000000000000000000000000000000007
859                    6d61696e6e657400000000000000000000000000000000000000000000000000
860                    "
861                )
862                .to_vec(),
863                Some(vec!["\"mainnet\"".to_string()]),
864            ),
865            (
866                // cast calldata "createFork(string,uint256)" "https://eth-mainnet.g.alchemy.com/v2/api_key" 1
867                "createFork(string,uint256)",
868                hex!(
869                    "
870                    6ba3ba2b
871                    0000000000000000000000000000000000000000000000000000000000000040
872                    0000000000000000000000000000000000000000000000000000000000000001
873                    000000000000000000000000000000000000000000000000000000000000002c
874                    68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
875                    6d2f76322f6170695f6b65790000000000000000000000000000000000000000
876                "
877                )
878                .to_vec(),
879                Some(vec!["\"<rpc url>\"".to_string(), "1".to_string()]),
880            ),
881            (
882                // cast calldata "createFork(string,uint256)" "mainnet" 1
883                "createFork(string,uint256)",
884                hex!(
885                    "
886                    6ba3ba2b
887                    0000000000000000000000000000000000000000000000000000000000000040
888                    0000000000000000000000000000000000000000000000000000000000000001
889                    0000000000000000000000000000000000000000000000000000000000000007
890                    6d61696e6e657400000000000000000000000000000000000000000000000000
891                "
892                )
893                .to_vec(),
894                Some(vec!["\"mainnet\"".to_string(), "1".to_string()]),
895            ),
896            (
897                // cast calldata "createFork(string,bytes32)" "https://eth-mainnet.g.alchemy.com/v2/api_key" 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
898                "createFork(string,bytes32)",
899                hex!(
900                    "
901                    7ca29682
902                    0000000000000000000000000000000000000000000000000000000000000040
903                    ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
904                    000000000000000000000000000000000000000000000000000000000000002c
905                    68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
906                    6d2f76322f6170695f6b65790000000000000000000000000000000000000000
907                "
908                )
909                .to_vec(),
910                Some(vec![
911                    "\"<rpc url>\"".to_string(),
912                    "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
913                        .to_string(),
914                ]),
915            ),
916            (
917                // cast calldata "createFork(string,bytes32)" "mainnet"
918                // 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
919                "createFork(string,bytes32)",
920                hex!(
921                    "
922                    7ca29682
923                    0000000000000000000000000000000000000000000000000000000000000040
924                    ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
925                    0000000000000000000000000000000000000000000000000000000000000007
926                    6d61696e6e657400000000000000000000000000000000000000000000000000
927                "
928                )
929                .to_vec(),
930                Some(vec![
931                    "\"mainnet\"".to_string(),
932                    "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
933                        .to_string(),
934                ]),
935            ),
936            (
937                // cast calldata "createSelectFork(string)" "https://eth-mainnet.g.alchemy.com/v2/api_key"
938                "createSelectFork(string)",
939                hex!(
940                    "
941                    98680034
942                    0000000000000000000000000000000000000000000000000000000000000020
943                    000000000000000000000000000000000000000000000000000000000000002c
944                    68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
945                    6d2f76322f6170695f6b65790000000000000000000000000000000000000000
946                    "
947                )
948                .to_vec(),
949                Some(vec!["\"<rpc url>\"".to_string()]),
950            ),
951            (
952                // cast calldata "createSelectFork(string)" "mainnet"
953                "createSelectFork(string)",
954                hex!(
955                    "
956                    98680034
957                    0000000000000000000000000000000000000000000000000000000000000020
958                    0000000000000000000000000000000000000000000000000000000000000007
959                    6d61696e6e657400000000000000000000000000000000000000000000000000
960                    "
961                )
962                .to_vec(),
963                Some(vec!["\"mainnet\"".to_string()]),
964            ),
965            (
966                // cast calldata "createSelectFork(string,uint256)" "https://eth-mainnet.g.alchemy.com/v2/api_key" 1
967                "createSelectFork(string,uint256)",
968                hex!(
969                    "
970                    71ee464d
971                    0000000000000000000000000000000000000000000000000000000000000040
972                    0000000000000000000000000000000000000000000000000000000000000001
973                    000000000000000000000000000000000000000000000000000000000000002c
974                    68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
975                    6d2f76322f6170695f6b65790000000000000000000000000000000000000000
976                "
977                )
978                .to_vec(),
979                Some(vec!["\"<rpc url>\"".to_string(), "1".to_string()]),
980            ),
981            (
982                // cast calldata "createSelectFork(string,uint256)" "mainnet" 1
983                "createSelectFork(string,uint256)",
984                hex!(
985                    "
986                    71ee464d
987                    0000000000000000000000000000000000000000000000000000000000000040
988                    0000000000000000000000000000000000000000000000000000000000000001
989                    0000000000000000000000000000000000000000000000000000000000000007
990                    6d61696e6e657400000000000000000000000000000000000000000000000000
991                "
992                )
993                .to_vec(),
994                Some(vec!["\"mainnet\"".to_string(), "1".to_string()]),
995            ),
996            (
997                // cast calldata "createSelectFork(string,bytes32)" "https://eth-mainnet.g.alchemy.com/v2/api_key" 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
998                "createSelectFork(string,bytes32)",
999                hex!(
1000                    "
1001                    84d52b7a
1002                    0000000000000000000000000000000000000000000000000000000000000040
1003                    ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
1004                    000000000000000000000000000000000000000000000000000000000000002c
1005                    68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
1006                    6d2f76322f6170695f6b65790000000000000000000000000000000000000000
1007                "
1008                )
1009                .to_vec(),
1010                Some(vec![
1011                    "\"<rpc url>\"".to_string(),
1012                    "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
1013                        .to_string(),
1014                ]),
1015            ),
1016            (
1017                // cast calldata "createSelectFork(string,bytes32)" "mainnet"
1018                // 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
1019                "createSelectFork(string,bytes32)",
1020                hex!(
1021                    "
1022                    84d52b7a
1023                    0000000000000000000000000000000000000000000000000000000000000040
1024                    ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
1025                    0000000000000000000000000000000000000000000000000000000000000007
1026                    6d61696e6e657400000000000000000000000000000000000000000000000000
1027                "
1028                )
1029                .to_vec(),
1030                Some(vec![
1031                    "\"mainnet\"".to_string(),
1032                    "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
1033                        .to_string(),
1034                ]),
1035            ),
1036            (
1037                // cast calldata "rpc(string,string,string)" "https://eth-mainnet.g.alchemy.com/v2/api_key" "eth_getBalance" "[\"0x551e7784778ef8e048e495df49f2614f84a4f1dc\",\"0x0\"]"
1038                "rpc(string,string,string)",
1039                hex!(
1040                    "
1041                    0199a220
1042                    0000000000000000000000000000000000000000000000000000000000000060
1043                    00000000000000000000000000000000000000000000000000000000000000c0
1044                    0000000000000000000000000000000000000000000000000000000000000100
1045                    000000000000000000000000000000000000000000000000000000000000002c
1046                    68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
1047                    6d2f76322f6170695f6b65790000000000000000000000000000000000000000
1048                    000000000000000000000000000000000000000000000000000000000000000e
1049                    6574685f67657442616c616e6365000000000000000000000000000000000000
1050                    0000000000000000000000000000000000000000000000000000000000000034
1051                    5b22307835353165373738343737386566386530343865343935646634396632
1052                    363134663834613466316463222c22307830225d000000000000000000000000
1053                "
1054                )
1055                .to_vec(),
1056                Some(vec![
1057                    "\"<rpc url>\"".to_string(),
1058                    "\"eth_getBalance\"".to_string(),
1059                    "\"[\\\"0x551e7784778ef8e048e495df49f2614f84a4f1dc\\\",\\\"0x0\\\"]\""
1060                        .to_string(),
1061                ]),
1062            ),
1063            (
1064                // cast calldata "rpc(string,string,string)" "mainnet" "eth_getBalance"
1065                // "[\"0x551e7784778ef8e048e495df49f2614f84a4f1dc\",\"0x0\"]"
1066                "rpc(string,string,string)",
1067                hex!(
1068                    "
1069                    0199a220
1070                    0000000000000000000000000000000000000000000000000000000000000060
1071                    00000000000000000000000000000000000000000000000000000000000000a0
1072                    00000000000000000000000000000000000000000000000000000000000000e0
1073                    0000000000000000000000000000000000000000000000000000000000000007
1074                    6d61696e6e657400000000000000000000000000000000000000000000000000
1075                    000000000000000000000000000000000000000000000000000000000000000e
1076                    6574685f67657442616c616e6365000000000000000000000000000000000000
1077                    0000000000000000000000000000000000000000000000000000000000000034
1078                    5b22307835353165373738343737386566386530343865343935646634396632
1079                    363134663834613466316463222c22307830225d000000000000000000000000
1080                "
1081                )
1082                .to_vec(),
1083                Some(vec![
1084                    "\"mainnet\"".to_string(),
1085                    "\"eth_getBalance\"".to_string(),
1086                    "\"[\\\"0x551e7784778ef8e048e495df49f2614f84a4f1dc\\\",\\\"0x0\\\"]\""
1087                        .to_string(),
1088                ]),
1089            ),
1090        ];
1091
1092        // [function_signature, expected]
1093        let cheatcode_output_test_cases = vec![
1094            // Should redact private key on output in all cases:
1095            ("createWallet(string)", Some("<pk>".to_string())),
1096            ("deriveKey(string,uint32)", Some("<pk>".to_string())),
1097            // Should redact RPC URL if defined, except if referenced by an alias:
1098            ("rpcUrl(string)", Some("<rpc url>".to_string())),
1099            ("rpcUrls()", Some("<rpc url>".to_string())),
1100            ("rpcUrlStructs()", Some("<rpc url>".to_string())),
1101        ];
1102
1103        for (function_signature, data, expected) in cheatcode_input_test_cases {
1104            let function = Function::parse(function_signature).unwrap();
1105            let result = decoder.decode_cheatcode_inputs(&function, &data);
1106            assert_eq!(result, expected, "Input case failed for: {function_signature}");
1107        }
1108
1109        for (function_signature, expected) in cheatcode_output_test_cases {
1110            let function = Function::parse(function_signature).unwrap();
1111            let result = Some(decoder.decode_cheatcode_outputs(&function).unwrap_or_default());
1112            assert_eq!(result, expected, "Output case failed for: {function_signature}");
1113        }
1114    }
1115}