use crate::error::{Error, Result};
use rust_decimal::prelude::ToPrimitive;
use rust_decimal::Decimal;
pub fn wei_to_ether(wei: u128) -> Decimal {
Decimal::from(wei) / Decimal::from(1_000_000_000_000_000_000u128)
}
pub fn ether_to_wei(ether: Decimal) -> u128 {
(ether * Decimal::from(1_000_000_000_000_000_000u128))
.to_u128()
.unwrap_or(0)
}
pub fn gwei_to_wei(gwei: Decimal) -> u128 {
(gwei * Decimal::from(1_000_000_000u128))
.to_u128()
.unwrap_or(0)
}
pub fn wei_to_gwei(wei: u128) -> Decimal {
Decimal::from(wei) / Decimal::from(1_000_000_000u128)
}
pub fn parse_token_amount(amount: &str, _decimals: u8) -> Result<u128> {
amount
.parse()
.map_err(|_| Error::generic(format!("Invalid token amount: {}", amount)))
}
pub fn format_token_amount(amount: u128, decimals: u8) -> String {
let divisor = 10u128.pow(decimals as u32);
let whole = amount / divisor;
let fractional = amount % divisor;
if fractional == 0 {
whole.to_string()
} else {
format!("{}.{:0width$}", whole, fractional, width = decimals as usize)
}
}
pub fn token_to_raw(amount: Decimal, decimals: u8) -> u128 {
let multiplier = 10u128.pow(decimals as u32);
(amount * Decimal::from(multiplier)).to_u128().unwrap_or(0)
}
pub fn raw_to_token(raw_amount: u128, decimals: u8) -> Decimal {
let divisor = 10u128.pow(decimals as u32);
Decimal::from(raw_amount) / Decimal::from(divisor)
}
pub fn amounts_match(expected: Decimal, actual: Decimal, tolerance_percent: Decimal) -> bool {
if expected == Decimal::ZERO {
return actual == Decimal::ZERO;
}
let diff = if actual > expected {
actual - expected
} else {
expected - actual
};
let tolerance_amount = expected * tolerance_percent / Decimal::from(100);
diff <= tolerance_amount
}
pub fn amount_sufficient(expected: Decimal, actual: Decimal, min_percent: Decimal) -> bool {
let min_required = expected * min_percent / Decimal::from(100);
actual >= min_required
}
pub fn is_valid_address(address: &str) -> bool {
if !address.starts_with("0x") {
return false;
}
if address.len() != 42 {
return false;
}
address[2..].chars().all(|c| c.is_ascii_hexdigit())
}
pub fn is_valid_tx_hash(hash: &str) -> bool {
if !hash.starts_with("0x") {
return false;
}
if hash.len() != 66 {
return false;
}
hash[2..].chars().all(|c| c.is_ascii_hexdigit())
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
#[test]
fn test_wei_ether_conversion() {
let wei = 1_000_000_000_000_000_000u128;
let ether = wei_to_ether(wei);
assert_eq!(ether, Decimal::from(1));
let wei_back = ether_to_wei(ether);
assert_eq!(wei_back, wei);
}
#[test]
fn test_token_conversions() {
let raw = token_to_raw(Decimal::from(100), 18);
let back = raw_to_token(raw, 18);
assert_eq!(back, Decimal::from(100));
}
#[test]
fn test_amounts_match() {
let expected = Decimal::from(100);
let actual = Decimal::from_str("100.5").unwrap();
let tolerance = Decimal::from(1);
assert!(amounts_match(expected, actual, tolerance));
let actual_far = Decimal::from(110);
assert!(!amounts_match(expected, actual_far, tolerance));
}
#[test]
fn test_amount_sufficient() {
let expected = Decimal::from(100);
let actual = Decimal::from(99); let min_percent = Decimal::from(95);
assert!(amount_sufficient(expected, actual, min_percent));
let actual_low = Decimal::from(90); assert!(!amount_sufficient(expected, actual_low, min_percent));
}
#[test]
fn test_address_validation() {
assert!(is_valid_address(
"0x1234567890123456789012345678901234567890"
));
assert!(!is_valid_address("1234567890123456789012345678901234567890")); assert!(!is_valid_address("0x123")); assert!(!is_valid_address(
"0xGGGG567890123456789012345678901234567890"
)); }
#[test]
fn test_tx_hash_validation() {
assert!(is_valid_tx_hash(
"0x1234567890123456789012345678901234567890123456789012345678901234"
));
assert!(!is_valid_tx_hash(
"1234567890123456789012345678901234567890123456789012345678901234"
)); assert!(!is_valid_tx_hash("0x123")); }
}