use alloy_primitives::U256;
use serde::{Deserialize, Serialize};
use std::ops::Add;
use super::decimals::TokenDecimals;
use super::normalized::NormalizedAmount;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(transparent)]
pub struct TokenAmount(U256);
impl TokenAmount {
pub const ZERO: Self = Self(U256::ZERO);
pub const fn new(amount: U256) -> Self {
Self(amount)
}
pub const fn as_u256(&self) -> U256 {
self.0
}
pub fn normalize(&self, decimals: TokenDecimals) -> NormalizedAmount {
let amount_str = self.0.to_string();
let amount_f64 = amount_str.parse::<f64>().unwrap_or_else(|e| {
tracing::warn!(
amount = %self.0,
error = %e,
"Failed to parse token amount to f64, using 0.0"
);
0.0
});
let divisor = 10_f64.powi(decimals.as_u8() as i32);
NormalizedAmount::new(amount_f64 / divisor)
}
}
impl From<u64> for TokenAmount {
fn from(value: u64) -> Self {
Self(U256::from(value))
}
}
impl From<U256> for TokenAmount {
fn from(value: U256) -> Self {
Self(value)
}
}
impl Add for TokenAmount {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self(self.0.saturating_add(rhs.0))
}
}
impl std::fmt::Display for TokenAmount {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_token_amount_creation() {
let amount = TokenAmount::new(U256::from(1000u64));
assert_eq!(amount.as_u256(), U256::from(1000u64));
}
#[test]
fn test_token_amount_normalization_eth() {
let raw = TokenAmount::new(U256::from(1_500_000_000_000_000_000u64));
let normalized = raw.normalize(TokenDecimals::STANDARD);
assert!((normalized.as_f64() - 1.5).abs() < 0.0001);
}
#[test]
fn test_token_amount_normalization_usdc() {
let raw = TokenAmount::new(U256::from(100_000_000u64));
let normalized = raw.normalize(TokenDecimals::USDC);
assert_eq!(normalized.as_f64(), 100.0);
}
#[test]
fn test_token_amount_normalization_wbtc() {
let raw = TokenAmount::new(U256::from(50_000_000u64));
let normalized = raw.normalize(TokenDecimals::WBTC);
assert_eq!(normalized.as_f64(), 0.5);
}
#[test]
fn test_token_amount_addition() {
let amt1 = TokenAmount::new(U256::from(1000u64));
let amt2 = TokenAmount::new(U256::from(2000u64));
let total = amt1 + amt2;
assert_eq!(total.as_u256(), U256::from(3000u64));
}
#[test]
fn test_token_amount_zero() {
assert_eq!(TokenAmount::ZERO.as_u256(), U256::ZERO);
}
#[test]
fn test_display_formatting() {
let amount = TokenAmount::new(U256::from(12345u64));
assert_eq!(format!("{}", amount), "12345");
}
#[test]
fn test_serialization() {
let amount = TokenAmount::new(U256::from(12345u64));
let json = serde_json::to_string(&amount).unwrap();
let deserialized: TokenAmount = serde_json::from_str(&json).unwrap();
assert_eq!(amount, deserialized);
}
#[test]
fn test_conversions() {
let u256_val = U256::from(12345u64);
let amount: TokenAmount = u256_val.into();
let back: U256 = amount.as_u256();
assert_eq!(u256_val, back);
}
}