use clarity::Address;
use super::uniswapv3::{
DAI_CONTRACT_ADDRESS, USDC_CONTRACT_ADDRESS, USDT_CONTRACT_ADDRESS, WETH_CONTRACT_ADDRESS,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SwapHop {
pub token: Address,
pub fee: u32,
}
impl SwapHop {
pub fn new(token: Address, fee: u32) -> Self {
Self { token, fee }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SwapRoute {
pub token_in: Address,
pub hops: Vec<SwapHop>,
}
impl SwapRoute {
pub fn hop_count(&self) -> usize {
self.hops.len()
}
pub fn token_out(&self) -> Option<Address> {
self.hops.last().map(|h| h.token)
}
pub fn to_tokens_and_fees(&self) -> (Vec<Address>, Vec<u32>) {
let mut tokens = Vec::with_capacity(self.hops.len() + 1);
let mut fees = Vec::with_capacity(self.hops.len());
tokens.push(self.token_in);
for hop in &self.hops {
fees.push(hop.fee);
tokens.push(hop.token);
}
(tokens, fees)
}
pub fn to_path(&self) -> Vec<SwapHop> {
self.hops.clone()
}
pub fn from_tokens_and_fees(tokens: &[Address], fees: &[u32]) -> Option<Self> {
if tokens.len() < 2 || tokens.len() != fees.len() + 1 {
return None;
}
let token_in = tokens[0];
let hops: Vec<SwapHop> = tokens[1..]
.iter()
.zip(fees.iter())
.map(|(&token, &fee)| SwapHop::new(token, fee))
.collect();
Some(Self { token_in, hops })
}
}
pub fn get_common_intermediary_tokens() -> Vec<Address> {
vec![
*WETH_CONTRACT_ADDRESS,
*USDC_CONTRACT_ADDRESS,
*USDT_CONTRACT_ADDRESS,
*DAI_CONTRACT_ADDRESS,
]
}
pub fn get_standard_fees() -> Vec<u32> {
vec![3000, 500, 100, 10000]
}
pub fn generate_potential_routes(token_in: Address, token_out: Address) -> Vec<SwapRoute> {
generate_routes_by_hop_count(token_in, token_out).concat()
}
pub fn generate_routes_by_hop_count(token_in: Address, token_out: Address) -> [Vec<SwapRoute>; 3] {
let intermediaries = get_common_intermediary_tokens();
let fees = get_standard_fees();
let mut direct_routes = Vec::new();
let mut one_hop_routes = Vec::new();
let mut two_hop_routes = Vec::new();
for &fee in &fees {
direct_routes.push(SwapRoute {
token_in,
hops: vec![SwapHop::new(token_out, fee)],
});
}
for &intermediary in &intermediaries {
if intermediary == token_in || intermediary == token_out {
continue;
}
for &fee1 in &fees {
for &fee2 in &fees {
one_hop_routes.push(SwapRoute {
token_in,
hops: vec![
SwapHop::new(intermediary, fee1),
SwapHop::new(token_out, fee2),
],
});
}
}
}
for (i, &intermediary_a) in intermediaries.iter().enumerate() {
if intermediary_a == token_in || intermediary_a == token_out {
continue;
}
for (j, &intermediary_b) in intermediaries.iter().enumerate() {
if i == j || intermediary_b == token_in || intermediary_b == token_out {
continue;
}
for &fee1 in &fees {
for &fee2 in &fees {
for &fee3 in &fees {
two_hop_routes.push(SwapRoute {
token_in,
hops: vec![
SwapHop::new(intermediary_a, fee1),
SwapHop::new(intermediary_b, fee2),
SwapHop::new(token_out, fee3),
],
});
}
}
}
}
}
[direct_routes, one_hop_routes, two_hop_routes]
}
#[cfg(test)]
mod tests {
use super::*;
fn test_token_a() -> Address {
Address::parse_and_validate("0x1111111111111111111111111111111111111111").unwrap()
}
fn test_token_b() -> Address {
Address::parse_and_validate("0x2222222222222222222222222222222222222222").unwrap()
}
#[test]
fn test_swap_hop_creation() {
let token = test_token_a();
let hop = SwapHop::new(token, 3000);
assert_eq!(hop.token, token);
assert_eq!(hop.fee, 3000);
}
#[test]
fn test_swap_route_hop_count() {
let route = SwapRoute {
token_in: test_token_a(),
hops: vec![SwapHop::new(test_token_b(), 3000)],
};
assert_eq!(route.hop_count(), 1);
let route_with_intermediary = SwapRoute {
token_in: test_token_a(),
hops: vec![
SwapHop::new(*WETH_CONTRACT_ADDRESS, 3000),
SwapHop::new(test_token_b(), 500),
],
};
assert_eq!(route_with_intermediary.hop_count(), 2);
}
#[test]
fn test_swap_route_token_out() {
let route = SwapRoute {
token_in: test_token_a(),
hops: vec![
SwapHop::new(*WETH_CONTRACT_ADDRESS, 3000),
SwapHop::new(test_token_b(), 500),
],
};
assert_eq!(route.token_out(), Some(test_token_b()));
}
#[test]
fn test_swap_route_to_tokens_and_fees() {
let route = SwapRoute {
token_in: test_token_a(),
hops: vec![
SwapHop::new(*WETH_CONTRACT_ADDRESS, 3000),
SwapHop::new(test_token_b(), 500),
],
};
let (tokens, fees) = route.to_tokens_and_fees();
assert_eq!(tokens.len(), 3);
assert_eq!(tokens[0], test_token_a());
assert_eq!(tokens[1], *WETH_CONTRACT_ADDRESS);
assert_eq!(tokens[2], test_token_b());
assert_eq!(fees, vec![3000, 500]);
}
#[test]
fn test_swap_route_from_tokens_and_fees() {
let tokens = vec![test_token_a(), *WETH_CONTRACT_ADDRESS, test_token_b()];
let fees = vec![3000, 500];
let route = SwapRoute::from_tokens_and_fees(&tokens, &fees).unwrap();
assert_eq!(route.token_in, test_token_a());
assert_eq!(route.hops.len(), 2);
assert_eq!(route.hops[0].token, *WETH_CONTRACT_ADDRESS);
assert_eq!(route.hops[0].fee, 3000);
assert_eq!(route.hops[1].token, test_token_b());
assert_eq!(route.hops[1].fee, 500);
}
#[test]
fn test_swap_route_roundtrip() {
let original = SwapRoute {
token_in: test_token_a(),
hops: vec![
SwapHop::new(*WETH_CONTRACT_ADDRESS, 3000),
SwapHop::new(*USDC_CONTRACT_ADDRESS, 500),
SwapHop::new(test_token_b(), 100),
],
};
let (tokens, fees) = original.to_tokens_and_fees();
let reconstructed = SwapRoute::from_tokens_and_fees(&tokens, &fees).unwrap();
assert_eq!(original, reconstructed);
}
#[test]
fn test_from_tokens_and_fees_invalid_lengths() {
assert!(SwapRoute::from_tokens_and_fees(&[test_token_a()], &[]).is_none());
assert!(
SwapRoute::from_tokens_and_fees(&[test_token_a(), test_token_b()], &[3000, 500])
.is_none()
);
}
#[test]
fn test_generate_potential_routes_direct() {
let routes = generate_potential_routes(test_token_a(), test_token_b());
let direct_routes: Vec<_> = routes.iter().filter(|r| r.hop_count() == 1).collect();
assert_eq!(direct_routes.len(), 4);
for route in direct_routes {
assert_eq!(route.token_in, test_token_a());
assert_eq!(route.token_out(), Some(test_token_b()));
}
}
#[test]
fn test_generate_potential_routes_one_hop() {
let routes = generate_potential_routes(test_token_a(), test_token_b());
let one_hop_routes: Vec<_> = routes.iter().filter(|r| r.hop_count() == 2).collect();
assert_eq!(one_hop_routes.len(), 64);
for route in &one_hop_routes {
assert_eq!(route.token_in, test_token_a());
assert_eq!(route.token_out(), Some(test_token_b()));
assert_ne!(route.hops[0].token, test_token_b());
let intermediaries = get_common_intermediary_tokens();
assert!(intermediaries.contains(&route.hops[0].token));
}
}
#[test]
fn test_generate_potential_routes_two_hops() {
let routes = generate_potential_routes(test_token_a(), test_token_b());
let two_hop_routes: Vec<_> = routes.iter().filter(|r| r.hop_count() == 3).collect();
assert!(!two_hop_routes.is_empty());
for route in &two_hop_routes {
assert_eq!(route.token_in, test_token_a());
assert_eq!(route.token_out(), Some(test_token_b()));
assert_ne!(route.hops[0].token, test_token_b());
assert_ne!(route.hops[1].token, test_token_b());
assert_ne!(route.hops[0].token, route.hops[1].token);
}
}
#[test]
fn test_generate_routes_by_hop_count() {
let [direct, one_hop, two_hop] =
generate_routes_by_hop_count(test_token_a(), test_token_b());
assert_eq!(direct.len(), 4); assert!(!one_hop.is_empty());
assert!(!two_hop.is_empty());
for route in &direct {
assert_eq!(route.hop_count(), 1);
}
for route in &one_hop {
assert_eq!(route.hop_count(), 2);
}
for route in &two_hop {
assert_eq!(route.hop_count(), 3);
}
}
#[test]
fn test_routes_exclude_input_output_as_intermediary() {
let routes = generate_potential_routes(*WETH_CONTRACT_ADDRESS, test_token_b());
for route in &routes {
for hop in &route.hops {
if hop.token != test_token_b() {
assert_ne!(hop.token, *WETH_CONTRACT_ADDRESS);
}
}
}
}
#[test]
fn test_routes_contain_valid_fee_tiers() {
let routes = generate_potential_routes(test_token_a(), test_token_b());
let valid_fees = get_standard_fees();
for route in &routes {
for hop in &route.hops {
assert!(
valid_fees.contains(&hop.fee),
"Invalid fee tier: {}",
hop.fee
);
}
}
}
#[test]
fn test_routes_paths_are_valid() {
let routes = generate_potential_routes(test_token_a(), test_token_b());
for route in &routes {
let (tokens, fees) = route.to_tokens_and_fees();
assert_eq!(
tokens.len(),
fees.len() + 1,
"Invalid path: tokens.len() ({}) != fees.len() ({}) + 1",
tokens.len(),
fees.len()
);
assert_eq!(tokens[0], test_token_a());
assert_eq!(tokens[tokens.len() - 1], test_token_b());
for i in 0..tokens.len() - 1 {
assert_ne!(
tokens[i],
tokens[i + 1],
"Consecutive duplicate tokens at index {}: {:?}",
i,
tokens[i]
);
}
}
}
}