diff --git a/src/cli/call.rs b/src/cli/call.rs index 4fa7697..b7f3216 100644 --- a/src/cli/call.rs +++ b/src/cli/call.rs @@ -157,7 +157,7 @@ fn parse_block_id(block: &str) -> Result { let num: u64 = s.parse().map_err(|_| { TraceError::InvalidInput(format!("--block: invalid block identifier '{s}'")) })?; - Ok(format!("0x{num:x}")) + Ok(format!("{num:#x}")) } } } diff --git a/src/cli/clean.rs b/src/cli/clean.rs index ef9d6c9..f8c4072 100644 --- a/src/cli/clean.rs +++ b/src/cli/clean.rs @@ -2,7 +2,6 @@ use crate::utils::disk_cache::{CacheError, DiskCache, ALL_CACHE_KINDS}; use clap::Parser; use std::fs; use std::path::Path; -use thiserror::Error; #[derive(Parser, Debug)] pub struct CleanArgs { @@ -11,17 +10,7 @@ pub struct CleanArgs { pub only_unknown: bool, } -#[derive(Debug, Error)] -pub enum CleanError { - #[error("{0}")] - Cache(#[from] CacheError), - - #[error("io error: {0}")] - Io(#[from] std::io::Error), -} - -#[allow(clippy::needless_pass_by_value)] // clap produces owned values -pub fn run(args: CleanArgs) -> Result<(), CleanError> { +pub fn run(args: &CleanArgs) -> Result<(), CacheError> { let mut found_any = false; for kind in ALL_CACHE_KINDS { diff --git a/src/main.rs b/src/main.rs index 8144fe7..7cda7bf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,7 +18,7 @@ fn run() -> Result<(), Box> { match args.command { cli::Command::Tx(tx_args) => cli::tx::run(tx_args)?, cli::Command::Call(call_args) => cli::call::run(call_args)?, - cli::Command::Clean(clean_args) => cli::clean::run(clean_args)?, + cli::Command::Clean(ref clean_args) => cli::clean::run(clean_args)?, } Ok(()) diff --git a/src/utils/disk_cache.rs b/src/utils/disk_cache.rs index 83773b2..5f172c5 100644 --- a/src/utils/disk_cache.rs +++ b/src/utils/disk_cache.rs @@ -229,5 +229,6 @@ mod tests { cache.insert_transient_miss("0xdeadbeef".to_owned()); assert!(matches!(cache.lookup("0xdeadbeef"), CacheLookup::Miss)); assert!(!cache.entries.contains_key("0xdeadbeef")); + assert!(cache.transient_misses.contains("0xdeadbeef")); } } diff --git a/src/utils/hex_utils.rs b/src/utils/hex_utils.rs index 1119a56..5a98543 100644 --- a/src/utils/hex_utils.rs +++ b/src/utils/hex_utils.rs @@ -1,21 +1,19 @@ use alloy_primitives::U256; -pub fn strip_0x(s: &str) -> &str { - s.strip_prefix("0x") - .or_else(|| s.strip_prefix("0X")) - .unwrap_or(s) -} - pub fn require_0x(s: &str) -> Option<&str> { s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) } +pub fn strip_0x(s: &str) -> &str { + require_0x(s).unwrap_or(s) +} + pub fn is_valid_address(s: &str) -> bool { - require_0x(s).is_some_and(|h| h.len() == 40 && h.chars().all(|c| c.is_ascii_hexdigit())) + require_0x(s).is_some_and(|h| h.len() == 40 && h.bytes().all(|b| b.is_ascii_hexdigit())) } pub fn is_valid_tx_hash(s: &str) -> bool { - require_0x(s).is_some_and(|h| h.len() == 64 && h.chars().all(|c| c.is_ascii_hexdigit())) + require_0x(s).is_some_and(|h| h.len() == 64 && h.bytes().all(|b| b.is_ascii_hexdigit())) } pub fn parse_hex_u256(s: &str) -> Option { @@ -27,6 +25,14 @@ pub fn parse_hex_u256(s: &str) -> Option { mod tests { use super::*; + #[test] + fn test_require_0x() { + assert_eq!(require_0x("0xabc"), Some("abc")); + assert_eq!(require_0x("0Xabc"), Some("abc")); + assert_eq!(require_0x("abc"), None); + assert_eq!(require_0x("0x"), Some("")); + } + #[test] fn test_strip_0x() { assert_eq!(strip_0x("0xabc"), "abc"); @@ -36,14 +42,6 @@ mod tests { assert_eq!(strip_0x("0X"), ""); } - #[test] - fn test_require_0x() { - assert_eq!(require_0x("0xabc"), Some("abc")); - assert_eq!(require_0x("0Xabc"), Some("abc")); - assert_eq!(require_0x("abc"), None); - assert_eq!(require_0x("0x"), Some("")); - } - #[test] fn test_is_valid_address() { assert!(is_valid_address( diff --git a/src/utils/precompiles.rs b/src/utils/precompiles.rs index 6162285..53b8428 100644 --- a/src/utils/precompiles.rs +++ b/src/utils/precompiles.rs @@ -3,35 +3,24 @@ /// Precompiled contracts are at addresses 0x01-0x0a (and potentially higher in newer forks). /// Returns `Some((name, signature))` if the address is a known precompile, `None` otherwise. pub fn get_precompile_info(address: &str) -> Option<(&'static str, &'static str)> { - let addr = super::hex_utils::strip_0x(address); - - // Full 40-char addresses: precompiles have 38+ leading zeros. - // Short-circuit: if the non-zero portion is beyond 0x0a, it's not a precompile. - if addr.len() == 40 && !addr[..24].chars().all(|c| c == '0') { - return None; - } - - let normalized = if addr.len() == 40 { &addr[24..] } else { addr }; - - // Lowercase only the short suffix for matching. - let normalized_lower = normalized.to_lowercase(); - - match normalized_lower.as_str() { - "0000000000000001" | "01" | "1" => { + match super::hex_utils::strip_0x(address) + .trim_start_matches('0') + .to_lowercase() + .as_str() + { + "1" => { // ecrecover takes 128 bytes: hash(32) + v(32) + r(32) + s(32). Some(("ecrecover", "ecrecover(bytes32,uint8,uint256,uint256)")) } - "0000000000000002" | "02" | "2" => Some(("sha256", "sha256(bytes)")), - "0000000000000003" | "03" | "3" => Some(("ripemd160", "ripemd160(bytes)")), - "0000000000000004" | "04" | "4" => Some(("identity", "identity(bytes)")), - "0000000000000005" | "05" | "5" => { - Some(("modexp", "modexp(uint256,uint256,uint256,bytes)")) - } - "0000000000000006" | "06" | "6" => Some(("ecadd", "ecadd(bytes)")), - "0000000000000007" | "07" | "7" => Some(("ecmul", "ecmul(bytes)")), - "0000000000000008" | "08" | "8" => Some(("ecpairing", "ecpairing(bytes)")), - "0000000000000009" | "09" | "9" => Some(("blake2f", "blake2f(bytes)")), - "000000000000000a" | "0a" | "a" => Some(("pointevaluation", "pointevaluation(bytes)")), + "2" => Some(("sha256", "sha256(bytes)")), + "3" => Some(("ripemd160", "ripemd160(bytes)")), + "4" => Some(("identity", "identity(bytes)")), + "5" => Some(("modexp", "modexp(uint256,uint256,uint256,bytes)")), + "6" => Some(("ecadd", "ecadd(bytes)")), + "7" => Some(("ecmul", "ecmul(bytes)")), + "8" => Some(("ecpairing", "ecpairing(bytes)")), + "9" => Some(("blake2f", "blake2f(bytes)")), + "a" => Some(("pointevaluation", "pointevaluation(bytes)")), _ => None, } } diff --git a/src/utils/value_parser.rs b/src/utils/value_parser.rs index 129562b..da7f602 100644 --- a/src/utils/value_parser.rs +++ b/src/utils/value_parser.rs @@ -12,40 +12,36 @@ pub fn parse_value(s: &str) -> Result { if let Some(hex) = hex_utils::require_0x(s) { let v = U256::from_str_radix(hex, 16).map_err(|_| format!("invalid hex value: {s}"))?; - return Ok(format!("0x{v:x}")); + return Ok(format!("{v:#x}")); } if let Some(num_str) = s.strip_suffix("ether") { let wei = to_wei(num_str, 18)?; - return Ok(format!("0x{wei:x}")); + return Ok(format!("{wei:#x}")); } if let Some(num_str) = s.strip_suffix("gwei") { let wei = to_wei(num_str, 9)?; - return Ok(format!("0x{wei:x}")); + return Ok(format!("{wei:#x}")); } if let Some(num_str) = s.strip_suffix("wei") { let v = U256::from_str_radix(num_str, 10).map_err(|_| format!("invalid value: {s}"))?; - return Ok(format!("0x{v:x}")); + return Ok(format!("{v:#x}")); } let v = U256::from_str_radix(s, 10).map_err(|_| format!("invalid value: {s}"))?; - Ok(format!("0x{v:x}")) + Ok(format!("{v:#x}")) } /// Convert a possibly-decimal number string to wei given the unit's decimal count. -fn to_wei(num_str: &str, decimals: u32) -> Result { - let num_str = num_str.trim(); - - let (combined, frac_len) = match num_str.find('.') { - Some(dot) => { - let frac = &num_str[dot + 1..]; - if frac.len() > decimals as usize { +fn to_wei(num_str: &str, decimals: usize) -> Result { + let (combined, frac_len) = match num_str.split_once('.') { + Some((int_part, frac)) => { + if frac.len() > decimals { return Err(format!("too many decimal places: {num_str}")); } - #[allow(clippy::cast_possible_truncation)] // frac.len() <= 18 (validated above) - (format!("{}{frac}", &num_str[..dot]), frac.len() as u32) + (format!("{int_part}{frac}"), frac.len()) } None => (num_str.to_string(), 0), }; @@ -77,11 +73,12 @@ mod tests { #[test] fn test_parse_value_ether() { assert_eq!(parse_value("1ether").unwrap(), "0xde0b6b3a7640000"); + assert_eq!(parse_value("1.ether").unwrap(), "0xde0b6b3a7640000"); assert_eq!(parse_value("0.000000000000000001ether").unwrap(), "0x1"); let result = parse_value("1.010101010101010101ether").unwrap(); let expected = U256::from(1_010_101_010_101_010_101u128); - assert_eq!(result, format!("0x{expected:x}")); + assert_eq!(result, format!("{expected:#x}")); } #[test]