use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::collections::{BinaryHeap, HashSet};
pub use super::atomic_swaps::Chain;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LiquidityPool {
pub id: String,
pub chain: Chain,
pub protocol: String,
pub token_a: String,
pub token_b: String,
pub reserve_a: Decimal,
pub reserve_b: Decimal,
pub fee: Decimal,
pub last_updated: DateTime<Utc>,
}
impl LiquidityPool {
pub fn calculate_output(&self, input_token: &str, input_amount: Decimal) -> Option<Decimal> {
let (reserve_in, reserve_out) = if input_token == self.token_a {
(self.reserve_a, self.reserve_b)
} else if input_token == self.token_b {
(self.reserve_b, self.reserve_a)
} else {
return None;
};
if reserve_in == Decimal::ZERO || reserve_out == Decimal::ZERO {
return None;
}
let fee_multiplier = Decimal::ONE - self.fee;
let amount_with_fee = input_amount * fee_multiplier;
let numerator = reserve_out * amount_with_fee;
let denominator = reserve_in + amount_with_fee;
if denominator == Decimal::ZERO {
return None;
}
Some(numerator / denominator)
}
pub fn calculate_price_impact(
&self,
input_token: &str,
input_amount: Decimal,
) -> Option<Decimal> {
let output = self.calculate_output(input_token, input_amount)?;
let (reserve_in, reserve_out) = if input_token == self.token_a {
(self.reserve_a, self.reserve_b)
} else {
(self.reserve_b, self.reserve_a)
};
let spot_price_before = reserve_out / reserve_in;
let effective_price = output / input_amount;
let impact = (spot_price_before - effective_price) / spot_price_before;
Some(impact)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Bridge {
pub id: String,
pub from_chain: Chain,
pub to_chain: Chain,
pub supported_tokens: Vec<String>,
pub fee: Decimal,
pub estimated_time_seconds: u64,
pub gas_cost: Decimal,
}
impl Bridge {
pub fn calculate_output(&self, amount: Decimal) -> Decimal {
amount * (Decimal::ONE - self.fee)
}
pub fn supports_token(&self, token: &str) -> bool {
self.supported_tokens.iter().any(|t| t == token)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RouteSegment {
Swap {
pool: LiquidityPool,
input_token: String,
output_token: String,
input_amount: Decimal,
output_amount: Decimal,
},
Bridge {
bridge: Bridge,
token: String,
input_amount: Decimal,
output_amount: Decimal,
},
}
impl RouteSegment {
pub fn output_amount(&self) -> Decimal {
match self {
RouteSegment::Swap { output_amount, .. } => *output_amount,
RouteSegment::Bridge { output_amount, .. } => *output_amount,
}
}
pub fn gas_cost(&self) -> Decimal {
match self {
RouteSegment::Swap { .. } => Decimal::from(5), RouteSegment::Bridge { bridge, .. } => bridge.gas_cost,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Route {
pub segments: Vec<RouteSegment>,
pub input_token: String,
pub output_token: String,
pub input_amount: Decimal,
pub output_amount: Decimal,
pub total_gas_cost: Decimal,
pub price_impact: Decimal,
pub execution_time_seconds: u64,
}
impl Route {
pub fn effective_rate(&self) -> Decimal {
if self.input_amount == Decimal::ZERO {
return Decimal::ZERO;
}
(self.output_amount - self.total_gas_cost) / self.input_amount
}
pub fn net_output(&self) -> Decimal {
self.output_amount - self.total_gas_cost
}
}
impl PartialEq for Route {
fn eq(&self, other: &Self) -> bool {
self.effective_rate() == other.effective_rate()
}
}
impl Eq for Route {}
impl PartialOrd for Route {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Route {
fn cmp(&self, other: &Self) -> Ordering {
self.effective_rate().cmp(&other.effective_rate())
}
}
pub struct LiquidityAggregator {
pools: Vec<LiquidityPool>,
bridges: Vec<Bridge>,
max_depth: usize,
}
impl LiquidityAggregator {
pub fn new(max_depth: usize) -> Self {
Self {
pools: Vec::new(),
bridges: Vec::new(),
max_depth,
}
}
pub fn add_pool(&mut self, pool: LiquidityPool) {
self.pools.push(pool);
}
pub fn add_bridge(&mut self, bridge: Bridge) {
self.bridges.push(bridge);
}
pub fn discover_pools(
&self,
chain: Chain,
token_a: &str,
token_b: &str,
) -> Vec<&LiquidityPool> {
self.pools
.iter()
.filter(|pool| {
pool.chain == chain
&& ((pool.token_a == token_a && pool.token_b == token_b)
|| (pool.token_a == token_b && pool.token_b == token_a))
})
.collect()
}
pub fn find_pools_with_token(&self, chain: Chain, token: &str) -> Vec<&LiquidityPool> {
self.pools
.iter()
.filter(|pool| pool.chain == chain && (pool.token_a == token || pool.token_b == token))
.collect()
}
pub fn find_bridges(&self, from_chain: Chain, to_chain: Chain, token: &str) -> Vec<&Bridge> {
self.bridges
.iter()
.filter(|bridge| {
bridge.from_chain == from_chain
&& bridge.to_chain == to_chain
&& bridge.supports_token(token)
})
.collect()
}
pub fn find_optimal_route(
&self,
input_chain: Chain,
input_token: &str,
output_chain: Chain,
output_token: &str,
input_amount: Decimal,
max_slippage: Decimal,
) -> Option<Route> {
let mut heap = BinaryHeap::new();
let mut visited = HashSet::new();
let initial_state = RouteState {
chain: input_chain,
token: input_token.to_string(),
amount: input_amount,
segments: Vec::new(),
total_gas_cost: Decimal::ZERO,
depth: 0,
};
heap.push(initial_state);
while let Some(state) = heap.pop() {
if state.chain == output_chain && state.token == output_token {
return Some(self.build_route(state, input_token, output_token, input_amount));
}
if state.depth >= self.max_depth {
continue;
}
let visit_key = (state.chain, state.token.clone(), state.depth);
if visited.contains(&visit_key) {
continue;
}
visited.insert(visit_key);
for pool in self.find_pools_with_token(state.chain, &state.token) {
let output_token = if pool.token_a == state.token {
&pool.token_b
} else {
&pool.token_a
};
if let Some(output_amount) = pool.calculate_output(&state.token, state.amount) {
if let Some(impact) = pool.calculate_price_impact(&state.token, state.amount) {
if impact > max_slippage {
continue;
}
}
let mut new_segments = state.segments.clone();
new_segments.push(RouteSegment::Swap {
pool: pool.clone(),
input_token: state.token.clone(),
output_token: output_token.clone(),
input_amount: state.amount,
output_amount,
});
let new_state = RouteState {
chain: state.chain,
token: output_token.clone(),
amount: output_amount,
segments: new_segments,
total_gas_cost: state.total_gas_cost + Decimal::from(5),
depth: state.depth + 1,
};
heap.push(new_state);
}
}
for target_chain in &[
Chain::Bitcoin,
Chain::Ethereum,
Chain::BinanceSmartChain,
Chain::Polygon,
] {
if *target_chain == state.chain {
continue;
}
for bridge in self.find_bridges(state.chain, *target_chain, &state.token) {
let output_amount = bridge.calculate_output(state.amount);
let mut new_segments = state.segments.clone();
new_segments.push(RouteSegment::Bridge {
bridge: bridge.clone(),
token: state.token.clone(),
input_amount: state.amount,
output_amount,
});
let new_state = RouteState {
chain: *target_chain,
token: state.token.clone(),
amount: output_amount,
segments: new_segments,
total_gas_cost: state.total_gas_cost + bridge.gas_cost,
depth: state.depth + 1,
};
heap.push(new_state);
}
}
}
None
}
fn build_route(
&self,
state: RouteState,
input_token: &str,
output_token: &str,
input_amount: Decimal,
) -> Route {
let mut total_gas_cost = Decimal::ZERO;
let mut execution_time = 0u64;
let mut total_impact = Decimal::ZERO;
for segment in &state.segments {
total_gas_cost += segment.gas_cost();
match segment {
RouteSegment::Swap {
pool,
input_token,
input_amount,
..
} => {
execution_time += 15; if let Some(impact) = pool.calculate_price_impact(input_token, *input_amount) {
total_impact += impact;
}
}
RouteSegment::Bridge { bridge, .. } => {
execution_time += bridge.estimated_time_seconds;
total_impact += bridge.fee; }
}
}
Route {
segments: state.segments,
input_token: input_token.to_string(),
output_token: output_token.to_string(),
input_amount,
output_amount: state.amount,
total_gas_cost,
price_impact: total_impact,
execution_time_seconds: execution_time,
}
}
pub fn get_liquidity_depth(&self, chain: Chain, token_a: &str, token_b: &str) -> Decimal {
self.discover_pools(chain, token_a, token_b)
.iter()
.map(|pool| {
if pool.token_a == token_a {
pool.reserve_a
} else {
pool.reserve_b
}
})
.sum()
}
}
#[derive(Clone)]
struct RouteState {
chain: Chain,
token: String,
amount: Decimal,
segments: Vec<RouteSegment>,
total_gas_cost: Decimal,
depth: usize,
}
impl PartialEq for RouteState {
fn eq(&self, other: &Self) -> bool {
self.amount == other.amount
}
}
impl Eq for RouteState {}
impl PartialOrd for RouteState {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for RouteState {
fn cmp(&self, other: &Self) -> Ordering {
self.amount.cmp(&other.amount)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_pool(
id: &str,
chain: Chain,
token_a: &str,
token_b: &str,
reserve_a: u64,
reserve_b: u64,
) -> LiquidityPool {
LiquidityPool {
id: id.to_string(),
chain,
protocol: "UniswapV2".to_string(),
token_a: token_a.to_string(),
token_b: token_b.to_string(),
reserve_a: Decimal::from(reserve_a),
reserve_b: Decimal::from(reserve_b),
fee: Decimal::new(3, 3), last_updated: Utc::now(),
}
}
#[test]
fn test_pool_output_calculation() {
let pool = create_test_pool("pool1", Chain::Ethereum, "ETH", "USDT", 1000, 2000000);
let output = pool.calculate_output("ETH", Decimal::from(10)).unwrap();
assert!(output > Decimal::from(19700) && output < Decimal::from(19800));
}
#[test]
fn test_price_impact_calculation() {
let pool = create_test_pool("pool1", Chain::Ethereum, "ETH", "USDT", 1000, 2000000);
let impact = pool
.calculate_price_impact("ETH", Decimal::from(10))
.unwrap();
assert!(impact > Decimal::ZERO);
assert!(impact < Decimal::new(1, 1)); }
#[test]
fn test_pool_discovery() {
let mut aggregator = LiquidityAggregator::new(3);
aggregator.add_pool(create_test_pool(
"pool1",
Chain::Ethereum,
"ETH",
"USDT",
1000,
2000000,
));
aggregator.add_pool(create_test_pool(
"pool2",
Chain::Ethereum,
"ETH",
"DAI",
500,
1000000,
));
aggregator.add_pool(create_test_pool(
"pool3",
Chain::Polygon,
"ETH",
"USDT",
300,
600000,
));
let pools = aggregator.discover_pools(Chain::Ethereum, "ETH", "USDT");
assert_eq!(pools.len(), 1);
assert_eq!(pools[0].id, "pool1");
}
#[test]
fn test_find_pools_with_token() {
let mut aggregator = LiquidityAggregator::new(3);
aggregator.add_pool(create_test_pool(
"pool1",
Chain::Ethereum,
"ETH",
"USDT",
1000,
2000000,
));
aggregator.add_pool(create_test_pool(
"pool2",
Chain::Ethereum,
"ETH",
"DAI",
500,
1000000,
));
aggregator.add_pool(create_test_pool(
"pool3",
Chain::Ethereum,
"BTC",
"USDT",
10,
200000,
));
let pools = aggregator.find_pools_with_token(Chain::Ethereum, "ETH");
assert_eq!(pools.len(), 2);
}
#[test]
fn test_bridge_output_calculation() {
let bridge = Bridge {
id: "bridge1".to_string(),
from_chain: Chain::Ethereum,
to_chain: Chain::Polygon,
supported_tokens: vec!["USDT".to_string()],
fee: Decimal::new(5, 3), estimated_time_seconds: 300,
gas_cost: Decimal::from(10),
};
let output = bridge.calculate_output(Decimal::from(1000));
assert_eq!(output, Decimal::from(995)); }
#[test]
fn test_route_effective_rate() {
let route = Route {
segments: vec![],
input_token: "ETH".to_string(),
output_token: "USDT".to_string(),
input_amount: Decimal::from(10),
output_amount: Decimal::from(20000),
total_gas_cost: Decimal::from(50),
price_impact: Decimal::new(5, 2), execution_time_seconds: 60,
};
let effective_rate = route.effective_rate();
assert_eq!(effective_rate, Decimal::from(1995));
}
#[test]
fn test_liquidity_depth() {
let mut aggregator = LiquidityAggregator::new(3);
aggregator.add_pool(create_test_pool(
"pool1",
Chain::Ethereum,
"ETH",
"USDT",
1000,
2000000,
));
aggregator.add_pool(create_test_pool(
"pool2",
Chain::Ethereum,
"ETH",
"USDT",
500,
1000000,
));
let depth = aggregator.get_liquidity_depth(Chain::Ethereum, "ETH", "USDT");
assert_eq!(depth, Decimal::from(1500)); }
}