use std::fmt::{Display, Formatter};
use std::str::FromStr;
use alloy_primitives::{Address, U256, hex};
use r402::amount::{MoneyAmount, MoneyAmountParseError, ScaleFromMantissa};
use r402::chain::{ChainId, DeployedTokenAmount};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ChecksummedAddress(pub Address);
impl FromStr for ChecksummedAddress {
type Err = hex::FromHexError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let address = Address::from_str(s)?;
Ok(Self(address))
}
}
impl Display for ChecksummedAddress {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.to_checksum(None))
}
}
impl Serialize for ChecksummedAddress {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.0.to_checksum(None))
}
}
impl<'de> Deserialize<'de> for ChecksummedAddress {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
s.parse().map_err(serde::de::Error::custom)
}
}
impl From<ChecksummedAddress> for Address {
fn from(value: ChecksummedAddress) -> Self {
value.0
}
}
impl From<Address> for ChecksummedAddress {
fn from(address: Address) -> Self {
Self(address)
}
}
impl PartialEq<ChecksummedAddress> for Address {
fn eq(&self, other: &ChecksummedAddress) -> bool {
self.eq(&other.0)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct TokenAmount(pub U256);
impl FromStr for TokenAmount {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let u256 = U256::from_str_radix(s, 10).map_err(|_| "invalid token amount".to_owned())?;
Ok(Self(u256))
}
}
impl Serialize for TokenAmount {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.0.to_string())
}
}
impl<'de> Deserialize<'de> for TokenAmount {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Self::from_str(&s).map_err(serde::de::Error::custom)
}
}
impl From<TokenAmount> for U256 {
fn from(value: TokenAmount) -> Self {
value.0
}
}
impl From<U256> for TokenAmount {
fn from(value: U256) -> Self {
Self(value)
}
}
impl From<u128> for TokenAmount {
fn from(value: u128) -> Self {
Self(U256::from(value))
}
}
impl From<u64> for TokenAmount {
fn from(value: u64) -> Self {
Self(U256::from(value))
}
}
pub const EIP155_NAMESPACE: &str = "eip155";
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Eip155ChainReference(u64);
impl Eip155ChainReference {
#[must_use]
pub const fn new(chain_id: u64) -> Self {
Self(chain_id)
}
#[must_use]
pub const fn inner(self) -> u64 {
self.0
}
#[must_use]
pub fn as_chain_id(self) -> ChainId {
ChainId::new(EIP155_NAMESPACE, self.0.to_string())
}
}
impl From<Eip155ChainReference> for ChainId {
fn from(value: Eip155ChainReference) -> Self {
Self::new(EIP155_NAMESPACE, value.0.to_string())
}
}
impl From<&Eip155ChainReference> for ChainId {
fn from(value: &Eip155ChainReference) -> Self {
Self::new(EIP155_NAMESPACE, value.0.to_string())
}
}
impl TryFrom<ChainId> for Eip155ChainReference {
type Error = Eip155ChainReferenceFormatError;
fn try_from(value: ChainId) -> Result<Self, Self::Error> {
Self::try_from(&value)
}
}
impl TryFrom<&ChainId> for Eip155ChainReference {
type Error = Eip155ChainReferenceFormatError;
fn try_from(value: &ChainId) -> Result<Self, Self::Error> {
if value.namespace() != EIP155_NAMESPACE {
return Err(Eip155ChainReferenceFormatError::InvalidNamespace(
value.namespace().to_owned(),
));
}
let chain_id: u64 = value.reference().parse().map_err(|_| {
Eip155ChainReferenceFormatError::InvalidReference(value.reference().to_owned())
})?;
Ok(Self(chain_id))
}
}
#[derive(Debug, thiserror::Error)]
pub enum Eip155ChainReferenceFormatError {
#[error("Invalid namespace {0}, expected eip155")]
InvalidNamespace(String),
#[error("Invalid eip155 chain reference {0}")]
InvalidReference(String),
}
impl Display for Eip155ChainReference {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Eip155TokenDeployment {
pub chain_reference: Eip155ChainReference,
pub address: Address,
pub decimals: u8,
pub eip712: Option<TokenDeploymentEip712>,
}
impl Eip155TokenDeployment {
pub fn amount<V: Into<TokenAmount>>(&self, v: V) -> DeployedTokenAmount<U256, Self> {
DeployedTokenAmount {
amount: v.into().0,
token: self.clone(),
}
}
pub fn parse<V>(&self, v: V) -> Result<DeployedTokenAmount<U256, Self>, MoneyAmountParseError>
where
V: TryInto<MoneyAmount>,
MoneyAmountParseError: From<<V as TryInto<MoneyAmount>>::Error>,
{
let TokenAmount(amount) = v.try_into()?.to_token_amount(self.decimals)?;
Ok(DeployedTokenAmount {
amount,
token: self.clone(),
})
}
}
impl ScaleFromMantissa for TokenAmount {
fn from_mantissa_scaled(
mantissa: u128,
scale_diff: u32,
) -> Result<Self, MoneyAmountParseError> {
let multiplier = U256::from(10)
.checked_pow(U256::from(scale_diff))
.ok_or(MoneyAmountParseError::OutOfRange)?;
let value = U256::from(mantissa)
.checked_mul(multiplier)
.ok_or(MoneyAmountParseError::OutOfRange)?;
Ok(Self(value))
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct TokenDeploymentEip712 {
pub name: String,
pub version: String,
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_deployment(decimals: u8) -> Eip155TokenDeployment {
let chain_ref = Eip155ChainReference::new(1); Eip155TokenDeployment {
chain_reference: chain_ref,
address: Address::ZERO,
decimals,
eip712: None,
}
}
#[test]
fn test_parse_whole_number() {
let deployment = create_test_deployment(6); let result = deployment.parse("100");
assert!(result.is_ok());
assert_eq!(result.unwrap().amount, U256::from(100_000_000u64)); }
#[test]
fn test_parse_with_decimals() {
let deployment = create_test_deployment(6);
let result = deployment.parse("1.50");
assert!(result.is_ok());
assert_eq!(result.unwrap().amount, U256::from(1_500_000u64)); }
#[test]
fn test_parse_zero_decimals() {
let deployment = create_test_deployment(0);
let result = deployment.parse("42");
assert!(result.is_ok());
assert_eq!(result.unwrap().amount, U256::from(42u64));
}
#[test]
fn test_parse_precision_too_high() {
let deployment = create_test_deployment(2); let result = deployment.parse("1.234"); assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(err, MoneyAmountParseError::WrongPrecision { .. }));
}
#[test]
fn test_parse_exact_precision() {
let deployment = create_test_deployment(9); let result = deployment.parse("0.123456789");
assert!(result.is_ok());
assert_eq!(result.unwrap().amount, U256::from(123_456_789u64));
}
#[test]
fn test_parse_smallest_amount() {
let deployment = create_test_deployment(6);
let result = deployment.parse("0.000001");
assert!(result.is_ok());
assert_eq!(result.unwrap().amount, U256::from(1u64));
}
#[test]
fn test_parse_with_currency_symbol() {
let deployment = create_test_deployment(6);
let result = deployment.parse("$10.50");
assert!(result.is_ok());
assert_eq!(result.unwrap().amount, U256::from(10_500_000u64));
}
#[test]
fn test_parse_with_commas() {
let deployment = create_test_deployment(6);
let result = deployment.parse("1,000");
assert!(result.is_ok());
assert_eq!(result.unwrap().amount, U256::from(1_000_000_000u64));
}
#[test]
fn test_parse_large_amount() {
let deployment = create_test_deployment(6);
let result = deployment.parse("999999999");
assert!(result.is_ok());
assert_eq!(result.unwrap().amount, U256::from(999_999_999_000_000u64));
}
#[test]
fn test_parse_very_large_amount_with_high_decimals() {
let deployment = create_test_deployment(18); let result = deployment.parse("999999999"); assert!(result.is_ok());
let expected = U256::from(999_999_999u64) * U256::from(10).pow(U256::from(18));
assert_eq!(result.unwrap().amount, expected);
}
}