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#[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 #[inline]
44 pub fn new() -> Self {
45 Self { decoder: CallTraceDecoder::new().clone() }
46 }
47
48 #[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 #[inline]
57 pub fn with_abi(mut self, abi: &JsonAbi) -> Self {
58 self.decoder.collect_abi(abi, None);
59 self
60 }
61
62 #[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 #[inline]
74 pub fn with_local_identifier_abis(self, identifier: &LocalTraceIdentifier<'_>) -> Self {
75 self.with_known_contracts(identifier.contracts())
76 }
77
78 #[inline]
80 pub fn with_verbosity(mut self, level: u8) -> Self {
81 self.decoder.verbosity = level;
82 self
83 }
84
85 #[inline]
87 pub fn with_signature_identifier(mut self, identifier: SignaturesIdentifier) -> Self {
88 self.decoder.signature_identifier = Some(identifier);
89 self
90 }
91
92 #[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 #[inline]
101 pub fn with_debug_identifier(mut self, identifier: DebugTraceIdentifier) -> Self {
102 self.decoder.debug_identifier = Some(identifier);
103 self
104 }
105
106 #[inline]
108 pub fn build(self) -> CallTraceDecoder {
109 self.decoder
110 }
111}
112
113#[derive(Clone, Debug, Default)]
121pub struct CallTraceDecoder {
122 pub contracts: HashMap<Address, String>,
126 pub labels: HashMap<Address, String>,
128 pub receive_contracts: HashSet<Address>,
130 pub fallback_contracts: HashMap<Address, HashSet<Selector>>,
133 pub non_fallback_contracts: HashMap<Address, HashSet<Selector>>,
136
137 pub functions: HashMap<Selector, Vec<Function>>,
139 pub events: BTreeMap<(B256, usize), Vec<Event>>,
143 pub revert_decoder: RevertDecoder,
145
146 pub signature_identifier: Option<SignaturesIdentifier>,
148 pub verbosity: u8,
150
151 pub debug_identifier: Option<DebugTraceIdentifier>,
153
154 pub disable_labels: bool,
156}
157
158impl CallTraceDecoder {
159 pub fn new() -> &'static Self {
164 static INIT: OnceLock<CallTraceDecoder> = OnceLock::new();
167 INIT.get_or_init(Self::init)
168 }
169
170 #[instrument(name = "CallTraceDecoder::init", level = "debug")]
171 fn init() -> Self {
172 Self {
173 contracts: Default::default(),
174 labels: HashMap::from_iter([
175 (CHEATCODE_ADDRESS, "VM".to_string()),
176 (HARDHAT_CONSOLE_ADDRESS, "console".to_string()),
177 (DEFAULT_CREATE2_DEPLOYER, "Create2Deployer".to_string()),
178 (CALLER, "DefaultSender".to_string()),
179 (TEST_CONTRACT_ADDRESS, "DefaultTestContract".to_string()),
180 (EC_RECOVER, "ECRecover".to_string()),
181 (SHA_256, "SHA-256".to_string()),
182 (RIPEMD_160, "RIPEMD-160".to_string()),
183 (IDENTITY, "Identity".to_string()),
184 (MOD_EXP, "ModExp".to_string()),
185 (EC_ADD, "ECAdd".to_string()),
186 (EC_MUL, "ECMul".to_string()),
187 (EC_PAIRING, "ECPairing".to_string()),
188 (BLAKE_2F, "Blake2F".to_string()),
189 (POINT_EVALUATION, "PointEvaluation".to_string()),
190 ]),
191 receive_contracts: Default::default(),
192 fallback_contracts: Default::default(),
193 non_fallback_contracts: Default::default(),
194
195 functions: console::hh::abi::functions()
196 .into_values()
197 .chain(Vm::abi::functions().into_values())
198 .flatten()
199 .map(|func| (func.selector(), vec![func]))
200 .collect(),
201 events: console::ds::abi::events()
202 .into_values()
203 .flatten()
204 .map(|event| ((event.selector(), indexed_inputs(&event)), vec![event]))
205 .collect(),
206 revert_decoder: Default::default(),
207
208 signature_identifier: None,
209 verbosity: 0,
210
211 debug_identifier: None,
212
213 disable_labels: false,
214 }
215 }
216
217 pub fn clear_addresses(&mut self) {
219 self.contracts.clear();
220
221 let default_labels = &Self::new().labels;
222 if self.labels.len() > default_labels.len() {
223 self.labels.clone_from(default_labels);
224 }
225
226 self.receive_contracts.clear();
227 self.fallback_contracts.clear();
228 }
229
230 pub fn identify(&mut self, arena: &CallTraceArena, identifier: &mut impl TraceIdentifier) {
234 self.collect_identified_addresses(self.identify_addresses(arena, identifier));
235 }
236
237 pub fn identify_addresses<'a>(
241 &self,
242 arena: &CallTraceArena,
243 identifier: &'a mut impl TraceIdentifier,
244 ) -> Vec<IdentifiedAddress<'a>> {
245 let nodes = arena.nodes().iter().filter(|node| {
246 let address = &node.trace.address;
247 !self.labels.contains_key(address) || !self.contracts.contains_key(address)
248 });
249 identifier.identify_addresses(&nodes.collect::<Vec<_>>())
250 }
251
252 pub fn push_event(&mut self, event: Event) {
254 self.events.entry((event.selector(), indexed_inputs(&event))).or_default().push(event);
255 }
256
257 pub fn push_function(&mut self, function: Function) {
259 match self.functions.entry(function.selector()) {
260 Entry::Occupied(entry) => {
261 if entry.get().contains(&function) {
263 return;
264 }
265 trace!(target: "evm::traces", selector=%entry.key(), new=%function.signature(), "duplicate function selector");
266 entry.into_mut().push(function);
267 }
268 Entry::Vacant(entry) => {
269 entry.insert(vec![function]);
270 }
271 }
272 }
273
274 pub fn push_error(&mut self, error: Error) {
276 self.revert_decoder.push_error(error);
277 }
278
279 pub fn without_label(&mut self, disable: bool) {
280 self.disable_labels = disable;
281 }
282
283 fn collect_identified_addresses(&mut self, mut addrs: Vec<IdentifiedAddress<'_>>) {
284 addrs.sort_by_key(|identity| identity.address);
285 addrs.dedup_by_key(|identity| identity.address);
286 if addrs.is_empty() {
287 return;
288 }
289
290 trace!(target: "evm::traces", len=addrs.len(), "collecting address identities");
291 for IdentifiedAddress { address, label, contract, abi, artifact_id: _ } in addrs {
292 let _span = trace_span!(target: "evm::traces", "identity", ?contract, ?label).entered();
293
294 if let Some(contract) = contract {
295 self.contracts.entry(address).or_insert(contract);
296 }
297
298 if let Some(label) = label {
299 self.labels.entry(address).or_insert(label);
300 }
301
302 if let Some(abi) = abi {
303 self.collect_abi(&abi, Some(address));
304 }
305 }
306 }
307
308 fn collect_abi(&mut self, abi: &JsonAbi, address: Option<Address>) {
309 let len = abi.len();
310 if len == 0 {
311 return;
312 }
313 trace!(target: "evm::traces", len, ?address, "collecting ABI");
314 for function in abi.functions() {
315 self.push_function(function.clone());
316 }
317 for event in abi.events() {
318 self.push_event(event.clone());
319 }
320 for error in abi.errors() {
321 self.push_error(error.clone());
322 }
323 if let Some(address) = address {
324 if abi.receive.is_some() {
325 self.receive_contracts.insert(address);
326 }
327
328 if abi.fallback.is_some() {
329 self.fallback_contracts
330 .insert(address, abi.functions().map(|f| f.selector()).collect());
331 } else {
332 self.non_fallback_contracts
333 .insert(address, abi.functions().map(|f| f.selector()).collect());
334 }
335 }
336 }
337
338 pub async fn populate_traces(&self, traces: &mut Vec<CallTraceNode>) {
342 for node in traces {
343 node.trace.decoded = Some(Box::new(self.decode_function(&node.trace).await));
344 for log in &mut node.logs {
345 log.decoded = Some(Box::new(self.decode_event(&log.raw_log).await));
346 }
347
348 if let Some(debug) = self.debug_identifier.as_ref()
349 && let Some(identified) = self.contracts.get(&node.trace.address)
350 {
351 debug.identify_node_steps(node, get_contract_name(identified))
352 }
353 }
354 }
355
356 pub async fn decode_function(&self, trace: &CallTrace) -> DecodedCallTrace {
358 let label =
359 if self.disable_labels { None } else { self.labels.get(&trace.address).cloned() };
360
361 if trace.kind.is_any_create() {
362 return DecodedCallTrace { label, ..Default::default() };
363 }
364
365 if let Some(trace) = precompiles::decode(trace, 1) {
366 return trace;
367 }
368
369 let cdata = &trace.data;
370 if trace.address == DEFAULT_CREATE2_DEPLOYER {
371 return DecodedCallTrace {
372 label,
373 call_data: Some(DecodedCallData { signature: "create2".to_string(), args: vec![] }),
374 return_data: self.default_return_data(trace),
375 };
376 }
377
378 if is_abi_call_data(cdata) {
379 let selector = Selector::try_from(&cdata[..SELECTOR_LEN]).unwrap();
380 let mut functions = Vec::new();
381 let functions = match self.functions.get(&selector) {
382 Some(fs) => fs,
383 None => {
384 if let Some(identifier) = &self.signature_identifier
385 && let Some(function) = identifier.identify_function(selector).await
386 {
387 functions.push(function);
388 }
389 &functions
390 }
391 };
392
393 if let Some(contract_selectors) = self.non_fallback_contracts.get(&trace.address)
396 && !contract_selectors.contains(&selector)
397 && (!cdata.is_empty() || !self.receive_contracts.contains(&trace.address))
398 {
399 let return_data = if !trace.success {
400 let revert_msg = self.revert_decoder.decode(&trace.output, trace.status);
401
402 if trace.output.is_empty() || revert_msg.contains("EvmError: Revert") {
403 Some(format!(
404 "unrecognized function selector {} for contract {}, which has no fallback function.",
405 selector, trace.address
406 ))
407 } else {
408 Some(revert_msg)
409 }
410 } else {
411 None
412 };
413
414 return if let Some(func) = functions.first() {
415 DecodedCallTrace {
416 label,
417 call_data: Some(self.decode_function_input(trace, func)),
418 return_data,
419 }
420 } else {
421 DecodedCallTrace {
422 label,
423 call_data: self.fallback_call_data(trace),
424 return_data,
425 }
426 };
427 }
428
429 let [func, ..] = &functions[..] else {
430 return DecodedCallTrace {
431 label,
432 call_data: self.fallback_call_data(trace),
433 return_data: self.default_return_data(trace),
434 };
435 };
436
437 let mut call_data = self.decode_function_input(trace, func);
440 if let Some(fallback_functions) = self.fallback_contracts.get(&trace.address)
441 && !fallback_functions.contains(&selector)
442 && let Some(cd) = self.fallback_call_data(trace)
443 {
444 call_data.signature = cd.signature;
445 }
446
447 DecodedCallTrace {
448 label,
449 call_data: Some(call_data),
450 return_data: self.decode_function_output(trace, functions),
451 }
452 } else {
453 DecodedCallTrace {
454 label,
455 call_data: self.fallback_call_data(trace),
456 return_data: self.default_return_data(trace),
457 }
458 }
459 }
460
461 fn decode_function_input(&self, trace: &CallTrace, func: &Function) -> DecodedCallData {
463 let mut args = None;
464 if trace.data.len() >= SELECTOR_LEN {
465 if trace.address == CHEATCODE_ADDRESS {
466 if let Some(v) = self.decode_cheatcode_inputs(func, &trace.data) {
468 args = Some(v);
469 }
470 }
471
472 if args.is_none()
473 && let Ok(v) = func.abi_decode_input(&trace.data[SELECTOR_LEN..])
474 {
475 args = Some(v.iter().map(|value| self.format_value(value)).collect());
476 }
477 }
478
479 DecodedCallData { signature: func.signature(), args: args.unwrap_or_default() }
480 }
481
482 fn decode_cheatcode_inputs(&self, func: &Function, data: &[u8]) -> Option<Vec<String>> {
484 match func.name.as_str() {
485 "expectRevert" => Some(vec![self.revert_decoder.decode(data, None)]),
486 "addr" | "createWallet" | "deriveKey" | "rememberKey" => {
487 Some(vec!["<pk>".to_string()])
489 }
490 "broadcast" | "startBroadcast" => {
491 if !func.inputs.is_empty() && func.inputs[0].ty == "uint256" {
494 Some(vec!["<pk>".to_string()])
495 } else {
496 None
497 }
498 }
499 "getNonce" => {
500 if !func.inputs.is_empty() && func.inputs[0].ty == "tuple" {
503 Some(vec!["<pk>".to_string()])
504 } else {
505 None
506 }
507 }
508 "sign" | "signP256" => {
509 let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?;
510
511 if !decoded.is_empty() &&
514 (func.inputs[0].ty == "uint256" || func.inputs[0].ty == "tuple")
515 {
516 decoded[0] = DynSolValue::String("<pk>".to_string());
517 }
518
519 Some(decoded.iter().map(format_token).collect())
520 }
521 "signDelegation" | "signAndAttachDelegation" => {
522 let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?;
523 decoded[1] = DynSolValue::String("<pk>".to_string());
527 Some(decoded.iter().map(format_token).collect())
528 }
529 "parseJson" |
530 "parseJsonUint" |
531 "parseJsonUintArray" |
532 "parseJsonInt" |
533 "parseJsonIntArray" |
534 "parseJsonString" |
535 "parseJsonStringArray" |
536 "parseJsonAddress" |
537 "parseJsonAddressArray" |
538 "parseJsonBool" |
539 "parseJsonBoolArray" |
540 "parseJsonBytes" |
541 "parseJsonBytesArray" |
542 "parseJsonBytes32" |
543 "parseJsonBytes32Array" |
544 "writeJson" |
545 "keyExists" |
547 "keyExistsJson" |
548 "serializeBool" |
549 "serializeUint" |
550 "serializeUintToHex" |
551 "serializeInt" |
552 "serializeAddress" |
553 "serializeBytes32" |
554 "serializeString" |
555 "serializeBytes" => {
556 if self.verbosity >= 5 {
557 None
558 } else {
559 let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?;
560 let token = if func.name.as_str() == "parseJson" ||
561 func.name.as_str() == "keyExists" ||
563 func.name.as_str() == "keyExistsJson"
564 {
565 "<JSON file>"
566 } else {
567 "<stringified JSON>"
568 };
569 decoded[0] = DynSolValue::String(token.to_string());
570 Some(decoded.iter().map(format_token).collect())
571 }
572 }
573 s if s.contains("Toml") => {
574 if self.verbosity >= 5 {
575 None
576 } else {
577 let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?;
578 let token = if func.name.as_str() == "parseToml" ||
579 func.name.as_str() == "keyExistsToml"
580 {
581 "<TOML file>"
582 } else {
583 "<stringified TOML>"
584 };
585 decoded[0] = DynSolValue::String(token.to_string());
586 Some(decoded.iter().map(format_token).collect())
587 }
588 }
589 "createFork" |
590 "createSelectFork" |
591 "rpc" => {
592 let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?;
593
594 if !decoded.is_empty() && func.inputs[0].ty == "string" {
596 let url_or_alias = decoded[0].as_str().unwrap_or_default();
597
598 if url_or_alias.starts_with("http") || url_or_alias.starts_with("ws") {
599 decoded[0] = DynSolValue::String("<rpc url>".to_string());
600 }
601 } else {
602 return None;
603 }
604
605 Some(decoded.iter().map(format_token).collect())
606 }
607 _ => None,
608 }
609 }
610
611 fn decode_function_output(&self, trace: &CallTrace, funcs: &[Function]) -> Option<String> {
613 if !trace.success {
614 return self.default_return_data(trace);
615 }
616
617 if trace.address == CHEATCODE_ADDRESS
618 && let Some(decoded) = funcs.iter().find_map(|func| self.decode_cheatcode_outputs(func))
619 {
620 return Some(decoded);
621 }
622
623 if let Some(values) =
624 funcs.iter().find_map(|func| func.abi_decode_output(&trace.output).ok())
625 {
626 if values.is_empty() {
629 return None;
630 }
631
632 return Some(
633 values.iter().map(|value| self.format_value(value)).format(", ").to_string(),
634 );
635 }
636
637 None
638 }
639
640 fn decode_cheatcode_outputs(&self, func: &Function) -> Option<String> {
642 match func.name.as_str() {
643 s if s.starts_with("env") => Some("<env var value>"),
644 "createWallet" | "deriveKey" => Some("<pk>"),
645 "promptSecret" | "promptSecretUint" => Some("<secret>"),
646 "parseJson" if self.verbosity < 5 => Some("<encoded JSON value>"),
647 "readFile" if self.verbosity < 5 => Some("<file>"),
648 "rpcUrl" | "rpcUrls" | "rpcUrlStructs" => Some("<rpc url>"),
649 _ => None,
650 }
651 .map(Into::into)
652 }
653
654 #[track_caller]
655 fn fallback_call_data(&self, trace: &CallTrace) -> Option<DecodedCallData> {
656 let cdata = &trace.data;
657 let signature = if cdata.is_empty() && self.receive_contracts.contains(&trace.address) {
658 "receive()"
659 } else if self.fallback_contracts.contains_key(&trace.address) {
660 "fallback()"
661 } else {
662 return None;
663 }
664 .to_string();
665 let args = if cdata.is_empty() { Vec::new() } else { vec![cdata.to_string()] };
666 Some(DecodedCallData { signature, args })
667 }
668
669 fn default_return_data(&self, trace: &CallTrace) -> Option<String> {
671 if trace.status.is_none() || trace.status.is_some_and(|s| s.is_ok()) {
676 return None;
677 }
678 (!trace.success).then(|| self.revert_decoder.decode(&trace.output, trace.status))
679 }
680
681 pub async fn decode_event(&self, log: &LogData) -> DecodedCallLog {
683 let &[t0, ..] = log.topics() else { return DecodedCallLog { name: None, params: None } };
684
685 let mut events = Vec::new();
686 let events = match self.events.get(&(t0, log.topics().len() - 1)) {
687 Some(es) => es,
688 None => {
689 if let Some(identifier) = &self.signature_identifier
690 && let Some(event) = identifier.identify_event(t0).await
691 {
692 events.push(get_indexed_event(event, log));
693 }
694 &events
695 }
696 };
697 for event in events {
698 if let Ok(decoded) = event.decode_log(log) {
699 let params = reconstruct_params(event, &decoded);
700 return DecodedCallLog {
701 name: Some(event.name.clone()),
702 params: Some(
703 params
704 .into_iter()
705 .zip(event.inputs.iter())
706 .map(|(param, input)| {
707 let name = input.name.clone();
709 (name, self.format_value(¶m))
710 })
711 .collect(),
712 ),
713 };
714 }
715 }
716
717 DecodedCallLog { name: None, params: None }
718 }
719
720 pub async fn prefetch_signatures(&self, nodes: &[CallTraceNode]) {
722 let Some(identifier) = &self.signature_identifier else { return };
723 let events = nodes
724 .iter()
725 .flat_map(|node| {
726 node.logs
727 .iter()
728 .map(|log| log.raw_log.topics())
729 .filter(|&topics| {
730 if let Some(&first) = topics.first()
731 && self.events.contains_key(&(first, topics.len() - 1))
732 {
733 return false;
734 }
735 true
736 })
737 .filter_map(|topics| topics.first())
738 })
739 .copied();
740 let functions = nodes
741 .iter()
742 .filter(|&n| {
743 if n.trace.address == DEFAULT_CREATE2_DEPLOYER
745 || n.is_precompile()
746 || precompiles::is_known_precompile(n.trace.address, 1)
747 {
748 return false;
749 }
750 if n.trace.kind.is_any_create() || !is_abi_call_data(&n.trace.data) {
752 return false;
753 }
754 true
755 })
756 .filter_map(|n| n.trace.data.first_chunk().map(Selector::from))
757 .filter(|selector| !self.functions.contains_key(selector));
758 let selectors = events
759 .map(SelectorKind::Event)
760 .chain(functions.map(SelectorKind::Function))
761 .unique()
762 .collect::<Vec<_>>();
763 let _ = identifier.identify(&selectors).await;
764 }
765
766 fn format_value(&self, value: &DynSolValue) -> String {
768 if let DynSolValue::Address(addr) = value
769 && let Some(label) = self.labels.get(addr)
770 {
771 return format!("{label}: [{addr}]");
772 }
773 format_token(value)
774 }
775}
776
777fn is_abi_call_data(data: &[u8]) -> bool {
781 match data.len().cmp(&SELECTOR_LEN) {
782 std::cmp::Ordering::Less => false,
783 std::cmp::Ordering::Equal => true,
784 std::cmp::Ordering::Greater => is_abi_data(&data[SELECTOR_LEN..]),
785 }
786}
787
788fn is_abi_data(data: &[u8]) -> bool {
792 let rem = data.len() % 32;
793 if rem == 0 || data.is_empty() {
794 return true;
795 }
796 data[data.len() - rem..].iter().all(|byte| *byte == 0)
798}
799
800fn reconstruct_params(event: &Event, decoded: &DecodedEvent) -> Vec<DynSolValue> {
803 let mut indexed = 0;
804 let mut unindexed = 0;
805 let mut inputs = vec![];
806 for input in &event.inputs {
807 if input.indexed && indexed < decoded.indexed.len() {
811 inputs.push(decoded.indexed[indexed].clone());
812 indexed += 1;
813 } else if unindexed < decoded.body.len() {
814 inputs.push(decoded.body[unindexed].clone());
815 unindexed += 1;
816 }
817 }
818
819 inputs
820}
821
822fn indexed_inputs(event: &Event) -> usize {
823 event.inputs.iter().filter(|param| param.indexed).count()
824}
825
826#[cfg(test)]
827mod tests {
828 use super::*;
829 use alloy_primitives::hex;
830
831 #[test]
832 fn test_should_redact() {
833 let decoder = CallTraceDecoder::new();
834
835 let cheatcode_input_test_cases = vec![
837 ("addr(uint256)", vec![], Some(vec!["<pk>".to_string()])),
839 ("createWallet(string)", vec![], Some(vec!["<pk>".to_string()])),
840 ("createWallet(uint256)", vec![], Some(vec!["<pk>".to_string()])),
841 ("deriveKey(string,uint32)", vec![], Some(vec!["<pk>".to_string()])),
842 ("deriveKey(string,string,uint32)", vec![], Some(vec!["<pk>".to_string()])),
843 ("deriveKey(string,uint32,string)", vec![], Some(vec!["<pk>".to_string()])),
844 ("deriveKey(string,string,uint32,string)", vec![], Some(vec!["<pk>".to_string()])),
845 ("rememberKey(uint256)", vec![], Some(vec!["<pk>".to_string()])),
846 ("broadcast(uint256)", vec![], Some(vec!["<pk>".to_string()])),
849 ("broadcast()", vec![], None), ("startBroadcast(uint256)", vec![], Some(vec!["<pk>".to_string()])),
851 ("startBroadcast()", vec![], None), ("getNonce((address,uint256,uint256,uint256))", vec![], Some(vec!["<pk>".to_string()])),
853 ("getNonce(address)", vec![], None), (
857 "sign(uint256,bytes32)",
858 hex!(
859 "
860 e341eaa4
861 7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6
862 0000000000000000000000000000000000000000000000000000000000000000
863 "
864 )
865 .to_vec(),
866 Some(vec![
867 "\"<pk>\"".to_string(),
868 "0x0000000000000000000000000000000000000000000000000000000000000000"
869 .to_string(),
870 ]),
871 ),
872 (
873 "signP256(uint256,bytes32)",
874 hex!(
875 "
876 83211b40
877 7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6
878 0000000000000000000000000000000000000000000000000000000000000000
879 "
880 )
881 .to_vec(),
882 Some(vec![
883 "\"<pk>\"".to_string(),
884 "0x0000000000000000000000000000000000000000000000000000000000000000"
885 .to_string(),
886 ]),
887 ),
888 (
889 "createFork(string)",
891 hex!(
892 "
893 31ba3498
894 0000000000000000000000000000000000000000000000000000000000000020
895 000000000000000000000000000000000000000000000000000000000000002c
896 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
897 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
898 "
899 )
900 .to_vec(),
901 Some(vec!["\"<rpc url>\"".to_string()]),
902 ),
903 (
904 "createFork(string)",
906 hex!(
907 "
908 31ba3498
909 0000000000000000000000000000000000000000000000000000000000000020
910 000000000000000000000000000000000000000000000000000000000000002a
911 7773733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f6d2f
912 76322f6170695f6b657900000000000000000000000000000000000000000000
913 "
914 )
915 .to_vec(),
916 Some(vec!["\"<rpc url>\"".to_string()]),
917 ),
918 (
919 "createFork(string)",
921 hex!(
922 "
923 31ba3498
924 0000000000000000000000000000000000000000000000000000000000000020
925 0000000000000000000000000000000000000000000000000000000000000007
926 6d61696e6e657400000000000000000000000000000000000000000000000000
927 "
928 )
929 .to_vec(),
930 Some(vec!["\"mainnet\"".to_string()]),
931 ),
932 (
933 "createFork(string,uint256)",
935 hex!(
936 "
937 6ba3ba2b
938 0000000000000000000000000000000000000000000000000000000000000040
939 0000000000000000000000000000000000000000000000000000000000000001
940 000000000000000000000000000000000000000000000000000000000000002c
941 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
942 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
943 "
944 )
945 .to_vec(),
946 Some(vec!["\"<rpc url>\"".to_string(), "1".to_string()]),
947 ),
948 (
949 "createFork(string,uint256)",
951 hex!(
952 "
953 6ba3ba2b
954 0000000000000000000000000000000000000000000000000000000000000040
955 0000000000000000000000000000000000000000000000000000000000000001
956 0000000000000000000000000000000000000000000000000000000000000007
957 6d61696e6e657400000000000000000000000000000000000000000000000000
958 "
959 )
960 .to_vec(),
961 Some(vec!["\"mainnet\"".to_string(), "1".to_string()]),
962 ),
963 (
964 "createFork(string,bytes32)",
966 hex!(
967 "
968 7ca29682
969 0000000000000000000000000000000000000000000000000000000000000040
970 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
971 000000000000000000000000000000000000000000000000000000000000002c
972 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
973 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
974 "
975 )
976 .to_vec(),
977 Some(vec![
978 "\"<rpc url>\"".to_string(),
979 "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
980 .to_string(),
981 ]),
982 ),
983 (
984 "createFork(string,bytes32)",
987 hex!(
988 "
989 7ca29682
990 0000000000000000000000000000000000000000000000000000000000000040
991 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
992 0000000000000000000000000000000000000000000000000000000000000007
993 6d61696e6e657400000000000000000000000000000000000000000000000000
994 "
995 )
996 .to_vec(),
997 Some(vec![
998 "\"mainnet\"".to_string(),
999 "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
1000 .to_string(),
1001 ]),
1002 ),
1003 (
1004 "createSelectFork(string)",
1006 hex!(
1007 "
1008 98680034
1009 0000000000000000000000000000000000000000000000000000000000000020
1010 000000000000000000000000000000000000000000000000000000000000002c
1011 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
1012 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
1013 "
1014 )
1015 .to_vec(),
1016 Some(vec!["\"<rpc url>\"".to_string()]),
1017 ),
1018 (
1019 "createSelectFork(string)",
1021 hex!(
1022 "
1023 98680034
1024 0000000000000000000000000000000000000000000000000000000000000020
1025 0000000000000000000000000000000000000000000000000000000000000007
1026 6d61696e6e657400000000000000000000000000000000000000000000000000
1027 "
1028 )
1029 .to_vec(),
1030 Some(vec!["\"mainnet\"".to_string()]),
1031 ),
1032 (
1033 "createSelectFork(string,uint256)",
1035 hex!(
1036 "
1037 71ee464d
1038 0000000000000000000000000000000000000000000000000000000000000040
1039 0000000000000000000000000000000000000000000000000000000000000001
1040 000000000000000000000000000000000000000000000000000000000000002c
1041 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
1042 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
1043 "
1044 )
1045 .to_vec(),
1046 Some(vec!["\"<rpc url>\"".to_string(), "1".to_string()]),
1047 ),
1048 (
1049 "createSelectFork(string,uint256)",
1051 hex!(
1052 "
1053 71ee464d
1054 0000000000000000000000000000000000000000000000000000000000000040
1055 0000000000000000000000000000000000000000000000000000000000000001
1056 0000000000000000000000000000000000000000000000000000000000000007
1057 6d61696e6e657400000000000000000000000000000000000000000000000000
1058 "
1059 )
1060 .to_vec(),
1061 Some(vec!["\"mainnet\"".to_string(), "1".to_string()]),
1062 ),
1063 (
1064 "createSelectFork(string,bytes32)",
1066 hex!(
1067 "
1068 84d52b7a
1069 0000000000000000000000000000000000000000000000000000000000000040
1070 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
1071 000000000000000000000000000000000000000000000000000000000000002c
1072 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
1073 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
1074 "
1075 )
1076 .to_vec(),
1077 Some(vec![
1078 "\"<rpc url>\"".to_string(),
1079 "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
1080 .to_string(),
1081 ]),
1082 ),
1083 (
1084 "createSelectFork(string,bytes32)",
1087 hex!(
1088 "
1089 84d52b7a
1090 0000000000000000000000000000000000000000000000000000000000000040
1091 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
1092 0000000000000000000000000000000000000000000000000000000000000007
1093 6d61696e6e657400000000000000000000000000000000000000000000000000
1094 "
1095 )
1096 .to_vec(),
1097 Some(vec![
1098 "\"mainnet\"".to_string(),
1099 "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
1100 .to_string(),
1101 ]),
1102 ),
1103 (
1104 "rpc(string,string,string)",
1106 hex!(
1107 "
1108 0199a220
1109 0000000000000000000000000000000000000000000000000000000000000060
1110 00000000000000000000000000000000000000000000000000000000000000c0
1111 0000000000000000000000000000000000000000000000000000000000000100
1112 000000000000000000000000000000000000000000000000000000000000002c
1113 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
1114 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
1115 000000000000000000000000000000000000000000000000000000000000000e
1116 6574685f67657442616c616e6365000000000000000000000000000000000000
1117 0000000000000000000000000000000000000000000000000000000000000034
1118 5b22307835353165373738343737386566386530343865343935646634396632
1119 363134663834613466316463222c22307830225d000000000000000000000000
1120 "
1121 )
1122 .to_vec(),
1123 Some(vec![
1124 "\"<rpc url>\"".to_string(),
1125 "\"eth_getBalance\"".to_string(),
1126 "\"[\\\"0x551e7784778ef8e048e495df49f2614f84a4f1dc\\\",\\\"0x0\\\"]\""
1127 .to_string(),
1128 ]),
1129 ),
1130 (
1131 "rpc(string,string,string)",
1134 hex!(
1135 "
1136 0199a220
1137 0000000000000000000000000000000000000000000000000000000000000060
1138 00000000000000000000000000000000000000000000000000000000000000a0
1139 00000000000000000000000000000000000000000000000000000000000000e0
1140 0000000000000000000000000000000000000000000000000000000000000007
1141 6d61696e6e657400000000000000000000000000000000000000000000000000
1142 000000000000000000000000000000000000000000000000000000000000000e
1143 6574685f67657442616c616e6365000000000000000000000000000000000000
1144 0000000000000000000000000000000000000000000000000000000000000034
1145 5b22307835353165373738343737386566386530343865343935646634396632
1146 363134663834613466316463222c22307830225d000000000000000000000000
1147 "
1148 )
1149 .to_vec(),
1150 Some(vec![
1151 "\"mainnet\"".to_string(),
1152 "\"eth_getBalance\"".to_string(),
1153 "\"[\\\"0x551e7784778ef8e048e495df49f2614f84a4f1dc\\\",\\\"0x0\\\"]\""
1154 .to_string(),
1155 ]),
1156 ),
1157 ];
1158
1159 let cheatcode_output_test_cases = vec![
1161 ("createWallet(string)", Some("<pk>".to_string())),
1163 ("deriveKey(string,uint32)", Some("<pk>".to_string())),
1164 ("rpcUrl(string)", Some("<rpc url>".to_string())),
1166 ("rpcUrls()", Some("<rpc url>".to_string())),
1167 ("rpcUrlStructs()", Some("<rpc url>".to_string())),
1168 ];
1169
1170 for (function_signature, data, expected) in cheatcode_input_test_cases {
1171 let function = Function::parse(function_signature).unwrap();
1172 let result = decoder.decode_cheatcode_inputs(&function, &data);
1173 assert_eq!(result, expected, "Input case failed for: {function_signature}");
1174 }
1175
1176 for (function_signature, expected) in cheatcode_output_test_cases {
1177 let function = Function::parse(function_signature).unwrap();
1178 let result = Some(decoder.decode_cheatcode_outputs(&function).unwrap_or_default());
1179 assert_eq!(result, expected, "Output case failed for: {function_signature}");
1180 }
1181 }
1182}