1use std::{borrow::Cow, fmt::Display, str::FromStr, sync::Arc};
17
18use alloy_primitives::{Address, keccak256};
19use nautilus_core::hex;
20use serde::{Deserialize, Serialize};
21use strum::{Display, EnumIter, EnumString};
22
23use crate::{
24 defi::{amm::Pool, chain::Chain, validation::validate_address},
25 identifiers::{InstrumentId, Symbol, Venue},
26 instruments::{Instrument, any::InstrumentAny, currency_pair::CurrencyPair},
27 types::{currency::Currency, fixed::FIXED_PRECISION, price::Price, quantity::Quantity},
28};
29
30#[derive(
32 Debug,
33 Clone,
34 Copy,
35 Hash,
36 PartialEq,
37 Eq,
38 Serialize,
39 Deserialize,
40 strum::EnumString,
41 strum::Display,
42 strum::EnumIter,
43)]
44#[cfg_attr(
45 feature = "python",
46 pyo3::pyclass(
47 frozen,
48 eq,
49 eq_int,
50 module = "nautilus_trader.model",
51 from_py_object,
52 rename_all = "SCREAMING_SNAKE_CASE",
53 )
54)]
55#[cfg_attr(feature = "python", pyo3_stub_gen::derive::gen_stub_pyclass_enum)]
56#[non_exhaustive]
57pub enum AmmType {
58 CPAMM,
60 CLAMM,
62 CLAMEnhanced,
64 StableSwap,
66 WeightedPool,
68 ComposablePool,
70}
71
72#[derive(
74 Debug,
75 Clone,
76 Copy,
77 Hash,
78 PartialOrd,
79 PartialEq,
80 Ord,
81 Eq,
82 Display,
83 EnumIter,
84 EnumString,
85 Serialize,
86 Deserialize,
87)]
88#[cfg_attr(
89 feature = "python",
90 pyo3::pyclass(
91 frozen,
92 eq,
93 eq_int,
94 module = "nautilus_trader.model",
95 from_py_object,
96 rename_all = "SCREAMING_SNAKE_CASE",
97 )
98)]
99#[cfg_attr(feature = "python", pyo3_stub_gen::derive::gen_stub_pyclass_enum)]
100pub enum DexType {
101 AerodromeSlipstream,
102 AerodromeV1,
103 BalancerV2,
104 BalancerV3,
105 BaseSwapV2,
106 BaseX,
107 CamelotV3,
108 CurveFinance,
109 FluidDEX,
110 MaverickV1,
111 MaverickV2,
112 PancakeSwapV3,
113 SushiSwapV2,
114 SushiSwapV3,
115 UniswapV2,
116 UniswapV3,
117 UniswapV4,
118}
119
120impl DexType {
121 #[must_use]
123 pub fn from_dex_name(dex_name: &str) -> Option<Self> {
124 Self::from_str(dex_name).ok()
125 }
126}
127
128#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
130#[cfg_attr(
131 feature = "python",
132 pyo3::pyclass(module = "nautilus_trader.model", from_py_object)
133)]
134#[cfg_attr(feature = "python", pyo3_stub_gen::derive::gen_stub_pyclass)]
135pub struct Dex {
136 pub chain: Chain,
138 pub name: DexType,
140 pub factory: Address,
142 pub factory_creation_block: u64,
144 pub pool_created_event: Cow<'static, str>,
146 pub initialize_event: Option<Cow<'static, str>>,
148 pub swap_created_event: Cow<'static, str>,
150 pub mint_created_event: Cow<'static, str>,
152 pub burn_created_event: Cow<'static, str>,
154 pub collect_created_event: Cow<'static, str>,
156 pub flash_created_event: Option<Cow<'static, str>>,
158 pub fee_protocol_update_event: Option<Cow<'static, str>>,
160 pub fee_protocol_collect_event: Option<Cow<'static, str>>,
162 pub amm_type: AmmType,
164 #[allow(dead_code)]
166 pairs: Vec<Pool>,
167}
168
169pub type SharedDex = Arc<Dex>;
171
172impl Dex {
173 #[must_use]
179 #[expect(clippy::too_many_arguments)]
180 pub fn new(
181 chain: Chain,
182 name: DexType,
183 factory: &str,
184 factory_creation_block: u64,
185 amm_type: AmmType,
186 pool_created_event: &str,
187 swap_event: &str,
188 mint_event: &str,
189 burn_event: &str,
190 collect_event: &str,
191 ) -> Self {
192 let encoded_pool_created_event =
193 hex::encode_prefixed(keccak256(pool_created_event.as_bytes()));
194 let encoded_swap_event = hex::encode_prefixed(keccak256(swap_event.as_bytes()));
195 let encoded_mint_event = hex::encode_prefixed(keccak256(mint_event.as_bytes()));
196 let encoded_burn_event = hex::encode_prefixed(keccak256(burn_event.as_bytes()));
197 let encoded_collect_event = hex::encode_prefixed(keccak256(collect_event.as_bytes()));
198 let factory_address = match validate_address(factory) {
199 Ok(address) => address,
200 Err(e) => panic!(
201 "Invalid factory address for DEX {name} on chain {chain} for factory address {factory}: {e}"
202 ),
203 };
204 Self {
205 chain,
206 name,
207 factory: factory_address,
208 factory_creation_block,
209 pool_created_event: encoded_pool_created_event.into(),
210 initialize_event: None,
211 swap_created_event: encoded_swap_event.into(),
212 mint_created_event: encoded_mint_event.into(),
213 burn_created_event: encoded_burn_event.into(),
214 collect_created_event: encoded_collect_event.into(),
215 flash_created_event: None,
216 fee_protocol_update_event: None,
217 fee_protocol_collect_event: None,
218 amm_type,
219 pairs: vec![],
220 }
221 }
222
223 #[must_use]
225 pub fn id(&self) -> String {
226 format!("{}:{}", self.chain.name, self.name)
227 }
228
229 pub fn set_initialize_event(&mut self, event: &str) {
231 self.initialize_event = Some(hex::encode_prefixed(keccak256(event.as_bytes())).into());
232 }
233
234 pub fn set_flash_event(&mut self, event: &str) {
236 self.flash_created_event = Some(hex::encode_prefixed(keccak256(event.as_bytes())).into());
237 }
238
239 pub fn set_fee_protocol_update_event(&mut self, event: &str) {
241 self.fee_protocol_update_event =
242 Some(hex::encode_prefixed(keccak256(event.as_bytes())).into());
243 }
244
245 pub fn set_fee_protocol_collect_event(&mut self, event: &str) {
247 self.fee_protocol_collect_event =
248 Some(hex::encode_prefixed(keccak256(event.as_bytes())).into());
249 }
250}
251
252impl Display for Dex {
253 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
254 write!(f, "Dex(chain={}, name={})", self.chain, self.name)
255 }
256}
257
258impl From<Pool> for CurrencyPair {
259 fn from(p: Pool) -> Self {
260 let symbol = Symbol::from(format!("{}/{}", p.token0.symbol, p.token1.symbol));
261 let id = InstrumentId::new(symbol, Venue::from(p.dex.id()));
262
263 let size_precision = p.token0.decimals.min(FIXED_PRECISION);
264 let price_precision = p.token1.decimals.min(FIXED_PRECISION);
265
266 let price_increment = Price::new(10f64.powi(-i32::from(price_precision)), price_precision);
267 let size_increment = Quantity::new(10f64.powi(-i32::from(size_precision)), size_precision);
268
269 Self::new(
270 id,
271 symbol,
272 Currency::from(p.token0.symbol.as_str()),
273 Currency::from(p.token1.symbol.as_str()),
274 price_precision,
275 size_precision,
276 price_increment,
277 size_increment,
278 None, None, None, None, None, None, None, None, None, None, None, None, None, None, 0.into(),
293 0.into(),
294 )
295 }
296}
297
298impl From<Pool> for InstrumentAny {
299 fn from(p: Pool) -> Self {
300 CurrencyPair::from(p).into_any()
301 }
302}
303
304#[cfg(test)]
305mod tests {
306 use rstest::rstest;
307
308 use super::DexType;
309
310 #[rstest]
311 fn test_dex_type_from_dex_name_valid() {
312 assert!(DexType::from_dex_name("UniswapV3").is_some());
314 assert!(DexType::from_dex_name("SushiSwapV2").is_some());
315 assert!(DexType::from_dex_name("BalancerV2").is_some());
316 assert!(DexType::from_dex_name("CamelotV3").is_some());
317
318 let uniswap_v3 = DexType::from_dex_name("UniswapV3").unwrap();
320 assert_eq!(uniswap_v3, DexType::UniswapV3);
321
322 let aerodrome_slipstream = DexType::from_dex_name("AerodromeSlipstream").unwrap();
324 assert_eq!(aerodrome_slipstream, DexType::AerodromeSlipstream);
325
326 let fluid_dex = DexType::from_dex_name("FluidDEX").unwrap();
328 assert_eq!(fluid_dex, DexType::FluidDEX);
329 }
330
331 #[rstest]
332 fn test_dex_type_from_dex_name_invalid() {
333 assert!(DexType::from_dex_name("InvalidDEX").is_none());
335 assert!(DexType::from_dex_name("").is_none());
336 assert!(DexType::from_dex_name("NonExistentDEX").is_none());
337 }
338
339 #[rstest]
340 fn test_dex_type_from_dex_name_case_sensitive() {
341 assert!(DexType::from_dex_name("UniswapV3").is_some());
343 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());
348 assert!(DexType::from_dex_name("sushiswapv2").is_none()); }
350
351 #[rstest]
352 fn test_dex_type_all_variants_mappable() {
353 let all_dex_names = vec![
355 "AerodromeSlipstream",
356 "AerodromeV1",
357 "BalancerV2",
358 "BalancerV3",
359 "BaseSwapV2",
360 "BaseX",
361 "CamelotV3",
362 "CurveFinance",
363 "FluidDEX",
364 "MaverickV1",
365 "MaverickV2",
366 "PancakeSwapV3",
367 "SushiSwapV2",
368 "SushiSwapV3",
369 "UniswapV2",
370 "UniswapV3",
371 "UniswapV4",
372 ];
373
374 for dex_name in all_dex_names {
375 assert!(
376 DexType::from_dex_name(dex_name).is_some(),
377 "DEX name '{dex_name}' should be valid but was not found",
378 );
379 }
380 }
381
382 #[rstest]
383 fn test_dex_type_display() {
384 assert_eq!(DexType::UniswapV3.to_string(), "UniswapV3");
386 assert_eq!(DexType::SushiSwapV2.to_string(), "SushiSwapV2");
387 assert_eq!(
388 DexType::AerodromeSlipstream.to_string(),
389 "AerodromeSlipstream"
390 );
391 assert_eq!(DexType::FluidDEX.to_string(), "FluidDEX");
392 }
393}