1use crate::contract::source::{AbiEntry, ContractSource};
19use serde::Deserialize;
20
21#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
23pub struct DecodedCall {
24 pub selector: String,
26 pub signature: String,
28 pub function_name: String,
30 pub parameters: Vec<DecodedParam>,
32 pub source: DecodeSource,
34}
35
36#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
38pub enum DecodeSource {
39 VerifiedAbi,
41 FourByteDirectory,
43 Unknown,
45}
46
47#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
49pub struct DecodedParam {
50 pub name: String,
52 pub param_type: String,
54 pub value: String,
56}
57
58#[derive(Deserialize)]
60struct FourByteResponse {
61 results: Vec<FourByteResult>,
62}
63
64#[derive(Deserialize)]
65struct FourByteResult {
66 text_signature: String,
67}
68
69#[derive(Deserialize)]
71struct OpenChainResponse {
72 ok: bool,
73 result: Option<OpenChainResult>,
74}
75
76#[derive(Deserialize)]
77struct OpenChainResult {
78 function: Option<std::collections::HashMap<String, Vec<OpenChainEntry>>>,
79}
80
81#[derive(Deserialize)]
82struct OpenChainEntry {
83 name: String,
84}
85
86pub async fn decode_calldata(
94 input: &str,
95 contract_source: Option<&ContractSource>,
96 http_client: &reqwest::Client,
97) -> DecodedCall {
98 if input.is_empty() || input == "0x" {
99 return DecodedCall {
100 selector: "0x".to_string(),
101 signature: "()".to_string(),
102 function_name: "Native Transfer".to_string(),
103 parameters: vec![],
104 source: DecodeSource::Unknown,
105 };
106 }
107
108 let selector = if input.len() >= 10 {
109 &input[..10]
110 } else {
111 input
112 };
113
114 if let Some(source) = contract_source
116 && let Some(entry) = find_abi_by_selector(selector, &source.parsed_abi)
117 {
118 let params = decode_params_from_abi(input, entry);
119 return DecodedCall {
120 selector: selector.to_string(),
121 signature: entry.signature(),
122 function_name: entry.name.clone(),
123 parameters: params,
124 source: DecodeSource::VerifiedAbi,
125 };
126 }
127
128 if let Some(sig) = lookup_openchain(selector, http_client).await {
130 let fn_name = sig.split('(').next().unwrap_or(&sig).to_string();
131 return DecodedCall {
132 selector: selector.to_string(),
133 signature: sig,
134 function_name: fn_name,
135 parameters: vec![],
136 source: DecodeSource::FourByteDirectory,
137 };
138 }
139
140 if let Some(sig) = lookup_4byte(selector, http_client).await {
142 let fn_name = sig.split('(').next().unwrap_or(&sig).to_string();
143 return DecodedCall {
144 selector: selector.to_string(),
145 signature: sig,
146 function_name: fn_name,
147 parameters: vec![],
148 source: DecodeSource::FourByteDirectory,
149 };
150 }
151
152 DecodedCall {
154 selector: selector.to_string(),
155 signature: format!("{}(...)", selector),
156 function_name: selector.to_string(),
157 parameters: vec![],
158 source: DecodeSource::Unknown,
159 }
160}
161
162async fn lookup_openchain(selector: &str, http_client: &reqwest::Client) -> Option<String> {
164 let url = format!(
165 "https://api.openchain.xyz/signature-database/v1/lookup?function={}&filter=true",
166 selector
167 );
168
169 let response = http_client.get(&url).send().await.ok()?;
170 let body: OpenChainResponse = response.json().await.ok()?;
171
172 if !body.ok {
173 return None;
174 }
175
176 body.result
177 .and_then(|r| r.function)
178 .and_then(|mut f| f.remove(selector))
179 .and_then(|entries| entries.into_iter().next())
180 .map(|e| e.name)
181}
182
183async fn lookup_4byte(selector: &str, http_client: &reqwest::Client) -> Option<String> {
185 let hex = selector.trim_start_matches("0x");
186 let url = format!(
187 "https://www.4byte.directory/api/v1/signatures/?hex_signature=0x{}",
188 hex
189 );
190
191 let response = http_client.get(&url).send().await.ok()?;
192 let body: FourByteResponse = response.json().await.ok()?;
193 body.results.into_iter().next().map(|r| r.text_signature)
194}
195
196fn find_abi_by_selector<'a>(selector: &str, abi: &'a [AbiEntry]) -> Option<&'a AbiEntry> {
198 let selector_lower = selector.to_lowercase();
199 abi.iter().find(|entry| {
200 entry.entry_type == "function" && entry.selector().to_lowercase() == selector_lower
201 })
202}
203
204fn decode_params_from_abi(input: &str, entry: &AbiEntry) -> Vec<DecodedParam> {
209 let data = if input.len() > 10 { &input[10..] } else { "" };
210
211 entry
212 .inputs
213 .iter()
214 .enumerate()
215 .map(|(i, param)| {
216 let offset = i * 64;
217 let raw_value = if offset + 64 <= data.len() {
218 &data[offset..offset + 64]
219 } else if offset < data.len() {
220 &data[offset..]
221 } else {
222 ""
223 };
224
225 let value = decode_abi_value(raw_value, ¶m.param_type);
226
227 DecodedParam {
228 name: if param.name.is_empty() {
229 format!("arg{}", i)
230 } else {
231 param.name.clone()
232 },
233 param_type: param.param_type.clone(),
234 value,
235 }
236 })
237 .collect()
238}
239
240fn decode_abi_value(hex_value: &str, solidity_type: &str) -> String {
242 if hex_value.is_empty() {
243 return "(empty)".to_string();
244 }
245
246 match solidity_type {
247 "address" => {
248 let addr = if hex_value.len() >= 40 {
250 &hex_value[hex_value.len() - 40..]
251 } else {
252 hex_value
253 };
254 format!("0x{}", addr)
255 }
256 t if t.starts_with("uint") || t.starts_with("int") => {
257 let trimmed = hex_value.trim_start_matches('0');
259 if trimmed.is_empty() {
260 "0".to_string()
261 } else if trimmed.len() <= 16 {
262 u64::from_str_radix(trimmed, 16)
264 .map(|v| v.to_string())
265 .unwrap_or_else(|_| format!("0x{}", hex_value))
266 } else {
267 format!("0x{}", trimmed)
268 }
269 }
270 "bool" => {
271 let last_char = hex_value.chars().last().unwrap_or('0');
272 if last_char == '1' {
273 "true".to_string()
274 } else {
275 "false".to_string()
276 }
277 }
278 t if t.starts_with("bytes") && !t.contains('[') => {
279 let n: usize = t.trim_start_matches("bytes").parse().unwrap_or(32);
281 let hex_chars = n * 2;
282 let bytes_hex = if hex_value.len() >= hex_chars {
283 &hex_value[..hex_chars]
284 } else {
285 hex_value
286 };
287 format!("0x{}", bytes_hex)
288 }
289 _ => {
290 if hex_value.len() > 16 {
292 format!("0x{}...", &hex_value[..16])
293 } else {
294 format!("0x{}", hex_value)
295 }
296 }
297 }
298}
299
300pub fn build_selector_map(abi: &[AbiEntry]) -> std::collections::HashMap<String, String> {
302 abi.iter()
303 .filter(|e| e.entry_type == "function")
304 .map(|e| (e.selector().to_lowercase(), e.signature()))
305 .collect()
306}
307
308pub fn get_state_changing_functions(abi: &[AbiEntry]) -> Vec<&AbiEntry> {
310 abi.iter().filter(|e| e.is_state_changing()).collect()
311}
312
313pub fn get_events(abi: &[AbiEntry]) -> Vec<&AbiEntry> {
315 abi.iter().filter(|e| e.entry_type == "event").collect()
316}
317
318#[cfg(test)]
319mod tests {
320 use super::*;
321 use crate::contract::source::AbiParam;
322
323 #[test]
324 fn test_decode_address() {
325 let hex = "000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7";
326 let result = decode_abi_value(hex, "address");
327 assert_eq!(result, "0xdac17f958d2ee523a2206206994597c13d831ec7");
328 }
329
330 #[test]
331 fn test_decode_uint256() {
332 let hex = "0000000000000000000000000000000000000000000000000000000005f5e100";
333 let result = decode_abi_value(hex, "uint256");
334 assert_eq!(result, "100000000");
335 }
336
337 #[test]
338 fn test_decode_uint256_zero() {
339 let hex = "0000000000000000000000000000000000000000000000000000000000000000";
340 let result = decode_abi_value(hex, "uint256");
341 assert_eq!(result, "0");
342 }
343
344 #[test]
345 fn test_decode_bool_true() {
346 let hex = "0000000000000000000000000000000000000000000000000000000000000001";
347 let result = decode_abi_value(hex, "bool");
348 assert_eq!(result, "true");
349 }
350
351 #[test]
352 fn test_decode_bool_false() {
353 let hex = "0000000000000000000000000000000000000000000000000000000000000000";
354 let result = decode_abi_value(hex, "bool");
355 assert_eq!(result, "false");
356 }
357
358 #[test]
359 fn test_decode_empty() {
360 let result = decode_abi_value("", "uint256");
361 assert_eq!(result, "(empty)");
362 }
363
364 #[test]
365 fn test_native_transfer_decoding() {
366 let rt = tokio::runtime::Runtime::new().unwrap();
367 let result = rt.block_on(async {
368 let client = reqwest::Client::new();
369 decode_calldata("0x", None, &client).await
370 });
371 assert_eq!(result.function_name, "Native Transfer");
372 }
373
374 #[test]
375 fn test_build_selector_map() {
376 let abi = vec![
377 AbiEntry {
378 entry_type: "function".to_string(),
379 name: "transfer".to_string(),
380 inputs: vec![
381 AbiParam {
382 name: "to".to_string(),
383 param_type: "address".to_string(),
384 indexed: false,
385 components: vec![],
386 },
387 AbiParam {
388 name: "amount".to_string(),
389 param_type: "uint256".to_string(),
390 indexed: false,
391 components: vec![],
392 },
393 ],
394 outputs: vec![],
395 state_mutability: "nonpayable".to_string(),
396 },
397 AbiEntry {
398 entry_type: "event".to_string(),
399 name: "Transfer".to_string(),
400 inputs: vec![],
401 outputs: vec![],
402 state_mutability: String::new(),
403 },
404 ];
405 let map = build_selector_map(&abi);
406 assert_eq!(map.len(), 1); assert!(map.values().any(|v| v == "transfer(address,uint256)"));
408 }
409
410 #[test]
411 fn test_get_state_changing_functions() {
412 let abi = vec![
413 AbiEntry {
414 entry_type: "function".to_string(),
415 name: "transfer".to_string(),
416 inputs: vec![],
417 outputs: vec![],
418 state_mutability: "nonpayable".to_string(),
419 },
420 AbiEntry {
421 entry_type: "function".to_string(),
422 name: "balanceOf".to_string(),
423 inputs: vec![],
424 outputs: vec![],
425 state_mutability: "view".to_string(),
426 },
427 ];
428 let sc_fns = get_state_changing_functions(&abi);
429 assert_eq!(sc_fns.len(), 1);
430 assert_eq!(sc_fns[0].name, "transfer");
431 }
432
433 #[test]
434 fn test_get_events() {
435 let abi = vec![
436 AbiEntry {
437 entry_type: "function".to_string(),
438 name: "transfer".to_string(),
439 inputs: vec![],
440 outputs: vec![],
441 state_mutability: "nonpayable".to_string(),
442 },
443 AbiEntry {
444 entry_type: "event".to_string(),
445 name: "Transfer".to_string(),
446 inputs: vec![],
447 outputs: vec![],
448 state_mutability: String::new(),
449 },
450 AbiEntry {
451 entry_type: "event".to_string(),
452 name: "Approval".to_string(),
453 inputs: vec![],
454 outputs: vec![],
455 state_mutability: String::new(),
456 },
457 ];
458 let events = get_events(&abi);
459 assert_eq!(events.len(), 2);
460 assert_eq!(events[0].name, "Transfer");
461 assert_eq!(events[1].name, "Approval");
462 }
463
464 #[test]
465 fn test_find_abi_by_selector_match() {
466 let entry = AbiEntry {
467 entry_type: "function".to_string(),
468 name: "transfer".to_string(),
469 inputs: vec![
470 AbiParam {
471 name: "to".to_string(),
472 param_type: "address".to_string(),
473 indexed: false,
474 components: vec![],
475 },
476 AbiParam {
477 name: "amount".to_string(),
478 param_type: "uint256".to_string(),
479 indexed: false,
480 components: vec![],
481 },
482 ],
483 outputs: vec![],
484 state_mutability: "nonpayable".to_string(),
485 };
486 let selector = entry.selector();
487 let abi = vec![entry];
488 let found = find_abi_by_selector(&selector, &abi);
489 assert!(found.is_some());
490 assert_eq!(found.unwrap().name, "transfer");
491 }
492
493 #[test]
494 fn test_find_abi_by_selector_no_match() {
495 let abi = vec![AbiEntry {
496 entry_type: "function".to_string(),
497 name: "transfer".to_string(),
498 inputs: vec![],
499 outputs: vec![],
500 state_mutability: "nonpayable".to_string(),
501 }];
502 let found = find_abi_by_selector("0xdeadbeef", &abi);
503 assert!(found.is_none());
504 }
505
506 #[test]
507 fn test_find_abi_by_selector_skips_events() {
508 let abi = vec![AbiEntry {
509 entry_type: "event".to_string(),
510 name: "Transfer".to_string(),
511 inputs: vec![],
512 outputs: vec![],
513 state_mutability: String::new(),
514 }];
515 let found = find_abi_by_selector("0xdeadbeef", &abi);
516 assert!(found.is_none());
517 }
518
519 #[test]
520 fn test_decode_params_from_abi_basic() {
521 let entry = AbiEntry {
522 entry_type: "function".to_string(),
523 name: "transfer".to_string(),
524 inputs: vec![
525 AbiParam {
526 name: "to".to_string(),
527 param_type: "address".to_string(),
528 indexed: false,
529 components: vec![],
530 },
531 AbiParam {
532 name: "amount".to_string(),
533 param_type: "uint256".to_string(),
534 indexed: false,
535 components: vec![],
536 },
537 ],
538 outputs: vec![],
539 state_mutability: "nonpayable".to_string(),
540 };
541 let selector = entry.selector();
542 let addr_pad = "000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7";
543 let amount_pad = "0000000000000000000000000000000000000000000000000000000005f5e100";
544 let input = format!("{}{}{}", selector, addr_pad, amount_pad);
545 let params = decode_params_from_abi(&input, &entry);
546 assert_eq!(params.len(), 2);
547 assert_eq!(params[0].name, "to");
548 assert_eq!(params[0].param_type, "address");
549 assert!(
550 params[0]
551 .value
552 .contains("dac17f958d2ee523a2206206994597c13d831ec7")
553 );
554 assert_eq!(params[1].name, "amount");
555 assert_eq!(params[1].value, "100000000");
556 }
557
558 #[test]
559 fn test_decode_params_from_abi_unnamed_params() {
560 let entry = AbiEntry {
561 entry_type: "function".to_string(),
562 name: "test".to_string(),
563 inputs: vec![AbiParam {
564 name: String::new(),
565 param_type: "bool".to_string(),
566 indexed: false,
567 components: vec![],
568 }],
569 outputs: vec![],
570 state_mutability: "nonpayable".to_string(),
571 };
572 let selector = entry.selector();
573 let bool_pad = "0000000000000000000000000000000000000000000000000000000000000001";
574 let input = format!("{}{}", selector, bool_pad);
575 let params = decode_params_from_abi(&input, &entry);
576 assert_eq!(params.len(), 1);
577 assert_eq!(params[0].name, "arg0");
578 assert_eq!(params[0].value, "true");
579 }
580
581 #[test]
582 fn test_decode_params_from_abi_short_data() {
583 let entry = AbiEntry {
584 entry_type: "function".to_string(),
585 name: "test".to_string(),
586 inputs: vec![
587 AbiParam {
588 name: "a".to_string(),
589 param_type: "uint256".to_string(),
590 indexed: false,
591 components: vec![],
592 },
593 AbiParam {
594 name: "b".to_string(),
595 param_type: "uint256".to_string(),
596 indexed: false,
597 components: vec![],
598 },
599 ],
600 outputs: vec![],
601 state_mutability: "nonpayable".to_string(),
602 };
603 let input = format!(
604 "0xa1b2c3d4{}",
605 "00000000000000000000000000000000000000000000000000000000000000ff00"
606 );
607 let params = decode_params_from_abi(&input, &entry);
608 assert_eq!(params.len(), 2);
609 assert_eq!(params[0].name, "a");
610 assert!(!params[1].value.is_empty());
611 }
612
613 #[test]
614 fn test_decode_params_no_data() {
615 let entry = AbiEntry {
616 entry_type: "function".to_string(),
617 name: "test".to_string(),
618 inputs: vec![AbiParam {
619 name: "x".to_string(),
620 param_type: "uint256".to_string(),
621 indexed: false,
622 components: vec![],
623 }],
624 outputs: vec![],
625 state_mutability: "nonpayable".to_string(),
626 };
627 let input = "0xa1b2c3d4";
628 let params = decode_params_from_abi(input, &entry);
629 assert_eq!(params.len(), 1);
630 assert_eq!(params[0].value, "(empty)");
631 }
632
633 #[test]
634 fn test_decode_abi_value_int() {
635 let hex = "0000000000000000000000000000000000000000000000000000000000000042";
636 assert_eq!(decode_abi_value(hex, "int256"), "66");
637 }
638
639 #[test]
640 fn test_decode_abi_value_large_uint() {
641 let hex = "00000000000000000000000000000000ffffffffffffffffffffffffffffffff";
642 let result = decode_abi_value(hex, "uint256");
643 assert!(result.starts_with("0x"));
644 }
645
646 #[test]
647 fn test_decode_abi_value_bytes4() {
648 let hex = "a9059cbb00000000000000000000000000000000000000000000000000000000";
649 let result = decode_abi_value(hex, "bytes4");
650 assert_eq!(result, "0xa9059cbb");
651 }
652
653 #[test]
654 fn test_decode_abi_value_bytes32() {
655 let hex = "a9059cbb000000000000000000000000000000000000000000000000deadbeef";
656 let result = decode_abi_value(hex, "bytes32");
657 assert!(result.starts_with("0x"));
658 assert_eq!(result.len(), 2 + 64);
659 }
660
661 #[test]
662 fn test_decode_abi_value_short_address() {
663 let hex = "deadbeef";
664 let result = decode_abi_value(hex, "address");
665 assert_eq!(result, "0xdeadbeef");
666 }
667
668 #[test]
669 fn test_decode_abi_value_dynamic_type_long() {
670 let hex = "0000000000000000000000000000000000000000000000000000000000000020";
671 let result = decode_abi_value(hex, "string");
672 assert!(result.starts_with("0x"));
673 assert!(result.ends_with("..."));
674 }
675
676 #[test]
677 fn test_decode_abi_value_dynamic_type_short() {
678 let hex = "abcdef01";
679 let result = decode_abi_value(hex, "bytes");
680 assert_eq!(result, "0xabcdef01");
681 }
682
683 #[test]
684 fn test_decode_calldata_empty_string() {
685 let rt = tokio::runtime::Runtime::new().unwrap();
686 let result = rt.block_on(async {
687 let client = reqwest::Client::new();
688 decode_calldata("", None, &client).await
689 });
690 assert_eq!(result.function_name, "Native Transfer");
691 assert_eq!(result.selector, "0x");
692 }
693
694 #[test]
695 fn test_decode_calldata_short_selector() {
696 let rt = tokio::runtime::Runtime::new().unwrap();
697 let result = rt.block_on(async {
698 let client = reqwest::Client::new();
699 decode_calldata("0xabcd", None, &client).await
700 });
701 assert_eq!(result.selector, "0xabcd");
702 }
703
704 #[test]
705 fn test_decode_calldata_with_abi() {
706 let entry = AbiEntry {
707 entry_type: "function".to_string(),
708 name: "transfer".to_string(),
709 inputs: vec![
710 AbiParam {
711 name: "to".to_string(),
712 param_type: "address".to_string(),
713 indexed: false,
714 components: vec![],
715 },
716 AbiParam {
717 name: "value".to_string(),
718 param_type: "uint256".to_string(),
719 indexed: false,
720 components: vec![],
721 },
722 ],
723 outputs: vec![],
724 state_mutability: "nonpayable".to_string(),
725 };
726 let selector = entry.selector();
727 let source = ContractSource {
728 contract_name: "Token".to_string(),
729 source_code: String::new(),
730 abi: String::new(),
731 compiler_version: String::new(),
732 optimization_used: false,
733 optimization_runs: 0,
734 evm_version: String::new(),
735 license_type: String::new(),
736 is_proxy: false,
737 implementation_address: None,
738 constructor_arguments: String::new(),
739 library: String::new(),
740 swarm_source: String::new(),
741 parsed_abi: vec![entry],
742 };
743 let addr_pad = "000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7";
744 let amt_pad = "0000000000000000000000000000000000000000000000000000000005f5e100";
745 let input = format!("{}{}{}", selector, addr_pad, amt_pad);
746
747 let rt = tokio::runtime::Runtime::new().unwrap();
748 let result = rt.block_on(async {
749 let client = reqwest::Client::new();
750 decode_calldata(&input, Some(&source), &client).await
751 });
752 assert_eq!(result.function_name, "transfer");
753 assert_eq!(result.parameters.len(), 2);
754 matches!(result.source, DecodeSource::VerifiedAbi);
755 }
756
757 #[test]
758 fn test_decode_abi_value_uint_parse_error() {
759 let hex = "zzzzzzzzzzzzzzzz";
760 let result = decode_abi_value(hex, "uint256");
761 assert!(result.starts_with("0x"));
762 }
763
764 #[test]
765 fn test_decode_abi_value_bytes_without_size() {
766 let hex = "a9059cbb00000000000000000000000000000000000000000000000000000000";
767 let result = decode_abi_value(hex, "bytes");
768 assert!(result.starts_with("0x"));
769 }
770
771 #[test]
772 fn test_decode_abi_value_bool_non_one_zero() {
773 let hex = "0000000000000000000000000000000000000000000000000000000000000002";
774 let result = decode_abi_value(hex, "bool");
775 assert_eq!(result, "false");
776 }
777
778 #[test]
779 fn test_decode_params_from_abi_offset_exceeds_data() {
780 let entry = AbiEntry {
781 entry_type: "function".to_string(),
782 name: "twoArgs".to_string(),
783 inputs: vec![
784 AbiParam {
785 name: "a".to_string(),
786 param_type: "uint256".to_string(),
787 indexed: false,
788 components: vec![],
789 },
790 AbiParam {
791 name: "b".to_string(),
792 param_type: "uint256".to_string(),
793 indexed: false,
794 components: vec![],
795 },
796 ],
797 outputs: vec![],
798 state_mutability: "nonpayable".to_string(),
799 };
800 let selector = entry.selector();
801 let single_param_data = "0000000000000000000000000000000000000000000000000000000000000042";
802 let input = format!("{}{}", selector, single_param_data);
803 let params = decode_params_from_abi(&input, &entry);
804 assert_eq!(params.len(), 2);
805 assert_eq!(params[0].value, "66");
806 assert_eq!(params[1].value, "(empty)");
807 }
808
809 #[test]
810 fn test_decode_params_from_abi_partial_second_param() {
811 let entry = AbiEntry {
812 entry_type: "function".to_string(),
813 name: "twoArgs".to_string(),
814 inputs: vec![
815 AbiParam {
816 name: "a".to_string(),
817 param_type: "uint256".to_string(),
818 indexed: false,
819 components: vec![],
820 },
821 AbiParam {
822 name: "b".to_string(),
823 param_type: "uint256".to_string(),
824 indexed: false,
825 components: vec![],
826 },
827 ],
828 outputs: vec![],
829 state_mutability: "nonpayable".to_string(),
830 };
831 let selector = entry.selector();
832 let first_full = "0000000000000000000000000000000000000000000000000000000000000042";
833 let second_partial = "000000000000000000000000000000000000000000000000000000000000";
834 let input = format!("{}{}{}", selector, first_full, second_partial);
835 let params = decode_params_from_abi(&input, &entry);
836 assert_eq!(params.len(), 2);
837 assert_eq!(params[0].value, "66");
838 }
839
840 #[test]
841 fn test_decode_calldata_raw_selector_fallback() {
842 let rt = tokio::runtime::Runtime::new().unwrap();
843 let result = rt.block_on(async {
844 let client = reqwest::Client::new();
845 decode_calldata("0xdeadbeef", None, &client).await
846 });
847 assert_eq!(result.selector, "0xdeadbeef");
848 matches!(
849 result.source,
850 DecodeSource::Unknown | DecodeSource::FourByteDirectory
851 );
852 }
853
854 #[test]
855 fn test_decoded_call_serialize_deserialize() {
856 let call = DecodedCall {
857 selector: "0xa9059cbb".to_string(),
858 signature: "transfer(address,uint256)".to_string(),
859 function_name: "transfer".to_string(),
860 parameters: vec![DecodedParam {
861 name: "to".to_string(),
862 param_type: "address".to_string(),
863 value: "0xdac17f958d2ee523a2206206994597c13d831ec7".to_string(),
864 }],
865 source: DecodeSource::VerifiedAbi,
866 };
867 let json = serde_json::to_string(&call).unwrap();
868 let restored: DecodedCall = serde_json::from_str(&json).unwrap();
869 assert_eq!(restored.selector, call.selector);
870 assert_eq!(restored.function_name, call.function_name);
871 assert_eq!(restored.parameters.len(), 1);
872 }
873}