1use alloy_primitives::U256;
46
47pub const TRANSFER_SELECTOR: [u8; 4] = [0xa9, 0x05, 0x9c, 0xbb];
51
52pub const APPROVE_SELECTOR: [u8; 4] = [0x09, 0x5e, 0xa7, 0xb3];
56
57pub const TRANSFER_FROM_SELECTOR: [u8; 4] = [0x23, 0xb8, 0x72, 0xdd];
61
62const MIN_TWO_PARAM_LENGTH: usize = 68;
64
65const MIN_THREE_PARAM_LENGTH: usize = 100;
67
68#[derive(Debug, Clone, PartialEq, Eq)]
95pub enum Erc20Call {
96 Transfer {
100 to: [u8; 20],
102 amount: U256,
104 },
105
106 Approve {
110 spender: [u8; 20],
112 amount: U256,
114 },
115
116 TransferFrom {
120 from: [u8; 20],
122 to: [u8; 20],
124 amount: U256,
126 },
127}
128
129impl Erc20Call {
130 #[must_use]
135 pub const fn recipient(&self) -> &[u8; 20] {
136 match self {
137 Self::Transfer { to, .. } | Self::TransferFrom { to, .. } => to,
138 Self::Approve { spender, .. } => spender,
139 }
140 }
141
142 #[must_use]
144 pub const fn amount(&self) -> &U256 {
145 match self {
146 Self::Transfer { amount, .. }
147 | Self::Approve { amount, .. }
148 | Self::TransferFrom { amount, .. } => amount,
149 }
150 }
151
152 #[must_use]
154 pub const fn is_transfer(&self) -> bool {
155 matches!(self, Self::Transfer { .. } | Self::TransferFrom { .. })
156 }
157
158 #[must_use]
160 pub const fn is_approval(&self) -> bool {
161 matches!(self, Self::Approve { .. })
162 }
163}
164
165#[must_use]
199pub fn parse_erc20_call(data: &[u8]) -> Option<Erc20Call> {
200 let selector = data.get(0..4)?;
202
203 let selector_arr: [u8; 4] = selector.try_into().ok()?;
205
206 match selector_arr {
207 TRANSFER_SELECTOR => parse_transfer(data),
208 APPROVE_SELECTOR => parse_approve(data),
209 TRANSFER_FROM_SELECTOR => parse_transfer_from(data),
210 _ => None,
211 }
212}
213
214fn parse_transfer(data: &[u8]) -> Option<Erc20Call> {
216 if data.len() < MIN_TWO_PARAM_LENGTH {
218 return None;
219 }
220
221 let to = extract_address(data, 4)?;
223
224 let amount = extract_u256(data, 36)?;
226
227 Some(Erc20Call::Transfer { to, amount })
228}
229
230fn parse_approve(data: &[u8]) -> Option<Erc20Call> {
232 if data.len() < MIN_TWO_PARAM_LENGTH {
234 return None;
235 }
236
237 let spender = extract_address(data, 4)?;
239
240 let amount = extract_u256(data, 36)?;
242
243 Some(Erc20Call::Approve { spender, amount })
244}
245
246fn parse_transfer_from(data: &[u8]) -> Option<Erc20Call> {
248 if data.len() < MIN_THREE_PARAM_LENGTH {
250 return None;
251 }
252
253 let from = extract_address(data, 4)?;
255
256 let to = extract_address(data, 36)?;
258
259 let amount = extract_u256(data, 68)?;
261
262 Some(Erc20Call::TransferFrom { from, to, amount })
263}
264
265fn extract_address(data: &[u8], offset: usize) -> Option<[u8; 20]> {
270 let addr_start = offset + 12;
272 let addr_end = offset + 32;
273
274 let addr_slice = data.get(addr_start..addr_end)?;
275 let addr: [u8; 20] = addr_slice.try_into().ok()?;
276
277 Some(addr)
278}
279
280fn extract_u256(data: &[u8], offset: usize) -> Option<U256> {
284 let end = offset + 32;
285 let word_slice = data.get(offset..end)?;
286 let word: [u8; 32] = word_slice.try_into().ok()?;
287
288 Some(U256::from_be_bytes(word))
289}
290
291#[must_use]
316pub fn is_erc20_selector(data: &[u8]) -> bool {
317 if data.len() < 4 {
318 return false;
319 }
320
321 let Some(selector) = data.get(0..4) else {
322 return false;
323 };
324
325 let Ok(selector_arr): Result<[u8; 4], _> = selector.try_into() else {
326 return false;
327 };
328
329 matches!(
330 selector_arr,
331 TRANSFER_SELECTOR | APPROVE_SELECTOR | TRANSFER_FROM_SELECTOR
332 )
333}
334
335#[cfg(test)]
336mod tests {
337 #![allow(
338 clippy::expect_used,
339 clippy::unwrap_used,
340 clippy::panic,
341 clippy::indexing_slicing,
342 clippy::similar_names,
343 clippy::redundant_clone,
344 clippy::manual_string_new,
345 clippy::needless_raw_string_hashes,
346 clippy::needless_collect,
347 clippy::unreadable_literal
348 )]
349
350 use super::*;
351 use alloy_primitives::hex;
352
353 #[test]
358 fn test_transfer_selector_is_correct() {
359 assert_eq!(TRANSFER_SELECTOR, [0xa9, 0x05, 0x9c, 0xbb]);
361 }
362
363 #[test]
364 fn test_approve_selector_is_correct() {
365 assert_eq!(APPROVE_SELECTOR, [0x09, 0x5e, 0xa7, 0xb3]);
367 }
368
369 #[test]
370 fn test_transfer_from_selector_is_correct() {
371 assert_eq!(TRANSFER_FROM_SELECTOR, [0x23, 0xb8, 0x72, 0xdd]);
373 }
374
375 #[test]
380 fn test_is_erc20_selector_transfer() {
381 assert!(is_erc20_selector(&TRANSFER_SELECTOR));
382 }
383
384 #[test]
385 fn test_is_erc20_selector_approve() {
386 assert!(is_erc20_selector(&APPROVE_SELECTOR));
387 }
388
389 #[test]
390 fn test_is_erc20_selector_transfer_from() {
391 assert!(is_erc20_selector(&TRANSFER_FROM_SELECTOR));
392 }
393
394 #[test]
395 fn test_is_erc20_selector_unknown() {
396 assert!(!is_erc20_selector(&[0x12, 0x34, 0x56, 0x78]));
397 }
398
399 #[test]
400 fn test_is_erc20_selector_too_short() {
401 assert!(!is_erc20_selector(&[]));
402 assert!(!is_erc20_selector(&[0xa9]));
403 assert!(!is_erc20_selector(&[0xa9, 0x05]));
404 assert!(!is_erc20_selector(&[0xa9, 0x05, 0x9c]));
405 }
406
407 #[test]
408 fn test_is_erc20_selector_with_extra_data() {
409 let mut data = TRANSFER_SELECTOR.to_vec();
411 data.extend_from_slice(&[0x00; 64]);
412 assert!(is_erc20_selector(&data));
413 }
414
415 #[test]
420 fn test_parse_empty_returns_none() {
421 assert!(parse_erc20_call(&[]).is_none());
422 }
423
424 #[test]
425 fn test_parse_too_short_for_selector_returns_none() {
426 assert!(parse_erc20_call(&[0xa9]).is_none());
427 assert!(parse_erc20_call(&[0xa9, 0x05]).is_none());
428 assert!(parse_erc20_call(&[0xa9, 0x05, 0x9c]).is_none());
429 }
430
431 #[test]
432 fn test_parse_selector_only_returns_none() {
433 assert!(parse_erc20_call(&TRANSFER_SELECTOR).is_none());
435 assert!(parse_erc20_call(&APPROVE_SELECTOR).is_none());
436 assert!(parse_erc20_call(&TRANSFER_FROM_SELECTOR).is_none());
437 }
438
439 #[test]
440 fn test_parse_unknown_selector_returns_none() {
441 let data = hex::decode(
442 "12345678\
443 0000000000000000000000001234567890123456789012345678901234567890\
444 0000000000000000000000000000000000000000000000000000000000000001",
445 )
446 .expect("valid hex");
447
448 assert!(parse_erc20_call(&data).is_none());
449 }
450
451 #[test]
456 fn test_parse_transfer_valid() {
457 let data = hex::decode(
459 "a9059cbb\
460 0000000000000000000000001234567890123456789012345678901234567890\
461 00000000000000000000000000000000000000000000000000000000000f4240",
462 )
463 .expect("valid hex");
464
465 let result = parse_erc20_call(&data);
466 assert!(result.is_some());
467
468 let call = result.expect("should parse successfully");
469 match call {
470 Erc20Call::Transfer { to, amount } => {
471 let expected_to =
472 hex::decode("1234567890123456789012345678901234567890").expect("valid hex");
473 let expected_to_arr: [u8; 20] = expected_to.try_into().expect("20 bytes");
474 assert_eq!(to, expected_to_arr);
475 assert_eq!(amount, U256::from(1_000_000u64));
476 }
477 _ => panic!("expected Transfer variant"),
478 }
479 }
480
481 #[test]
482 fn test_parse_transfer_zero_amount() {
483 let data = hex::decode(
484 "a9059cbb\
485 000000000000000000000000abcdefabcdefabcdefabcdefabcdefabcdefabcd\
486 0000000000000000000000000000000000000000000000000000000000000000",
487 )
488 .expect("valid hex");
489
490 let result = parse_erc20_call(&data);
491 assert!(result.is_some());
492
493 if let Some(Erc20Call::Transfer { amount, .. }) = result {
494 assert_eq!(amount, U256::ZERO);
495 } else {
496 panic!("expected Transfer variant");
497 }
498 }
499
500 #[test]
501 fn test_parse_transfer_max_amount() {
502 let data = hex::decode(
503 "a9059cbb\
504 000000000000000000000000abcdefabcdefabcdefabcdefabcdefabcdefabcd\
505 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
506 )
507 .expect("valid hex");
508
509 let result = parse_erc20_call(&data);
510 assert!(result.is_some());
511
512 if let Some(Erc20Call::Transfer { amount, .. }) = result {
513 assert_eq!(amount, U256::MAX);
514 } else {
515 panic!("expected Transfer variant");
516 }
517 }
518
519 #[test]
520 fn test_parse_transfer_too_short() {
521 let data = hex::decode(
523 "a9059cbb\
524 0000000000000000000000001234567890123456789012345678901234567890\
525 000000000000000000000000000000000000000000000000000000000000f4",
526 )
527 .expect("valid hex");
528
529 assert!(parse_erc20_call(&data).is_none());
530 }
531
532 #[test]
533 fn test_parse_transfer_with_extra_data() {
534 let mut data = hex::decode(
536 "a9059cbb\
537 0000000000000000000000001234567890123456789012345678901234567890\
538 00000000000000000000000000000000000000000000000000000000000f4240",
539 )
540 .expect("valid hex");
541 data.extend_from_slice(&[0xde, 0xad, 0xbe, 0xef]);
542
543 let result = parse_erc20_call(&data);
544 assert!(result.is_some());
545 assert!(matches!(result, Some(Erc20Call::Transfer { .. })));
546 }
547
548 #[test]
553 fn test_parse_approve_valid() {
554 let data = hex::decode(
556 "095ea7b3\
557 000000000000000000000000abcdefabcdefabcdefabcdefabcdefabcdefabcd\
558 00000000000000000000000000000000000000000000000000000000004c4b40",
559 )
560 .expect("valid hex");
561
562 let result = parse_erc20_call(&data);
563 assert!(result.is_some());
564
565 let call = result.expect("should parse successfully");
566 match call {
567 Erc20Call::Approve { spender, amount } => {
568 let expected_spender =
569 hex::decode("abcdefabcdefabcdefabcdefabcdefabcdefabcd").expect("valid hex");
570 let expected_spender_arr: [u8; 20] = expected_spender.try_into().expect("20 bytes");
571 assert_eq!(spender, expected_spender_arr);
572 assert_eq!(amount, U256::from(5_000_000u64));
573 }
574 _ => panic!("expected Approve variant"),
575 }
576 }
577
578 #[test]
579 fn test_parse_approve_unlimited() {
580 let data = hex::decode(
582 "095ea7b3\
583 000000000000000000000000abcdefabcdefabcdefabcdefabcdefabcdefabcd\
584 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
585 )
586 .expect("valid hex");
587
588 let result = parse_erc20_call(&data);
589 assert!(result.is_some());
590
591 if let Some(Erc20Call::Approve { amount, .. }) = result {
592 assert_eq!(amount, U256::MAX);
593 } else {
594 panic!("expected Approve variant");
595 }
596 }
597
598 #[test]
599 fn test_parse_approve_too_short() {
600 let data = hex::decode(
601 "095ea7b3\
602 000000000000000000000000abcdefabcdefabcdefabcdefabcdefabcdefab",
603 )
604 .expect("valid hex");
605
606 assert!(parse_erc20_call(&data).is_none());
607 }
608
609 #[test]
614 fn test_parse_transfer_from_valid() {
615 let data = hex::decode(
617 "23b872dd\
618 0000000000000000000000001111111111111111111111111111111111111111\
619 0000000000000000000000002222222222222222222222222222222222222222\
620 0000000000000000000000000000000000000000000000000de0b6b3a7640000",
621 )
622 .expect("valid hex");
623
624 let result = parse_erc20_call(&data);
625 assert!(result.is_some());
626
627 let call = result.expect("should parse successfully");
628 match call {
629 Erc20Call::TransferFrom { from, to, amount } => {
630 let expected_from =
631 hex::decode("1111111111111111111111111111111111111111").expect("valid hex");
632 let expected_from_arr: [u8; 20] = expected_from.try_into().expect("20 bytes");
633
634 let expected_to =
635 hex::decode("2222222222222222222222222222222222222222").expect("valid hex");
636 let expected_to_arr: [u8; 20] = expected_to.try_into().expect("20 bytes");
637
638 assert_eq!(from, expected_from_arr);
639 assert_eq!(to, expected_to_arr);
640 assert_eq!(amount, U256::from(1_000_000_000_000_000_000u64)); }
642 _ => panic!("expected TransferFrom variant"),
643 }
644 }
645
646 #[test]
647 fn test_parse_transfer_from_too_short() {
648 let data = hex::decode(
650 "23b872dd\
651 0000000000000000000000001111111111111111111111111111111111111111\
652 0000000000000000000000002222222222222222222222222222222222222222",
653 )
654 .expect("valid hex");
655
656 assert!(parse_erc20_call(&data).is_none());
657 }
658
659 #[test]
660 fn test_parse_transfer_from_partial_amount() {
661 let data = hex::decode(
663 "23b872dd\
664 0000000000000000000000001111111111111111111111111111111111111111\
665 0000000000000000000000002222222222222222222222222222222222222222\
666 00000000000000000000000000000000000000000000000000000000000001",
667 )
668 .expect("valid hex");
669
670 assert!(parse_erc20_call(&data).is_none());
671 }
672
673 #[test]
678 fn test_erc20call_recipient_transfer() {
679 let to = [0x12; 20];
680 let call = Erc20Call::Transfer {
681 to,
682 amount: U256::from(100u64),
683 };
684 assert_eq!(call.recipient(), &to);
685 }
686
687 #[test]
688 fn test_erc20call_recipient_approve() {
689 let spender = [0x34; 20];
690 let call = Erc20Call::Approve {
691 spender,
692 amount: U256::from(100u64),
693 };
694 assert_eq!(call.recipient(), &spender);
695 }
696
697 #[test]
698 fn test_erc20call_recipient_transfer_from() {
699 let from = [0x12; 20];
700 let to = [0x34; 20];
701 let call = Erc20Call::TransferFrom {
702 from,
703 to,
704 amount: U256::from(100u64),
705 };
706 assert_eq!(call.recipient(), &to);
707 }
708
709 #[test]
710 fn test_erc20call_amount() {
711 let amount = U256::from(12345u64);
712
713 let transfer = Erc20Call::Transfer {
714 to: [0; 20],
715 amount,
716 };
717 assert_eq!(*transfer.amount(), amount);
718
719 let approve = Erc20Call::Approve {
720 spender: [0; 20],
721 amount,
722 };
723 assert_eq!(*approve.amount(), amount);
724
725 let transfer_from = Erc20Call::TransferFrom {
726 from: [0; 20],
727 to: [0; 20],
728 amount,
729 };
730 assert_eq!(*transfer_from.amount(), amount);
731 }
732
733 #[test]
734 fn test_erc20call_is_transfer() {
735 let transfer = Erc20Call::Transfer {
736 to: [0; 20],
737 amount: U256::ZERO,
738 };
739 assert!(transfer.is_transfer());
740 assert!(!transfer.is_approval());
741
742 let transfer_from = Erc20Call::TransferFrom {
743 from: [0; 20],
744 to: [0; 20],
745 amount: U256::ZERO,
746 };
747 assert!(transfer_from.is_transfer());
748 assert!(!transfer_from.is_approval());
749
750 let approve = Erc20Call::Approve {
751 spender: [0; 20],
752 amount: U256::ZERO,
753 };
754 assert!(!approve.is_transfer());
755 assert!(approve.is_approval());
756 }
757
758 #[test]
763 fn test_erc20call_clone() {
764 let call = Erc20Call::Transfer {
765 to: [0x12; 20],
766 amount: U256::from(100u64),
767 };
768 let cloned = call.clone();
769 assert_eq!(call, cloned);
770 }
771
772 #[test]
773 fn test_erc20call_debug() {
774 let call = Erc20Call::Transfer {
775 to: [0x12; 20],
776 amount: U256::from(100u64),
777 };
778 let debug_str = format!("{call:?}");
779 assert!(debug_str.contains("Transfer"));
780 }
781
782 #[test]
787 fn test_parse_usdc_transfer() {
788 let data = hex::decode(
790 "a9059cbb\
791 000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7\
792 00000000000000000000000000000000000000000000000000000000000f4240",
793 )
794 .expect("valid hex");
795
796 let result = parse_erc20_call(&data);
797 assert!(result.is_some());
798
799 if let Some(Erc20Call::Transfer { amount, .. }) = result {
800 assert_eq!(amount, U256::from(1_000_000u64));
802 } else {
803 panic!("expected Transfer variant");
804 }
805 }
806
807 #[test]
808 fn test_parse_dai_approve() {
809 let data = hex::decode(
811 "095ea7b3\
812 0000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488d\
813 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
814 )
815 .expect("valid hex");
816
817 let result = parse_erc20_call(&data);
818 assert!(result.is_some());
819
820 if let Some(Erc20Call::Approve { spender, amount }) = result {
821 let expected_spender =
823 hex::decode("7a250d5630b4cf539739df2c5dacb4c659f2488d").expect("valid hex");
824 let expected_spender_arr: [u8; 20] = expected_spender.try_into().expect("20 bytes");
825 assert_eq!(spender, expected_spender_arr);
826 assert_eq!(amount, U256::MAX);
827 } else {
828 panic!("expected Approve variant");
829 }
830 }
831
832 #[test]
837 fn test_address_with_leading_zeros() {
838 let data = hex::decode(
840 "a9059cbb\
841 0000000000000000000000000000000000000000000000000000000000000001\
842 0000000000000000000000000000000000000000000000000000000000000064",
843 )
844 .expect("valid hex");
845
846 let result = parse_erc20_call(&data);
847 assert!(result.is_some());
848
849 if let Some(Erc20Call::Transfer { to, amount }) = result {
850 let expected: [u8; 20] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1];
852 assert_eq!(to, expected);
853 assert_eq!(amount, U256::from(100u64));
854 } else {
855 panic!("expected Transfer variant");
856 }
857 }
858
859 #[test]
860 fn test_exactly_minimum_length() {
861 let data = hex::decode(
863 "a9059cbb\
864 0000000000000000000000001234567890123456789012345678901234567890\
865 0000000000000000000000000000000000000000000000000000000000000001",
866 )
867 .expect("valid hex");
868 assert_eq!(data.len(), 68);
869
870 let result = parse_erc20_call(&data);
871 assert!(result.is_some());
872 }
873
874 #[test]
875 fn test_one_byte_short() {
876 let data = hex::decode(
878 "a9059cbb\
879 0000000000000000000000001234567890123456789012345678901234567890\
880 00000000000000000000000000000000000000000000000000000000000001",
881 )
882 .expect("valid hex");
883 assert_eq!(data.len(), 67);
884
885 assert!(parse_erc20_call(&data).is_none());
886 }
887
888 #[test]
893 fn should_return_none_when_transfer_calldata_has_partial_address() {
894 let data = hex::decode(
896 "a9059cbb\
897 0000000000000000000000001234567890123456789012345678901234567890",
898 )
899 .expect("valid hex");
900 assert_eq!(data.len(), 36); let result = parse_erc20_call(&data);
904
905 assert!(
907 result.is_none(),
908 "Should return None for incomplete transfer calldata"
909 );
910 }
911
912 #[test]
913 fn should_return_none_when_transfer_calldata_missing_partial_amount() {
914 let data = hex::decode(
916 "a9059cbb\
917 0000000000000000000000001234567890123456789012345678901234567890\
918 00000000000000000000000000000000",
919 )
920 .expect("valid hex");
921 assert_eq!(data.len(), 52); let result = parse_erc20_call(&data);
925
926 assert!(
928 result.is_none(),
929 "Should return None when amount field is truncated"
930 );
931 }
932
933 #[test]
934 fn should_return_none_when_approve_calldata_truncated_before_amount() {
935 let data = hex::decode(
937 "095ea7b3\
938 000000000000000000000000abcdefabcdefabcdefabcdefabcdefabcdefabcd",
939 )
940 .expect("valid hex");
941 assert_eq!(data.len(), 36); let result = parse_erc20_call(&data);
945
946 assert!(
948 result.is_none(),
949 "Should return None for approve missing amount"
950 );
951 }
952
953 #[test]
954 fn should_return_none_when_approve_calldata_has_partial_amount() {
955 let data = hex::decode(
957 "095ea7b3\
958 000000000000000000000000abcdefabcdefabcdefabcdefabcdefabcdefabcd\
959 000000000000000000000000000000000000000000000000",
960 )
961 .expect("valid hex");
962 assert_eq!(data.len(), 60); let result = parse_erc20_call(&data);
966
967 assert!(
969 result.is_none(),
970 "Should return None when approve amount is truncated"
971 );
972 }
973
974 #[test]
975 fn should_return_none_when_transfer_from_calldata_missing_to_address() {
976 let data = hex::decode(
978 "23b872dd\
979 0000000000000000000000001111111111111111111111111111111111111111",
980 )
981 .expect("valid hex");
982 assert_eq!(data.len(), 36); let result = parse_erc20_call(&data);
986
987 assert!(
989 result.is_none(),
990 "Should return None when transferFrom missing 'to' and amount"
991 );
992 }
993
994 #[test]
995 fn should_return_none_when_transfer_from_calldata_missing_amount() {
996 let data = hex::decode(
998 "23b872dd\
999 0000000000000000000000001111111111111111111111111111111111111111\
1000 0000000000000000000000002222222222222222222222222222222222222222",
1001 )
1002 .expect("valid hex");
1003 assert_eq!(data.len(), 68); let result = parse_erc20_call(&data);
1007
1008 assert!(
1010 result.is_none(),
1011 "Should return None when transferFrom missing amount"
1012 );
1013 }
1014
1015 #[test]
1016 fn should_return_none_when_transfer_from_calldata_has_partial_to_address() {
1017 let data = hex::decode(
1019 "23b872dd\
1020 0000000000000000000000001111111111111111111111111111111111111111\
1021 00000000000000000000000000000000",
1022 )
1023 .expect("valid hex");
1024 assert_eq!(data.len(), 52); let result = parse_erc20_call(&data);
1028
1029 assert!(
1031 result.is_none(),
1032 "Should return None when 'to' address is truncated"
1033 );
1034 }
1035
1036 #[test]
1037 fn should_return_none_when_transfer_from_calldata_has_partial_amount() {
1038 let data = hex::decode(
1040 "23b872dd\
1041 0000000000000000000000001111111111111111111111111111111111111111\
1042 0000000000000000000000002222222222222222222222222222222222222222\
1043 00000000000000000000000000000000",
1044 )
1045 .expect("valid hex");
1046 assert_eq!(data.len(), 84); let result = parse_erc20_call(&data);
1050
1051 assert!(
1053 result.is_none(),
1054 "Should return None when amount is truncated"
1055 );
1056 }
1057
1058 #[test]
1059 fn should_handle_valid_selector_with_parameter_extraction_failure() {
1060 let data = vec![0xa9, 0x05, 0x9c, 0xbb, 0x00, 0x00, 0x00, 0x00]; let result = parse_erc20_call(&data);
1065
1066 assert!(
1068 result.is_none(),
1069 "Should fail gracefully when extraction impossible"
1070 );
1071 }
1072
1073 #[test]
1074 fn should_identify_all_transfer_variants_correctly() {
1075 let transfer = Erc20Call::Transfer {
1077 to: [0xAA; 20],
1078 amount: U256::from(1000u64),
1079 };
1080 assert!(transfer.is_transfer());
1081 assert!(!transfer.is_approval());
1082
1083 let transfer_from = Erc20Call::TransferFrom {
1085 from: [0xBB; 20],
1086 to: [0xCC; 20],
1087 amount: U256::from(2000u64),
1088 };
1089 assert!(transfer_from.is_transfer());
1090 assert!(!transfer_from.is_approval());
1091
1092 let approve = Erc20Call::Approve {
1094 spender: [0xDD; 20],
1095 amount: U256::from(3000u64),
1096 };
1097 assert!(!approve.is_transfer());
1098 }
1099
1100 #[test]
1101 fn should_identify_all_approval_variants_correctly() {
1102 let approve = Erc20Call::Approve {
1104 spender: [0xEE; 20],
1105 amount: U256::MAX,
1106 };
1107 assert!(approve.is_approval());
1108 assert!(!approve.is_transfer());
1109
1110 let transfer = Erc20Call::Transfer {
1112 to: [0xFF; 20],
1113 amount: U256::from(500u64),
1114 };
1115 assert!(!transfer.is_approval());
1116
1117 let transfer_from = Erc20Call::TransferFrom {
1119 from: [0x11; 20],
1120 to: [0x22; 20],
1121 amount: U256::from(600u64),
1122 };
1123 assert!(!transfer_from.is_approval());
1124 }
1125
1126 #[test]
1131 fn should_format_transfer_debug_output_correctly() {
1132 let transfer = Erc20Call::Transfer {
1134 to: [
1135 0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56, 0x78, 0x90, 0xAB,
1136 0xCD, 0xEF, 0x12, 0x34, 0x56, 0x78,
1137 ],
1138 amount: U256::from(1_000_000u64),
1139 };
1140
1141 let debug_output = format!("{transfer:?}");
1143
1144 assert!(debug_output.contains("Transfer"));
1146 assert!(debug_output.contains("to"));
1147 assert!(debug_output.contains("amount"));
1148 }
1149
1150 #[test]
1151 fn should_format_approve_debug_output_correctly() {
1152 let approve = Erc20Call::Approve {
1154 spender: [0xFF; 20],
1155 amount: U256::MAX,
1156 };
1157
1158 let debug_output = format!("{approve:?}");
1160
1161 assert!(debug_output.contains("Approve"));
1163 assert!(debug_output.contains("spender"));
1164 assert!(debug_output.contains("amount"));
1165 }
1166
1167 #[test]
1168 fn should_format_transfer_from_debug_output_correctly() {
1169 let transfer_from = Erc20Call::TransferFrom {
1171 from: [0xAA; 20],
1172 to: [0xBB; 20],
1173 amount: U256::from(500_000_000u64),
1174 };
1175
1176 let debug_output = format!("{transfer_from:?}");
1178
1179 assert!(debug_output.contains("TransferFrom"));
1181 assert!(debug_output.contains("from"));
1182 assert!(debug_output.contains("to"));
1183 assert!(debug_output.contains("amount"));
1184 }
1185
1186 #[test]
1187 fn should_format_erc20call_debug_with_zero_amount() {
1188 let transfer = Erc20Call::Transfer {
1190 to: [0x00; 20],
1191 amount: U256::ZERO,
1192 };
1193
1194 let debug_output = format!("{transfer:?}");
1196
1197 assert!(debug_output.contains("Transfer"));
1199 assert!(!debug_output.is_empty());
1200 }
1201
1202 #[test]
1203 fn should_format_erc20call_debug_with_max_amount() {
1204 let approve = Erc20Call::Approve {
1206 spender: [0x00; 20],
1207 amount: U256::MAX,
1208 };
1209
1210 let debug_output = format!("{approve:?}");
1212
1213 assert!(debug_output.contains("Approve"));
1215 assert!(!debug_output.is_empty());
1216 }
1217}