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