Skip to main content

foundry_evm_traces/decoder/
mod.rs

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