use alloy_primitives::Address;
use crate::price::SwapData;
use crate::{NormalizedAmount, TokenAmount, TokenDecimals, UsdValue};
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct SwapAmounts {
pub token_amount: NormalizedAmount,
pub usdc_amount: UsdValue,
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct NormalizedSwap {
pub token_in_amount: NormalizedAmount,
pub token_out_amount: NormalizedAmount,
pub token_in_decimals: TokenDecimals,
pub token_out_decimals: TokenDecimals,
}
pub(crate) fn involves_pair(swap: &SwapData, target_token: Address, usdc: Address) -> bool {
(swap.token_in == target_token && swap.token_out == usdc)
|| (swap.token_in == usdc && swap.token_out == target_token)
}
pub(crate) fn normalize_against_pair(
swap: &SwapData,
target_token: Address,
usdc: Address,
target_decimals: TokenDecimals,
usdc_decimals: TokenDecimals,
) -> Option<SwapAmounts> {
if swap.token_in == target_token && swap.token_out == usdc {
let token_amount = TokenAmount::new(swap.token_in_amount).normalize(target_decimals);
let usdc_amount = TokenAmount::new(swap.token_out_amount).normalize(usdc_decimals);
return Some(SwapAmounts {
token_amount,
usdc_amount: UsdValue::new(usdc_amount.as_f64()),
});
}
if swap.token_in == usdc && swap.token_out == target_token {
let token_amount = TokenAmount::new(swap.token_out_amount).normalize(target_decimals);
let usdc_amount = TokenAmount::new(swap.token_in_amount).normalize(usdc_decimals);
return Some(SwapAmounts {
token_amount,
usdc_amount: UsdValue::new(usdc_amount.as_f64()),
});
}
None
}
pub(crate) fn normalize_swap(
swap: &SwapData,
token_in_decimals: TokenDecimals,
token_out_decimals: TokenDecimals,
) -> NormalizedSwap {
NormalizedSwap {
token_in_amount: TokenAmount::new(swap.token_in_amount).normalize(token_in_decimals),
token_out_amount: TokenAmount::new(swap.token_out_amount).normalize(token_out_decimals),
token_in_decimals,
token_out_decimals,
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::{address, U256};
fn token() -> Address {
address!("1111111111111111111111111111111111111111")
}
fn usdc() -> Address {
address!("2222222222222222222222222222222222222222")
}
fn other() -> Address {
address!("3333333333333333333333333333333333333333")
}
fn swap(token_in: Address, in_amt: u128, token_out: Address, out_amt: u128) -> SwapData {
SwapData {
token_in,
token_in_amount: U256::from(in_amt),
token_out,
token_out_amount: U256::from(out_amt),
sender: None,
tx_hash: None,
block_number: None,
}
}
#[test]
fn pair_normalises_sell_direction() {
let s = swap(
token(),
1_500_000_000_000_000_000u128,
usdc(),
3_000_000u128,
);
let amounts = normalize_against_pair(
&s,
token(),
usdc(),
TokenDecimals::new(18),
TokenDecimals::new(6),
)
.expect("pair-relevant swap normalises");
assert!((amounts.token_amount.as_f64() - 1.5).abs() < 1e-9);
assert!((amounts.usdc_amount.as_f64() - 3.0).abs() < 1e-9);
}
#[test]
fn pair_normalises_buy_direction() {
let s = swap(usdc(), 2_000_000u128, token(), 500_000_000_000_000_000u128);
let amounts = normalize_against_pair(
&s,
token(),
usdc(),
TokenDecimals::new(18),
TokenDecimals::new(6),
)
.expect("reverse-direction swap normalises");
assert!((amounts.token_amount.as_f64() - 0.5).abs() < 1e-9);
assert!((amounts.usdc_amount.as_f64() - 2.0).abs() < 1e-9);
}
#[test]
fn pair_returns_none_for_irrelevant_swap() {
let s = swap(token(), 1, other(), 1);
assert!(normalize_against_pair(
&s,
token(),
usdc(),
TokenDecimals::new(18),
TokenDecimals::new(6),
)
.is_none());
let s2 = swap(other(), 1, other(), 1);
assert!(normalize_against_pair(
&s2,
token(),
usdc(),
TokenDecimals::new(18),
TokenDecimals::new(6),
)
.is_none());
}
#[test]
fn involves_pair_matches_both_directions_only() {
let sell = swap(token(), 1, usdc(), 1);
let buy = swap(usdc(), 1, token(), 1);
let unrelated = swap(token(), 1, other(), 1);
assert!(involves_pair(&sell, token(), usdc()));
assert!(involves_pair(&buy, token(), usdc()));
assert!(!involves_pair(&unrelated, token(), usdc()));
}
#[test]
fn normalize_swap_normalises_both_legs() {
let s = swap(
token(),
1_000_000_000_000_000_000u128,
usdc(),
2_000_000u128,
);
let n = normalize_swap(&s, TokenDecimals::new(18), TokenDecimals::new(6));
assert!((n.token_in_amount.as_f64() - 1.0).abs() < 1e-9);
assert!((n.token_out_amount.as_f64() - 2.0).abs() < 1e-9);
assert_eq!(n.token_in_decimals, TokenDecimals::new(18));
assert_eq!(n.token_out_decimals, TokenDecimals::new(6));
}
}