1use crate::{
2 debug::DebugTraceIdentifier,
3 identifier::{
4 AddressIdentity, LocalTraceIdentifier, SingleSignaturesIdentifier, TraceIdentifier,
5 },
6 CallTrace, CallTraceArena, CallTraceNode, DecodedCallData,
7};
8use alloy_dyn_abi::{DecodedEvent, DynSolValue, EventExt, FunctionExt, JsonAbiExt};
9use alloy_json_abi::{Error, Event, Function, JsonAbi};
10use alloy_primitives::{
11 map::{hash_map::Entry, HashMap},
12 Address, LogData, Selector, B256,
13};
14use foundry_common::{
15 abi::get_indexed_event, fmt::format_token, get_contract_name, ContractsByArtifact, SELECTOR_LEN,
16};
17use foundry_evm_core::{
18 abi::{console, Vm},
19 constants::{
20 CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, HARDHAT_CONSOLE_ADDRESS,
21 TEST_CONTRACT_ADDRESS,
22 },
23 decode::RevertDecoder,
24 precompiles::{
25 BLAKE_2F, EC_ADD, EC_MUL, EC_PAIRING, EC_RECOVER, IDENTITY, MOD_EXP, POINT_EVALUATION,
26 RIPEMD_160, SHA_256,
27 },
28};
29use itertools::Itertools;
30use revm_inspectors::tracing::types::{DecodedCallLog, DecodedCallTrace};
31use std::{collections::BTreeMap, sync::OnceLock};
32
33mod precompiles;
34
35#[derive(Default)]
37#[must_use = "builders do nothing unless you call `build` on them"]
38pub struct CallTraceDecoderBuilder {
39 decoder: CallTraceDecoder,
40}
41
42impl CallTraceDecoderBuilder {
43 #[inline]
45 pub fn new() -> Self {
46 Self { decoder: CallTraceDecoder::new().clone() }
47 }
48
49 #[inline]
51 pub fn with_labels(mut self, labels: impl IntoIterator<Item = (Address, String)>) -> Self {
52 self.decoder.labels.extend(labels);
53 self
54 }
55
56 #[inline]
58 pub fn with_abi(mut self, abi: &JsonAbi) -> Self {
59 self.decoder.collect_abi(abi, None);
60 self
61 }
62
63 #[inline]
65 pub fn with_known_contracts(mut self, contracts: &ContractsByArtifact) -> Self {
66 trace!(target: "evm::traces", len=contracts.len(), "collecting known contract ABIs");
67 for contract in contracts.values() {
68 self.decoder.collect_abi(&contract.abi, None);
69 }
70 self
71 }
72
73 #[inline]
75 pub fn with_local_identifier_abis(self, identifier: &LocalTraceIdentifier<'_>) -> Self {
76 self.with_known_contracts(identifier.contracts())
77 }
78
79 #[inline]
81 pub fn with_verbosity(mut self, level: u8) -> Self {
82 self.decoder.verbosity = level;
83 self
84 }
85
86 #[inline]
88 pub fn with_signature_identifier(mut self, identifier: SingleSignaturesIdentifier) -> Self {
89 self.decoder.signature_identifier = Some(identifier);
90 self
91 }
92
93 #[inline]
95 pub fn with_debug_identifier(mut self, identifier: DebugTraceIdentifier) -> Self {
96 self.decoder.debug_identifier = Some(identifier);
97 self
98 }
99
100 #[inline]
102 pub fn build(self) -> CallTraceDecoder {
103 self.decoder
104 }
105}
106
107#[derive(Clone, Debug, Default)]
115pub struct CallTraceDecoder {
116 pub contracts: HashMap<Address, String>,
120 pub labels: HashMap<Address, String>,
122 pub receive_contracts: Vec<Address>,
124 pub fallback_contracts: HashMap<Address, Vec<String>>,
126
127 pub functions: HashMap<Selector, Vec<Function>>,
129 pub events: BTreeMap<(B256, usize), Vec<Event>>,
131 pub revert_decoder: RevertDecoder,
133
134 pub signature_identifier: Option<SingleSignaturesIdentifier>,
136 pub verbosity: u8,
138
139 pub debug_identifier: Option<DebugTraceIdentifier>,
141}
142
143impl CallTraceDecoder {
144 pub fn new() -> &'static Self {
149 static INIT: OnceLock<CallTraceDecoder> = OnceLock::new();
152 INIT.get_or_init(Self::init)
153 }
154
155 fn init() -> Self {
156 Self {
157 contracts: Default::default(),
158 labels: HashMap::from_iter([
159 (CHEATCODE_ADDRESS, "VM".to_string()),
160 (HARDHAT_CONSOLE_ADDRESS, "console".to_string()),
161 (DEFAULT_CREATE2_DEPLOYER, "Create2Deployer".to_string()),
162 (CALLER, "DefaultSender".to_string()),
163 (TEST_CONTRACT_ADDRESS, "DefaultTestContract".to_string()),
164 (EC_RECOVER, "ECRecover".to_string()),
165 (SHA_256, "SHA-256".to_string()),
166 (RIPEMD_160, "RIPEMD-160".to_string()),
167 (IDENTITY, "Identity".to_string()),
168 (MOD_EXP, "ModExp".to_string()),
169 (EC_ADD, "ECAdd".to_string()),
170 (EC_MUL, "ECMul".to_string()),
171 (EC_PAIRING, "ECPairing".to_string()),
172 (BLAKE_2F, "Blake2F".to_string()),
173 (POINT_EVALUATION, "PointEvaluation".to_string()),
174 ]),
175 receive_contracts: Default::default(),
176 fallback_contracts: Default::default(),
177
178 functions: console::hh::abi::functions()
179 .into_values()
180 .chain(Vm::abi::functions().into_values())
181 .flatten()
182 .map(|func| (func.selector(), vec![func]))
183 .collect(),
184 events: console::ds::abi::events()
185 .into_values()
186 .flatten()
187 .map(|event| ((event.selector(), indexed_inputs(&event)), vec![event]))
188 .collect(),
189 revert_decoder: Default::default(),
190
191 signature_identifier: None,
192 verbosity: 0,
193
194 debug_identifier: None,
195 }
196 }
197
198 pub fn clear_addresses(&mut self) {
200 self.contracts.clear();
201
202 let default_labels = &Self::new().labels;
203 if self.labels.len() > default_labels.len() {
204 self.labels.clone_from(default_labels);
205 }
206
207 self.receive_contracts.clear();
208 self.fallback_contracts.clear();
209 }
210
211 pub fn identify(&mut self, trace: &CallTraceArena, identifier: &mut impl TraceIdentifier) {
215 self.collect_identities(identifier.identify_addresses(self.trace_addresses(trace)));
216 }
217
218 pub fn push_event(&mut self, event: Event) {
220 self.events.entry((event.selector(), indexed_inputs(&event))).or_default().push(event);
221 }
222
223 pub fn push_function(&mut self, function: Function) {
225 match self.functions.entry(function.selector()) {
226 Entry::Occupied(entry) => {
227 if entry.get().contains(&function) {
229 return;
230 }
231 trace!(target: "evm::traces", selector=%entry.key(), new=%function.signature(), "duplicate function selector");
232 entry.into_mut().push(function);
233 }
234 Entry::Vacant(entry) => {
235 entry.insert(vec![function]);
236 }
237 }
238 }
239
240 pub fn push_error(&mut self, error: Error) {
242 self.revert_decoder.push_error(error);
243 }
244
245 pub fn trace_addresses<'a>(
247 &'a self,
248 arena: &'a CallTraceArena,
249 ) -> impl Iterator<Item = (&'a Address, Option<&'a [u8]>, Option<&'a [u8]>)> + Clone + 'a {
250 arena
251 .nodes()
252 .iter()
253 .map(|node| {
254 (
255 &node.trace.address,
256 node.trace.kind.is_any_create().then_some(&node.trace.output[..]),
257 node.trace.kind.is_any_create().then_some(&node.trace.data[..]),
258 )
259 })
260 .filter(|&(address, _, _)| {
261 !self.labels.contains_key(address) || !self.contracts.contains_key(address)
262 })
263 }
264
265 fn collect_identities(&mut self, identities: Vec<AddressIdentity<'_>>) {
266 if identities.is_empty() {
268 return;
269 }
270
271 trace!(target: "evm::traces", len=identities.len(), "collecting address identities");
272 for AddressIdentity { address, label, contract, abi, artifact_id: _ } in identities {
273 let _span = trace_span!(target: "evm::traces", "identity", ?contract, ?label).entered();
274
275 if let Some(contract) = contract {
276 self.contracts.entry(address).or_insert(contract);
277 }
278
279 if let Some(label) = label {
280 self.labels.entry(address).or_insert(label);
281 }
282
283 if let Some(abi) = abi {
284 self.collect_abi(&abi, Some(&address));
285 }
286 }
287 }
288
289 fn collect_abi(&mut self, abi: &JsonAbi, address: Option<&Address>) {
290 trace!(target: "evm::traces", len=abi.len(), ?address, "collecting ABI");
291 for function in abi.functions() {
292 self.push_function(function.clone());
293 }
294 for event in abi.events() {
295 self.push_event(event.clone());
296 }
297 for error in abi.errors() {
298 self.push_error(error.clone());
299 }
300 if let Some(address) = address {
301 if abi.receive.is_some() {
302 self.receive_contracts.push(*address);
303 }
304
305 if abi.fallback.is_some() {
306 let mut functions_sig = vec![];
307 for function in abi.functions() {
308 functions_sig.push(function.signature());
309 }
310 self.fallback_contracts.insert(*address, functions_sig);
311 }
312 }
313 }
314
315 pub async fn populate_traces(&self, traces: &mut Vec<CallTraceNode>) {
319 for node in traces {
320 node.trace.decoded = self.decode_function(&node.trace).await;
321 for log in &mut node.logs {
322 log.decoded = self.decode_event(&log.raw_log).await;
323 }
324
325 if let Some(debug) = self.debug_identifier.as_ref() {
326 if let Some(identified) = self.contracts.get(&node.trace.address) {
327 debug.identify_node_steps(node, get_contract_name(identified))
328 }
329 }
330 }
331 }
332
333 pub async fn decode_function(&self, trace: &CallTrace) -> DecodedCallTrace {
335 if let Some(trace) = precompiles::decode(trace, 1) {
336 return trace;
337 }
338
339 let label = self.labels.get(&trace.address).cloned();
340
341 let cdata = &trace.data;
342 if trace.address == DEFAULT_CREATE2_DEPLOYER {
343 return DecodedCallTrace {
344 label,
345 call_data: Some(DecodedCallData { signature: "create2".to_string(), args: vec![] }),
346 return_data: self.default_return_data(trace),
347 };
348 }
349
350 if cdata.len() >= SELECTOR_LEN {
351 let selector = &cdata[..SELECTOR_LEN];
352 let mut functions = Vec::new();
353 let functions = match self.functions.get(selector) {
354 Some(fs) => fs,
355 None => {
356 if let Some(identifier) = &self.signature_identifier {
357 if let Some(function) =
358 identifier.write().await.identify_function(selector).await
359 {
360 functions.push(function);
361 }
362 }
363 &functions
364 }
365 };
366 let [func, ..] = &functions[..] else {
367 return DecodedCallTrace {
368 label,
369 call_data: self.fallback_contracts.get(&trace.address).map(|_| {
370 DecodedCallData {
371 signature: "fallback()".to_string(),
372 args: vec![cdata.to_string()],
373 }
374 }),
375 return_data: self.default_return_data(trace),
376 };
377 };
378
379 let mut call_data = self.decode_function_input(trace, func);
382 if let Some(fallback_functions) = self.fallback_contracts.get(&trace.address) {
383 if !fallback_functions.contains(&func.signature()) {
384 call_data.signature = "fallback()".to_string();
385 }
386 }
387
388 DecodedCallTrace {
389 label,
390 call_data: Some(call_data),
391 return_data: self.decode_function_output(trace, functions),
392 }
393 } else {
394 let has_receive = self.receive_contracts.contains(&trace.address);
395 let signature =
396 if cdata.is_empty() && has_receive { "receive()" } else { "fallback()" }
397 .to_string();
398 let args = if cdata.is_empty() { Vec::new() } else { vec![cdata.to_string()] };
399 DecodedCallTrace {
400 label,
401 call_data: Some(DecodedCallData { signature, args }),
402 return_data: self.default_return_data(trace),
403 }
404 }
405 }
406
407 fn decode_function_input(&self, trace: &CallTrace, func: &Function) -> DecodedCallData {
409 let mut args = None;
410 if trace.data.len() >= SELECTOR_LEN {
411 if trace.address == CHEATCODE_ADDRESS {
412 if let Some(v) = self.decode_cheatcode_inputs(func, &trace.data) {
414 args = Some(v);
415 }
416 }
417
418 if args.is_none() {
419 if let Ok(v) = func.abi_decode_input(&trace.data[SELECTOR_LEN..], false) {
420 args = Some(v.iter().map(|value| self.format_value(value)).collect());
421 }
422 }
423 }
424
425 DecodedCallData { signature: func.signature(), args: args.unwrap_or_default() }
426 }
427
428 fn decode_cheatcode_inputs(&self, func: &Function, data: &[u8]) -> Option<Vec<String>> {
430 match func.name.as_str() {
431 "expectRevert" => Some(vec![self.revert_decoder.decode(data, None)]),
432 "addr" | "createWallet" | "deriveKey" | "rememberKey" => {
433 Some(vec!["<pk>".to_string()])
435 }
436 "broadcast" | "startBroadcast" => {
437 if !func.inputs.is_empty() && func.inputs[0].ty == "uint256" {
440 Some(vec!["<pk>".to_string()])
441 } else {
442 None
443 }
444 }
445 "getNonce" => {
446 if !func.inputs.is_empty() && func.inputs[0].ty == "tuple" {
449 Some(vec!["<pk>".to_string()])
450 } else {
451 None
452 }
453 }
454 "sign" | "signP256" => {
455 let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..], false).ok()?;
456
457 if !decoded.is_empty() &&
460 (func.inputs[0].ty == "uint256" || func.inputs[0].ty == "tuple")
461 {
462 decoded[0] = DynSolValue::String("<pk>".to_string());
463 }
464
465 Some(decoded.iter().map(format_token).collect())
466 }
467 "signDelegation" | "signAndAttachDelegation" => {
468 let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..], false).ok()?;
469 decoded[1] = DynSolValue::String("<pk>".to_string());
473 Some(decoded.iter().map(format_token).collect())
474 }
475 "parseJson" |
476 "parseJsonUint" |
477 "parseJsonUintArray" |
478 "parseJsonInt" |
479 "parseJsonIntArray" |
480 "parseJsonString" |
481 "parseJsonStringArray" |
482 "parseJsonAddress" |
483 "parseJsonAddressArray" |
484 "parseJsonBool" |
485 "parseJsonBoolArray" |
486 "parseJsonBytes" |
487 "parseJsonBytesArray" |
488 "parseJsonBytes32" |
489 "parseJsonBytes32Array" |
490 "writeJson" |
491 "keyExists" |
493 "keyExistsJson" |
494 "serializeBool" |
495 "serializeUint" |
496 "serializeUintToHex" |
497 "serializeInt" |
498 "serializeAddress" |
499 "serializeBytes32" |
500 "serializeString" |
501 "serializeBytes" => {
502 if self.verbosity >= 5 {
503 None
504 } else {
505 let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..], false).ok()?;
506 let token = if func.name.as_str() == "parseJson" ||
507 func.name.as_str() == "keyExists" ||
509 func.name.as_str() == "keyExistsJson"
510 {
511 "<JSON file>"
512 } else {
513 "<stringified JSON>"
514 };
515 decoded[0] = DynSolValue::String(token.to_string());
516 Some(decoded.iter().map(format_token).collect())
517 }
518 }
519 s if s.contains("Toml") => {
520 if self.verbosity >= 5 {
521 None
522 } else {
523 let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..], false).ok()?;
524 let token = if func.name.as_str() == "parseToml" ||
525 func.name.as_str() == "keyExistsToml"
526 {
527 "<TOML file>"
528 } else {
529 "<stringified TOML>"
530 };
531 decoded[0] = DynSolValue::String(token.to_string());
532 Some(decoded.iter().map(format_token).collect())
533 }
534 }
535 "createFork" |
536 "createSelectFork" |
537 "rpc" => {
538 let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..], false).ok()?;
539
540 if !decoded.is_empty() && func.inputs[0].ty == "string" {
542 let url_or_alias = decoded[0].as_str().unwrap_or_default();
543
544 if url_or_alias.starts_with("http") || url_or_alias.starts_with("ws") {
545 decoded[0] = DynSolValue::String("<rpc url>".to_string());
546 }
547 } else {
548 return None;
549 }
550
551 Some(decoded.iter().map(format_token).collect())
552 }
553 _ => None,
554 }
555 }
556
557 fn decode_function_output(&self, trace: &CallTrace, funcs: &[Function]) -> Option<String> {
559 if !trace.success {
560 return self.default_return_data(trace);
561 }
562
563 if trace.address == CHEATCODE_ADDRESS {
564 if let Some(decoded) = funcs.iter().find_map(|func| self.decode_cheatcode_outputs(func))
565 {
566 return Some(decoded);
567 }
568 }
569
570 if let Some(values) =
571 funcs.iter().find_map(|func| func.abi_decode_output(&trace.output, false).ok())
572 {
573 if values.is_empty() {
576 return None;
577 }
578
579 return Some(
580 values.iter().map(|value| self.format_value(value)).format(", ").to_string(),
581 );
582 }
583
584 None
585 }
586
587 fn decode_cheatcode_outputs(&self, func: &Function) -> Option<String> {
589 match func.name.as_str() {
590 s if s.starts_with("env") => Some("<env var value>"),
591 "createWallet" | "deriveKey" => Some("<pk>"),
592 "promptSecret" | "promptSecretUint" => Some("<secret>"),
593 "parseJson" if self.verbosity < 5 => Some("<encoded JSON value>"),
594 "readFile" if self.verbosity < 5 => Some("<file>"),
595 "rpcUrl" | "rpcUrls" | "rpcUrlStructs" => Some("<rpc url>"),
596 _ => None,
597 }
598 .map(Into::into)
599 }
600
601 fn default_return_data(&self, trace: &CallTrace) -> Option<String> {
603 (!trace.success).then(|| self.revert_decoder.decode(&trace.output, Some(trace.status)))
604 }
605
606 pub async fn decode_event(&self, log: &LogData) -> DecodedCallLog {
608 let &[t0, ..] = log.topics() else { return DecodedCallLog { name: None, params: None } };
609
610 let mut events = Vec::new();
611 let events = match self.events.get(&(t0, log.topics().len() - 1)) {
612 Some(es) => es,
613 None => {
614 if let Some(identifier) = &self.signature_identifier {
615 if let Some(event) = identifier.write().await.identify_event(&t0[..]).await {
616 events.push(get_indexed_event(event, log));
617 }
618 }
619 &events
620 }
621 };
622 for event in events {
623 if let Ok(decoded) = event.decode_log(log, false) {
624 let params = reconstruct_params(event, &decoded);
625 return DecodedCallLog {
626 name: Some(event.name.clone()),
627 params: Some(
628 params
629 .into_iter()
630 .zip(event.inputs.iter())
631 .map(|(param, input)| {
632 let name = input.name.clone();
634 (name, self.format_value(¶m))
635 })
636 .collect(),
637 ),
638 };
639 }
640 }
641
642 DecodedCallLog { name: None, params: None }
643 }
644
645 pub async fn prefetch_signatures(&self, nodes: &[CallTraceNode]) {
647 let Some(identifier) = &self.signature_identifier else { return };
648
649 let events_it = nodes
650 .iter()
651 .flat_map(|node| node.logs.iter().filter_map(|log| log.raw_log.topics().first()))
652 .unique();
653 identifier.write().await.identify_events(events_it).await;
654
655 const DEFAULT_CREATE2_DEPLOYER_BYTES: [u8; 20] = DEFAULT_CREATE2_DEPLOYER.0 .0;
656 let funcs_it = nodes
657 .iter()
658 .filter_map(|n| match n.trace.address.0 .0 {
659 DEFAULT_CREATE2_DEPLOYER_BYTES => None,
660 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01..=0x0a] => None,
661 _ => n.trace.data.get(..SELECTOR_LEN),
662 })
663 .filter(|v| !self.functions.contains_key(*v))
664 .unique();
665 identifier.write().await.identify_functions(funcs_it).await;
666 }
667
668 fn format_value(&self, value: &DynSolValue) -> String {
670 if let DynSolValue::Address(addr) = value {
671 if let Some(label) = self.labels.get(addr) {
672 return format!("{label}: [{addr}]");
673 }
674 }
675 format_token(value)
676 }
677}
678
679fn reconstruct_params(event: &Event, decoded: &DecodedEvent) -> Vec<DynSolValue> {
682 let mut indexed = 0;
683 let mut unindexed = 0;
684 let mut inputs = vec![];
685 for input in &event.inputs {
686 if input.indexed && indexed < decoded.indexed.len() {
690 inputs.push(decoded.indexed[indexed].clone());
691 indexed += 1;
692 } else if unindexed < decoded.body.len() {
693 inputs.push(decoded.body[unindexed].clone());
694 unindexed += 1;
695 }
696 }
697
698 inputs
699}
700
701fn indexed_inputs(event: &Event) -> usize {
702 event.inputs.iter().filter(|param| param.indexed).count()
703}
704
705#[cfg(test)]
706mod tests {
707 use super::*;
708 use alloy_primitives::hex;
709
710 #[test]
711 fn test_should_redact() {
712 let decoder = CallTraceDecoder::new();
713
714 let cheatcode_input_test_cases = vec![
716 ("addr(uint256)", vec![], Some(vec!["<pk>".to_string()])),
718 ("createWallet(string)", vec![], Some(vec!["<pk>".to_string()])),
719 ("createWallet(uint256)", vec![], Some(vec!["<pk>".to_string()])),
720 ("deriveKey(string,uint32)", vec![], Some(vec!["<pk>".to_string()])),
721 ("deriveKey(string,string,uint32)", vec![], Some(vec!["<pk>".to_string()])),
722 ("deriveKey(string,uint32,string)", vec![], Some(vec!["<pk>".to_string()])),
723 ("deriveKey(string,string,uint32,string)", vec![], Some(vec!["<pk>".to_string()])),
724 ("rememberKey(uint256)", vec![], Some(vec!["<pk>".to_string()])),
725 ("broadcast(uint256)", vec![], Some(vec!["<pk>".to_string()])),
728 ("broadcast()", vec![], None), ("startBroadcast(uint256)", vec![], Some(vec!["<pk>".to_string()])),
730 ("startBroadcast()", vec![], None), ("getNonce((address,uint256,uint256,uint256))", vec![], Some(vec!["<pk>".to_string()])),
732 ("getNonce(address)", vec![], None), (
736 "sign(uint256,bytes32)",
737 hex!(
738 "
739 e341eaa4
740 7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6
741 0000000000000000000000000000000000000000000000000000000000000000
742 "
743 )
744 .to_vec(),
745 Some(vec![
746 "\"<pk>\"".to_string(),
747 "0x0000000000000000000000000000000000000000000000000000000000000000"
748 .to_string(),
749 ]),
750 ),
751 (
752 "signP256(uint256,bytes32)",
753 hex!(
754 "
755 83211b40
756 7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6
757 0000000000000000000000000000000000000000000000000000000000000000
758 "
759 )
760 .to_vec(),
761 Some(vec![
762 "\"<pk>\"".to_string(),
763 "0x0000000000000000000000000000000000000000000000000000000000000000"
764 .to_string(),
765 ]),
766 ),
767 (
768 "createFork(string)",
770 hex!(
771 "
772 31ba3498
773 0000000000000000000000000000000000000000000000000000000000000020
774 000000000000000000000000000000000000000000000000000000000000002c
775 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
776 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
777 "
778 )
779 .to_vec(),
780 Some(vec!["\"<rpc url>\"".to_string()]),
781 ),
782 (
783 "createFork(string)",
785 hex!(
786 "
787 31ba3498
788 0000000000000000000000000000000000000000000000000000000000000020
789 000000000000000000000000000000000000000000000000000000000000002a
790 7773733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f6d2f
791 76322f6170695f6b657900000000000000000000000000000000000000000000
792 "
793 )
794 .to_vec(),
795 Some(vec!["\"<rpc url>\"".to_string()]),
796 ),
797 (
798 "createFork(string)",
800 hex!(
801 "
802 31ba3498
803 0000000000000000000000000000000000000000000000000000000000000020
804 0000000000000000000000000000000000000000000000000000000000000007
805 6d61696e6e657400000000000000000000000000000000000000000000000000
806 "
807 )
808 .to_vec(),
809 Some(vec!["\"mainnet\"".to_string()]),
810 ),
811 (
812 "createFork(string,uint256)",
814 hex!(
815 "
816 6ba3ba2b
817 0000000000000000000000000000000000000000000000000000000000000040
818 0000000000000000000000000000000000000000000000000000000000000001
819 000000000000000000000000000000000000000000000000000000000000002c
820 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
821 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
822 "
823 )
824 .to_vec(),
825 Some(vec!["\"<rpc url>\"".to_string(), "1".to_string()]),
826 ),
827 (
828 "createFork(string,uint256)",
830 hex!(
831 "
832 6ba3ba2b
833 0000000000000000000000000000000000000000000000000000000000000040
834 0000000000000000000000000000000000000000000000000000000000000001
835 0000000000000000000000000000000000000000000000000000000000000007
836 6d61696e6e657400000000000000000000000000000000000000000000000000
837 "
838 )
839 .to_vec(),
840 Some(vec!["\"mainnet\"".to_string(), "1".to_string()]),
841 ),
842 (
843 "createFork(string,bytes32)",
845 hex!(
846 "
847 7ca29682
848 0000000000000000000000000000000000000000000000000000000000000040
849 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
850 000000000000000000000000000000000000000000000000000000000000002c
851 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
852 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
853 "
854 )
855 .to_vec(),
856 Some(vec![
857 "\"<rpc url>\"".to_string(),
858 "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
859 .to_string(),
860 ]),
861 ),
862 (
863 "createFork(string,bytes32)",
866 hex!(
867 "
868 7ca29682
869 0000000000000000000000000000000000000000000000000000000000000040
870 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
871 0000000000000000000000000000000000000000000000000000000000000007
872 6d61696e6e657400000000000000000000000000000000000000000000000000
873 "
874 )
875 .to_vec(),
876 Some(vec![
877 "\"mainnet\"".to_string(),
878 "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
879 .to_string(),
880 ]),
881 ),
882 (
883 "createSelectFork(string)",
885 hex!(
886 "
887 98680034
888 0000000000000000000000000000000000000000000000000000000000000020
889 000000000000000000000000000000000000000000000000000000000000002c
890 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
891 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
892 "
893 )
894 .to_vec(),
895 Some(vec!["\"<rpc url>\"".to_string()]),
896 ),
897 (
898 "createSelectFork(string)",
900 hex!(
901 "
902 98680034
903 0000000000000000000000000000000000000000000000000000000000000020
904 0000000000000000000000000000000000000000000000000000000000000007
905 6d61696e6e657400000000000000000000000000000000000000000000000000
906 "
907 )
908 .to_vec(),
909 Some(vec!["\"mainnet\"".to_string()]),
910 ),
911 (
912 "createSelectFork(string,uint256)",
914 hex!(
915 "
916 71ee464d
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 "createSelectFork(string,uint256)",
930 hex!(
931 "
932 71ee464d
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 "createSelectFork(string,bytes32)",
945 hex!(
946 "
947 84d52b7a
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 "createSelectFork(string,bytes32)",
966 hex!(
967 "
968 84d52b7a
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 "rpc(string,string,string)",
985 hex!(
986 "
987 0199a220
988 0000000000000000000000000000000000000000000000000000000000000060
989 00000000000000000000000000000000000000000000000000000000000000c0
990 0000000000000000000000000000000000000000000000000000000000000100
991 000000000000000000000000000000000000000000000000000000000000002c
992 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
993 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
994 000000000000000000000000000000000000000000000000000000000000000e
995 6574685f67657442616c616e6365000000000000000000000000000000000000
996 0000000000000000000000000000000000000000000000000000000000000034
997 5b22307835353165373738343737386566386530343865343935646634396632
998 363134663834613466316463222c22307830225d000000000000000000000000
999 "
1000 )
1001 .to_vec(),
1002 Some(vec![
1003 "\"<rpc url>\"".to_string(),
1004 "\"eth_getBalance\"".to_string(),
1005 "\"[\\\"0x551e7784778ef8e048e495df49f2614f84a4f1dc\\\",\\\"0x0\\\"]\""
1006 .to_string(),
1007 ]),
1008 ),
1009 (
1010 "rpc(string,string,string)",
1013 hex!(
1014 "
1015 0199a220
1016 0000000000000000000000000000000000000000000000000000000000000060
1017 00000000000000000000000000000000000000000000000000000000000000a0
1018 00000000000000000000000000000000000000000000000000000000000000e0
1019 0000000000000000000000000000000000000000000000000000000000000007
1020 6d61696e6e657400000000000000000000000000000000000000000000000000
1021 000000000000000000000000000000000000000000000000000000000000000e
1022 6574685f67657442616c616e6365000000000000000000000000000000000000
1023 0000000000000000000000000000000000000000000000000000000000000034
1024 5b22307835353165373738343737386566386530343865343935646634396632
1025 363134663834613466316463222c22307830225d000000000000000000000000
1026 "
1027 )
1028 .to_vec(),
1029 Some(vec![
1030 "\"mainnet\"".to_string(),
1031 "\"eth_getBalance\"".to_string(),
1032 "\"[\\\"0x551e7784778ef8e048e495df49f2614f84a4f1dc\\\",\\\"0x0\\\"]\""
1033 .to_string(),
1034 ]),
1035 ),
1036 ];
1037
1038 let cheatcode_output_test_cases = vec![
1040 ("createWallet(string)", Some("<pk>".to_string())),
1042 ("deriveKey(string,uint32)", Some("<pk>".to_string())),
1043 ("rpcUrl(string)", Some("<rpc url>".to_string())),
1045 ("rpcUrls()", Some("<rpc url>".to_string())),
1046 ("rpcUrlStructs()", Some("<rpc url>".to_string())),
1047 ];
1048
1049 for (function_signature, data, expected) in cheatcode_input_test_cases {
1050 let function = Function::parse(function_signature).unwrap();
1051 let result = decoder.decode_cheatcode_inputs(&function, &data);
1052 assert_eq!(result, expected, "Input case failed for: {function_signature}");
1053 }
1054
1055 for (function_signature, expected) in cheatcode_output_test_cases {
1056 let function = Function::parse(function_signature).unwrap();
1057 let result = Some(decoder.decode_cheatcode_outputs(&function).unwrap_or_default());
1058 assert_eq!(result, expected, "Output case failed for: {function_signature}");
1059 }
1060 }
1061}