use std::str::FromStr;
use eyre::{Result, bail};
use revm::primitives::U256;
#[derive(Debug, Clone, Copy)]
pub enum EthUnit {
Wei,
Kwei, Mwei, Gwei, Szabo, Finney, Ether,
Kether, Mether,
Gether,
Tether,
}
impl FromStr for EthUnit {
type Err = eyre::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"wei" => Ok(EthUnit::Wei),
"kwei" | "babbage" | "femtoether" => Ok(EthUnit::Kwei),
"mwei" | "lovelace" | "picoether" => Ok(EthUnit::Mwei),
"gwei" | "shannon" | "nanoether" | "nano" => Ok(EthUnit::Gwei),
"szabo" | "microether" | "micro" => Ok(EthUnit::Szabo),
"finney" | "milliether" | "milli" => Ok(EthUnit::Finney),
"ether" | "eth" => Ok(EthUnit::Ether),
"kether" | "grand" => Ok(EthUnit::Kether),
"mether" => Ok(EthUnit::Mether),
"gether" => Ok(EthUnit::Gether),
"tether" => Ok(EthUnit::Tether),
_ => bail!("Unknown unit: {}", s),
}
}
}
impl EthUnit {
pub fn multiplier(&self) -> U256 {
match self {
EthUnit::Wei => U256::from(1),
EthUnit::Kwei => U256::from(10).pow(U256::from(3)),
EthUnit::Mwei => U256::from(10).pow(U256::from(6)),
EthUnit::Gwei => U256::from(10).pow(U256::from(9)),
EthUnit::Szabo => U256::from(10).pow(U256::from(12)),
EthUnit::Finney => U256::from(10).pow(U256::from(15)),
EthUnit::Ether => U256::from(10).pow(U256::from(18)),
EthUnit::Kether => U256::from(10).pow(U256::from(21)),
EthUnit::Mether => U256::from(10).pow(U256::from(24)),
EthUnit::Gether => U256::from(10).pow(U256::from(27)),
EthUnit::Tether => U256::from(10).pow(U256::from(30)),
}
}
}
#[allow(clippy::result_large_err)]
pub fn parse_eth_value(input: &str) -> Result<U256> {
if input.chars().all(|c| c.is_ascii_digit() || c == '.') {
return parse_decimal_value(input, EthUnit::Wei);
}
let mut numeric_part = String::new();
let mut unit_part = String::new();
let mut in_unit_part = false;
for c in input.chars() {
if !in_unit_part && (c.is_ascii_digit() || c == '.') {
numeric_part.push(c);
} else {
in_unit_part = true;
unit_part.push(c);
}
}
if numeric_part.is_empty() || unit_part.is_empty() {
bail!("Invalid format: expected '<number><unit>', got '{}'", input)
}
let unit = EthUnit::from_str(&unit_part)?;
parse_decimal_value(&numeric_part, unit)
}
fn parse_decimal_value(value_str: &str, unit: EthUnit) -> Result<U256> {
if !value_str.contains('.') {
let value: U256 = value_str.parse()?;
return Ok(value * unit.multiplier());
}
let parts: Vec<&str> = value_str.split('.').collect();
if parts.len() != 2 {
bail!("Invalid decimal format in '{}'", value_str)
}
let whole_part: U256 = if parts[0].is_empty() {
U256::from(0)
} else {
parts[0].parse()?
};
let decimal_str = parts[1];
if !decimal_str.is_empty() {
let max_decimal_len = 77; let limited_decimal = if decimal_str.len() > max_decimal_len {
&decimal_str[0..max_decimal_len]
} else {
decimal_str
};
let decimal_part: U256 = limited_decimal.parse()?;
let decimal_scale = U256::from(10).pow(U256::from(limited_decimal.len()));
let whole_in_wei = whole_part * unit.multiplier();
let decimal_in_wei = decimal_part * unit.multiplier() / decimal_scale;
return Ok(whole_in_wei + decimal_in_wei);
}
Ok(whole_part * unit.multiplier())
}
pub fn u256_from_f64_lossy(value: f64) -> U256 {
let value_string = format!("{value:.0}");
value_string
.parse::<U256>()
.unwrap_or_else(|_| U256::from(value as u64))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_eth_value() {
assert_eq!(
parse_eth_value("100").unwrap(),
U256::from(100),
"Should parse raw integer as wei"
);
assert_eq!(
parse_eth_value("1kwei").unwrap(),
U256::from(10).pow(U256::from(3)),
"Should convert 1 kwei to wei correctly"
);
assert_eq!(
parse_eth_value("1mwei").unwrap(),
U256::from(10).pow(U256::from(6)),
"Should convert 1 mwei to wei correctly"
);
assert_eq!(
parse_eth_value("5gwei").unwrap(),
U256::from(5) * U256::from(10).pow(U256::from(9)),
"Should convert 5 gwei to wei correctly"
);
assert_eq!(
parse_eth_value("1szabo").unwrap(),
U256::from(10).pow(U256::from(12)),
"Should convert 1 szabo to wei correctly"
);
assert_eq!(
parse_eth_value("1finney").unwrap(),
U256::from(10).pow(U256::from(15)),
"Should convert 1 finney to wei correctly"
);
assert_eq!(
parse_eth_value("1ether").unwrap(),
U256::from(10).pow(U256::from(18)),
"Should convert 1 ether to wei correctly"
);
assert_eq!(
parse_eth_value("0.5ether").unwrap(),
U256::from(10).pow(U256::from(18)) / U256::from(2),
"Should convert 0.5 ether to wei correctly"
);
assert_eq!(
parse_eth_value("1kether").unwrap(),
U256::from(10).pow(U256::from(21)),
"Should convert 1 kether to wei correctly"
);
assert_eq!(
parse_eth_value("1mether").unwrap(),
U256::from(10).pow(U256::from(24)),
"Should convert 1 mether to wei correctly"
);
assert_eq!(
parse_eth_value("1gether").unwrap(),
U256::from(10).pow(U256::from(27)),
"Should convert 1 gether to wei correctly"
);
assert_eq!(
parse_eth_value("1tether").unwrap(),
U256::from(10).pow(U256::from(30)),
"Should convert 1 tether to wei correctly"
);
}
#[test]
fn test_unit_aliases() {
assert_eq!(
parse_eth_value("1babbage").unwrap(),
parse_eth_value("1kwei").unwrap()
);
assert_eq!(
parse_eth_value("1femtoether").unwrap(),
parse_eth_value("1kwei").unwrap()
);
assert_eq!(
parse_eth_value("1lovelace").unwrap(),
parse_eth_value("1mwei").unwrap()
);
assert_eq!(
parse_eth_value("1picoether").unwrap(),
parse_eth_value("1mwei").unwrap()
);
assert_eq!(
parse_eth_value("1shannon").unwrap(),
parse_eth_value("1gwei").unwrap()
);
assert_eq!(
parse_eth_value("1nanoether").unwrap(),
parse_eth_value("1gwei").unwrap()
);
assert_eq!(
parse_eth_value("1nano").unwrap(),
parse_eth_value("1gwei").unwrap()
);
assert_eq!(
parse_eth_value("1microether").unwrap(),
parse_eth_value("1szabo").unwrap()
);
assert_eq!(
parse_eth_value("1micro").unwrap(),
parse_eth_value("1szabo").unwrap()
);
assert_eq!(
parse_eth_value("1milliether").unwrap(),
parse_eth_value("1finney").unwrap()
);
assert_eq!(
parse_eth_value("1milli").unwrap(),
parse_eth_value("1finney").unwrap()
);
assert_eq!(
parse_eth_value("1grand").unwrap(),
parse_eth_value("1kether").unwrap()
);
}
}