From 7a4e35c91c9d322b3608ccd70a673d6d1cf9e416 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tino=20Mart=C3=ADnez=20Molina?= Date: Tue, 3 Mar 2026 23:10:19 -0800 Subject: [PATCH] test: selector resolver and positional args parsing --- src/cli/call.rs | 91 +++++++++++++++++++ src/utils/selector_resolver.rs | 154 +++++++++++++++++++++++++++++++++ src/utils/value_parser.rs | 12 +-- 3 files changed, 251 insertions(+), 6 deletions(-) diff --git a/src/cli/call.rs b/src/cli/call.rs index a583513..cdcfe9d 100644 --- a/src/cli/call.rs +++ b/src/cli/call.rs @@ -165,6 +165,97 @@ fn parse_block_id(block: &str) -> Result { mod tests { use super::*; + fn make_call_args(args: Vec<&str>, create: bool) -> CallArgs { + CallArgs { + args: args.into_iter().map(String::from).collect(), + create, + from: None, + gas_limit: None, + value: None, + block: "latest".to_string(), + opts: TraceOpts { + rpc_url: None, + resolve_selectors: false, + resolve_contracts: false, + include_args: false, + include_calldata: false, + include_logs: false, + no_proxy: false, + no_color: false, + }, + } + } + + #[test] + fn test_parse_positional_args_create_one_arg() { + let args = make_call_args(vec!["0xdead"], true); + let parsed = parse_positional_args(&args).unwrap(); + assert!(parsed.to.is_none()); + assert_eq!(parsed.data, "0xdead"); + } + + #[test] + fn test_parse_positional_args_create_two_args() { + let args = make_call_args(vec!["0xaddr", "0xdata"], true); + let Err(err) = parse_positional_args(&args) else { + panic!("expected Err"); + }; + assert!(err.to_string().contains("--create cannot be used")); + } + + #[test] + fn test_parse_positional_args_create_no_args() { + let args = make_call_args(vec![], true); + let Err(err) = parse_positional_args(&args) else { + panic!("expected Err"); + }; + assert!(err.to_string().contains("expected exactly 1 argument")); + } + + #[test] + fn test_parse_positional_args_create_three_args() { + let args = make_call_args(vec!["a", "b", "c"], true); + let Err(err) = parse_positional_args(&args) else { + panic!("expected Err"); + }; + assert!(err.to_string().contains("expected exactly 1 argument")); + } + + #[test] + fn test_parse_positional_args_normal_two_args() { + let args = make_call_args(vec!["0xaddr", "0xdata"], false); + let parsed = parse_positional_args(&args).unwrap(); + assert_eq!(parsed.to, Some("0xaddr".to_string())); + assert_eq!(parsed.data, "0xdata"); + } + + #[test] + fn test_parse_positional_args_normal_one_arg() { + let args = make_call_args(vec!["0xdata"], false); + let Err(err) = parse_positional_args(&args) else { + panic!("expected Err"); + }; + assert!(err.to_string().contains("missing DATA argument")); + } + + #[test] + fn test_parse_positional_args_normal_no_args() { + let args = make_call_args(vec![], false); + let Err(err) = parse_positional_args(&args) else { + panic!("expected Err"); + }; + assert!(err.to_string().contains("expected 2 arguments")); + } + + #[test] + fn test_parse_positional_args_normal_three_args() { + let args = make_call_args(vec!["a", "b", "c"], false); + let Err(err) = parse_positional_args(&args) else { + panic!("expected Err"); + }; + assert!(err.to_string().contains("expected 2 arguments")); + } + #[test] fn test_parse_block_id_tags() { assert_eq!(parse_block_id("latest").unwrap(), "latest"); diff --git a/src/utils/selector_resolver.rs b/src/utils/selector_resolver.rs index b650cda..308783d 100644 --- a/src/utils/selector_resolver.rs +++ b/src/utils/selector_resolver.rs @@ -153,3 +153,157 @@ fn select_best_entry(entries: &[serde_json::Value], calldata: Option<&str>) -> O entries.first().and_then(name) } } + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + const DECODABLE_SIG: &str = "transfer(address,uint256)"; + const DECODABLE_SIG_ALT: &str = "approve(address,uint256)"; + const NON_DECODABLE_SIG: &str = "foo(address,address,address)"; + const CALLDATA: &str = "0xa9059cbb000000000000000000000000deadbeefdeadbeefdeadbeefdeadbeefdeadbeef00000000000000000000000000000000000000000000000000000000000003e8"; + + fn entry(name: &str, verified: bool, filtered: bool) -> serde_json::Value { + json!({"name": name, "hasVerifiedContract": verified, "filtered": filtered}) + } + + #[test] + fn test_select_best_entry_single_tiers() { + let calldata = Some(CALLDATA); + + let entries = vec![entry(DECODABLE_SIG, true, false)]; + assert_eq!( + select_best_entry(&entries, calldata), + Some(DECODABLE_SIG.to_string()) + ); + + let entries = vec![entry(DECODABLE_SIG, true, true)]; + assert_eq!( + select_best_entry(&entries, calldata), + Some(DECODABLE_SIG.to_string()) + ); + + let entries = vec![entry(DECODABLE_SIG, false, false)]; + assert_eq!( + select_best_entry(&entries, calldata), + Some(DECODABLE_SIG.to_string()) + ); + + let entries = vec![entry(DECODABLE_SIG, false, true)]; + assert_eq!( + select_best_entry(&entries, calldata), + Some(DECODABLE_SIG.to_string()) + ); + } + + #[test] + fn test_select_best_entry_non_decodable_fallback() { + let entries = vec![entry(NON_DECODABLE_SIG, true, false)]; + assert_eq!( + select_best_entry(&entries, Some(CALLDATA)), + Some(NON_DECODABLE_SIG.to_string()), + ); + } + + #[test] + fn test_select_best_entry_missing_name() { + let entries = vec![json!({"hasVerifiedContract": true, "filtered": false})]; + assert_eq!(select_best_entry(&entries, Some(CALLDATA)), None); + } + + #[test] + fn test_select_best_entry_priority_order() { + let calldata = Some(CALLDATA); + + let entries = vec![ + entry(DECODABLE_SIG_ALT, true, true), + entry(DECODABLE_SIG, true, false), + ]; + assert_eq!( + select_best_entry(&entries, calldata), + Some(DECODABLE_SIG.to_string()) + ); + + let entries = vec![ + entry(DECODABLE_SIG_ALT, false, false), + entry(DECODABLE_SIG, true, true), + ]; + assert_eq!( + select_best_entry(&entries, calldata), + Some(DECODABLE_SIG.to_string()) + ); + + let entries = vec![ + entry(DECODABLE_SIG_ALT, false, true), + entry(DECODABLE_SIG, false, false), + ]; + assert_eq!( + select_best_entry(&entries, calldata), + Some(DECODABLE_SIG.to_string()) + ); + } + + #[test] + fn test_select_best_entry_decodable_beats_non_decodable() { + let entries = vec![ + entry(NON_DECODABLE_SIG, true, false), + entry(DECODABLE_SIG, false, true), + ]; + assert_eq!( + select_best_entry(&entries, Some(CALLDATA)), + Some(DECODABLE_SIG.to_string()), + ); + } + + #[test] + fn test_select_best_entry_all_non_decodable() { + let entries = vec![ + entry(NON_DECODABLE_SIG, true, false), + entry("bar(address,address,address)", false, true), + ]; + assert_eq!( + select_best_entry(&entries, Some(CALLDATA)), + Some(NON_DECODABLE_SIG.to_string()), + ); + } + + #[test] + fn test_select_best_entry_empty() { + let entries: Vec = vec![]; + assert_eq!(select_best_entry(&entries, Some(CALLDATA)), None); + } + + #[test] + fn test_select_best_entry_event_single() { + let entries = vec![entry("Transfer(address,address,uint256)", true, false)]; + assert_eq!( + select_best_entry(&entries, None), + Some("Transfer(address,address,uint256)".to_string()), + ); + } + + #[test] + fn test_select_best_entry_event_no_name() { + let entries = vec![json!({"hasVerifiedContract": true, "filtered": false})]; + assert_eq!(select_best_entry(&entries, None), None); + } + + #[test] + fn test_select_best_entry_event_multiple() { + let entries = vec![ + entry("Transfer(address,address,uint256)", true, false), + entry("Approval(address,address,uint256)", true, false), + ]; + assert_eq!( + select_best_entry(&entries, None), + Some("Transfer(address,address,uint256)".to_string()), + ); + } + + #[test] + fn test_select_best_entry_event_empty() { + let entries: Vec = vec![]; + assert_eq!(select_best_entry(&entries, None), None); + } +} diff --git a/src/utils/value_parser.rs b/src/utils/value_parser.rs index cebf329..129562b 100644 --- a/src/utils/value_parser.rs +++ b/src/utils/value_parser.rs @@ -60,7 +60,7 @@ mod tests { use super::*; #[test] - fn test_hex() { + fn test_parse_value_hex() { assert_eq!( parse_value("0xde0b6b3a7640000").unwrap(), "0xde0b6b3a7640000" @@ -69,13 +69,13 @@ mod tests { } #[test] - fn test_decimal() { + fn test_parse_value_decimal() { assert_eq!(parse_value("1000000").unwrap(), "0xf4240"); assert_eq!(parse_value("0").unwrap(), "0x0"); } #[test] - fn test_ether() { + fn test_parse_value_ether() { assert_eq!(parse_value("1ether").unwrap(), "0xde0b6b3a7640000"); assert_eq!(parse_value("0.000000000000000001ether").unwrap(), "0x1"); @@ -85,13 +85,13 @@ mod tests { } #[test] - fn test_gwei() { + fn test_parse_value_gwei() { assert_eq!(parse_value("1gwei").unwrap(), "0x3b9aca00"); assert_eq!(parse_value("100gwei").unwrap(), "0x174876e800"); } #[test] - fn test_wei() { + fn test_parse_value_wei() { assert_eq!(parse_value("1wei").unwrap(), "0x1"); assert_eq!(parse_value("1000000000wei").unwrap(), "0x3b9aca00"); } @@ -105,7 +105,7 @@ mod tests { } #[test] - fn test_invalid() { + fn test_parse_value_invalid() { assert!(parse_value("abc").is_err()); assert!(parse_value("1.2.3ether").is_err()); }