use std::{borrow::Cow, fmt::Display, str::FromStr, sync::Arc};
use alloy_primitives::{Address, keccak256};
use nautilus_core::hex;
use serde::{Deserialize, Serialize};
use strum::{Display, EnumIter, EnumString};
use crate::{
defi::{amm::Pool, chain::Chain, validation::validate_address},
identifiers::{InstrumentId, Symbol, Venue},
instruments::{Instrument, any::InstrumentAny, currency_pair::CurrencyPair},
types::{currency::Currency, fixed::FIXED_PRECISION, price::Price, quantity::Quantity},
};
#[derive(
Debug,
Clone,
Copy,
Hash,
PartialEq,
Eq,
Serialize,
Deserialize,
strum::EnumString,
strum::Display,
strum::EnumIter,
)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(feature = "python", pyo3_stub_gen::derive::gen_stub_pyclass_enum)]
#[non_exhaustive]
pub enum AmmType {
CPAMM,
CLAMM,
CLAMEnhanced,
StableSwap,
WeightedPool,
ComposablePool,
}
#[derive(
Debug,
Clone,
Copy,
Hash,
PartialOrd,
PartialEq,
Ord,
Eq,
Display,
EnumIter,
EnumString,
Serialize,
Deserialize,
)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(feature = "python", pyo3_stub_gen::derive::gen_stub_pyclass_enum)]
pub enum DexType {
AerodromeSlipstream,
AerodromeV1,
BalancerV2,
BalancerV3,
BaseSwapV2,
BaseX,
CamelotV3,
CurveFinance,
FluidDEX,
MaverickV1,
MaverickV2,
PancakeSwapV3,
SushiSwapV2,
SushiSwapV3,
UniswapV2,
UniswapV3,
UniswapV4,
}
impl DexType {
pub fn from_dex_name(dex_name: &str) -> Option<Self> {
Self::from_str(dex_name).ok()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.model", from_py_object)
)]
#[cfg_attr(feature = "python", pyo3_stub_gen::derive::gen_stub_pyclass)]
pub struct Dex {
pub chain: Chain,
pub name: DexType,
pub factory: Address,
pub factory_creation_block: u64,
pub pool_created_event: Cow<'static, str>,
pub initialize_event: Option<Cow<'static, str>>,
pub swap_created_event: Cow<'static, str>,
pub mint_created_event: Cow<'static, str>,
pub burn_created_event: Cow<'static, str>,
pub collect_created_event: Cow<'static, str>,
pub flash_created_event: Option<Cow<'static, str>>,
pub amm_type: AmmType,
#[allow(dead_code)]
pairs: Vec<Pool>,
}
pub type SharedDex = Arc<Dex>;
impl Dex {
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn new(
chain: Chain,
name: DexType,
factory: &str,
factory_creation_block: u64,
amm_type: AmmType,
pool_created_event: &str,
swap_event: &str,
mint_event: &str,
burn_event: &str,
collect_event: &str,
) -> Self {
let encoded_pool_created_event =
hex::encode_prefixed(keccak256(pool_created_event.as_bytes()));
let encoded_swap_event = hex::encode_prefixed(keccak256(swap_event.as_bytes()));
let encoded_mint_event = hex::encode_prefixed(keccak256(mint_event.as_bytes()));
let encoded_burn_event = hex::encode_prefixed(keccak256(burn_event.as_bytes()));
let encoded_collect_event = hex::encode_prefixed(keccak256(collect_event.as_bytes()));
let factory_address = match validate_address(factory) {
Ok(address) => address,
Err(e) => panic!(
"Invalid factory address for DEX {name} on chain {chain} for factory address {factory}: {e}"
),
};
Self {
chain,
name,
factory: factory_address,
factory_creation_block,
pool_created_event: encoded_pool_created_event.into(),
initialize_event: None,
swap_created_event: encoded_swap_event.into(),
mint_created_event: encoded_mint_event.into(),
burn_created_event: encoded_burn_event.into(),
collect_created_event: encoded_collect_event.into(),
flash_created_event: None,
amm_type,
pairs: vec![],
}
}
pub fn id(&self) -> String {
format!("{}:{}", self.chain.name, self.name)
}
pub fn set_initialize_event(&mut self, event: &str) {
self.initialize_event = Some(hex::encode_prefixed(keccak256(event.as_bytes())).into());
}
pub fn set_flash_event(&mut self, event: &str) {
self.flash_created_event = Some(hex::encode_prefixed(keccak256(event.as_bytes())).into());
}
}
impl Display for Dex {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Dex(chain={}, name={})", self.chain, self.name)
}
}
impl From<Pool> for CurrencyPair {
fn from(p: Pool) -> Self {
let symbol = Symbol::from(format!("{}/{}", p.token0.symbol, p.token1.symbol));
let id = InstrumentId::new(symbol, Venue::from(p.dex.id()));
let size_precision = p.token0.decimals.min(FIXED_PRECISION);
let price_precision = p.token1.decimals.min(FIXED_PRECISION);
let price_increment = Price::new(10f64.powi(-(price_precision as i32)), price_precision);
let size_increment = Quantity::new(10f64.powi(-(size_precision as i32)), size_precision);
Self::new(
id,
symbol,
Currency::from(p.token0.symbol.as_str()),
Currency::from(p.token1.symbol.as_str()),
price_precision,
size_precision,
price_increment,
size_increment,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
0.into(),
0.into(),
)
}
}
impl From<Pool> for InstrumentAny {
fn from(p: Pool) -> Self {
CurrencyPair::from(p).into_any()
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::DexType;
#[rstest]
fn test_dex_type_from_dex_name_valid() {
assert!(DexType::from_dex_name("UniswapV3").is_some());
assert!(DexType::from_dex_name("SushiSwapV2").is_some());
assert!(DexType::from_dex_name("BalancerV2").is_some());
assert!(DexType::from_dex_name("CamelotV3").is_some());
let uniswap_v3 = DexType::from_dex_name("UniswapV3").unwrap();
assert_eq!(uniswap_v3, DexType::UniswapV3);
let aerodrome_slipstream = DexType::from_dex_name("AerodromeSlipstream").unwrap();
assert_eq!(aerodrome_slipstream, DexType::AerodromeSlipstream);
let fluid_dex = DexType::from_dex_name("FluidDEX").unwrap();
assert_eq!(fluid_dex, DexType::FluidDEX);
}
#[rstest]
fn test_dex_type_from_dex_name_invalid() {
assert!(DexType::from_dex_name("InvalidDEX").is_none());
assert!(DexType::from_dex_name("").is_none());
assert!(DexType::from_dex_name("NonExistentDEX").is_none());
}
#[rstest]
fn test_dex_type_from_dex_name_case_sensitive() {
assert!(DexType::from_dex_name("UniswapV3").is_some());
assert!(DexType::from_dex_name("uniswapv3").is_none()); assert!(DexType::from_dex_name("UNISWAPV3").is_none()); assert!(DexType::from_dex_name("UniSwapV3").is_none());
assert!(DexType::from_dex_name("SushiSwapV2").is_some());
assert!(DexType::from_dex_name("sushiswapv2").is_none()); }
#[rstest]
fn test_dex_type_all_variants_mappable() {
let all_dex_names = vec![
"AerodromeSlipstream",
"AerodromeV1",
"BalancerV2",
"BalancerV3",
"BaseSwapV2",
"BaseX",
"CamelotV3",
"CurveFinance",
"FluidDEX",
"MaverickV1",
"MaverickV2",
"PancakeSwapV3",
"SushiSwapV2",
"SushiSwapV3",
"UniswapV2",
"UniswapV3",
"UniswapV4",
];
for dex_name in all_dex_names {
assert!(
DexType::from_dex_name(dex_name).is_some(),
"DEX name '{dex_name}' should be valid but was not found",
);
}
}
#[rstest]
fn test_dex_type_display() {
assert_eq!(DexType::UniswapV3.to_string(), "UniswapV3");
assert_eq!(DexType::SushiSwapV2.to_string(), "SushiSwapV2");
assert_eq!(
DexType::AerodromeSlipstream.to_string(),
"AerodromeSlipstream"
);
assert_eq!(DexType::FluidDEX.to_string(), "FluidDEX");
}
}