pub mod bellman_ford;
pub mod most_liquid;
#[cfg(test)]
pub mod test_utils;
use std::time::Duration;
pub use bellman_ford::BellmanFordAlgorithm;
pub use most_liquid::MostLiquidAlgorithm;
use tycho_simulation::tycho_core::models::Address;
use crate::{
derived::{computation::ComputationRequirements, SharedDerivedDataRef},
feed::market_data::SharedMarketDataRef,
graph::GraphManager,
types::{quote::Order, RouteResult},
};
#[must_use]
#[derive(Debug, Clone)]
pub struct AlgorithmConfig {
min_hops: usize,
max_hops: usize,
timeout: Duration,
max_routes: Option<usize>,
gas_aware: bool,
}
impl AlgorithmConfig {
pub fn new(
min_hops: usize,
max_hops: usize,
timeout: Duration,
max_routes: Option<usize>,
) -> Result<Self, AlgorithmError> {
if min_hops == 0 {
return Err(AlgorithmError::InvalidConfiguration {
reason: "min_hops must be at least 1".to_string(),
});
}
if min_hops > max_hops {
return Err(AlgorithmError::InvalidConfiguration {
reason: format!("min_hops ({}) cannot exceed max_hops ({})", min_hops, max_hops),
});
}
if max_routes == Some(0) {
return Err(AlgorithmError::InvalidConfiguration {
reason: "max_routes must be at least 1".to_string(),
});
}
Ok(Self { min_hops, max_hops, timeout, max_routes, gas_aware: true })
}
pub fn min_hops(&self) -> usize {
self.min_hops
}
pub fn max_hops(&self) -> usize {
self.max_hops
}
pub fn max_routes(&self) -> Option<usize> {
self.max_routes
}
pub fn timeout(&self) -> Duration {
self.timeout
}
pub fn gas_aware(&self) -> bool {
self.gas_aware
}
pub fn with_gas_aware(mut self, enabled: bool) -> Self {
self.gas_aware = enabled;
self
}
}
impl Default for AlgorithmConfig {
fn default() -> Self {
Self::new(1, 3, Duration::from_millis(100), None).unwrap()
}
}
#[allow(async_fn_in_trait)]
pub trait Algorithm: Send + Sync {
type GraphType: Send + Sync;
type GraphManager: GraphManager<Self::GraphType> + Default;
fn name(&self) -> &str;
async fn find_best_route(
&self,
graph: &Self::GraphType,
market: SharedMarketDataRef,
derived: Option<SharedDerivedDataRef>,
order: &Order,
) -> Result<RouteResult, AlgorithmError>;
fn computation_requirements(&self) -> ComputationRequirements;
fn timeout(&self) -> Duration;
}
#[non_exhaustive]
#[derive(Debug, Clone, thiserror::Error, PartialEq)]
pub enum AlgorithmError {
#[non_exhaustive]
#[error("invalid configuration: {reason}")]
InvalidConfiguration {
reason: String,
},
#[non_exhaustive]
#[error("no path from {from:?} to {to:?}: {reason}")]
NoPath {
from: Address,
to: Address,
reason: NoPathReason,
},
#[error("insufficient liquidity on all paths")]
InsufficientLiquidity,
#[non_exhaustive]
#[error("timeout after {elapsed_ms}ms")]
Timeout {
elapsed_ms: u64,
},
#[error("exact-out orders not supported")]
ExactOutNotSupported,
#[non_exhaustive]
#[error("simulation failed for {component_id}: {error}")]
SimulationFailed {
component_id: String,
error: String,
},
#[non_exhaustive]
#[error("{kind} not found{}", id.as_ref().map(|i| format!(": {i}")).unwrap_or_default())]
DataNotFound {
kind: &'static str,
id: Option<String>,
},
#[error("{0}")]
Other(String),
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NoPathReason {
SourceTokenNotInGraph,
DestinationTokenNotInGraph,
NoGraphPath,
NoScorablePaths,
}
impl std::fmt::Display for NoPathReason {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::SourceTokenNotInGraph => write!(f, "source token not in graph"),
Self::DestinationTokenNotInGraph => write!(f, "destination token not in graph"),
Self::NoGraphPath => write!(f, "no connecting path in graph"),
Self::NoScorablePaths => write!(f, "no paths with valid scores"),
}
}
}