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 amm_type: AmmType,
160 #[allow(dead_code)]
162 pairs: Vec<Pool>,
163}
164
165pub type SharedDex = Arc<Dex>;
167
168impl Dex {
169 #[must_use]
175 #[expect(clippy::too_many_arguments)]
176 pub fn new(
177 chain: Chain,
178 name: DexType,
179 factory: &str,
180 factory_creation_block: u64,
181 amm_type: AmmType,
182 pool_created_event: &str,
183 swap_event: &str,
184 mint_event: &str,
185 burn_event: &str,
186 collect_event: &str,
187 ) -> Self {
188 let encoded_pool_created_event =
189 hex::encode_prefixed(keccak256(pool_created_event.as_bytes()));
190 let encoded_swap_event = hex::encode_prefixed(keccak256(swap_event.as_bytes()));
191 let encoded_mint_event = hex::encode_prefixed(keccak256(mint_event.as_bytes()));
192 let encoded_burn_event = hex::encode_prefixed(keccak256(burn_event.as_bytes()));
193 let encoded_collect_event = hex::encode_prefixed(keccak256(collect_event.as_bytes()));
194 let factory_address = match validate_address(factory) {
195 Ok(address) => address,
196 Err(e) => panic!(
197 "Invalid factory address for DEX {name} on chain {chain} for factory address {factory}: {e}"
198 ),
199 };
200 Self {
201 chain,
202 name,
203 factory: factory_address,
204 factory_creation_block,
205 pool_created_event: encoded_pool_created_event.into(),
206 initialize_event: None,
207 swap_created_event: encoded_swap_event.into(),
208 mint_created_event: encoded_mint_event.into(),
209 burn_created_event: encoded_burn_event.into(),
210 collect_created_event: encoded_collect_event.into(),
211 flash_created_event: None,
212 amm_type,
213 pairs: vec![],
214 }
215 }
216
217 #[must_use]
219 pub fn id(&self) -> String {
220 format!("{}:{}", self.chain.name, self.name)
221 }
222
223 pub fn set_initialize_event(&mut self, event: &str) {
225 self.initialize_event = Some(hex::encode_prefixed(keccak256(event.as_bytes())).into());
226 }
227
228 pub fn set_flash_event(&mut self, event: &str) {
230 self.flash_created_event = Some(hex::encode_prefixed(keccak256(event.as_bytes())).into());
231 }
232}
233
234impl Display for Dex {
235 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
236 write!(f, "Dex(chain={}, name={})", self.chain, self.name)
237 }
238}
239
240impl From<Pool> for CurrencyPair {
241 fn from(p: Pool) -> Self {
242 let symbol = Symbol::from(format!("{}/{}", p.token0.symbol, p.token1.symbol));
243 let id = InstrumentId::new(symbol, Venue::from(p.dex.id()));
244
245 let size_precision = p.token0.decimals.min(FIXED_PRECISION);
246 let price_precision = p.token1.decimals.min(FIXED_PRECISION);
247
248 let price_increment = Price::new(10f64.powi(-i32::from(price_precision)), price_precision);
249 let size_increment = Quantity::new(10f64.powi(-i32::from(size_precision)), size_precision);
250
251 Self::new(
252 id,
253 symbol,
254 Currency::from(p.token0.symbol.as_str()),
255 Currency::from(p.token1.symbol.as_str()),
256 price_precision,
257 size_precision,
258 price_increment,
259 size_increment,
260 None,
261 None,
262 None,
263 None,
264 None,
265 None,
266 None,
267 None,
268 None,
269 None,
270 None,
271 None,
272 None,
273 0.into(),
274 0.into(),
275 )
276 }
277}
278
279impl From<Pool> for InstrumentAny {
280 fn from(p: Pool) -> Self {
281 CurrencyPair::from(p).into_any()
282 }
283}
284
285#[cfg(test)]
286mod tests {
287 use rstest::rstest;
288
289 use super::DexType;
290
291 #[rstest]
292 fn test_dex_type_from_dex_name_valid() {
293 assert!(DexType::from_dex_name("UniswapV3").is_some());
295 assert!(DexType::from_dex_name("SushiSwapV2").is_some());
296 assert!(DexType::from_dex_name("BalancerV2").is_some());
297 assert!(DexType::from_dex_name("CamelotV3").is_some());
298
299 let uniswap_v3 = DexType::from_dex_name("UniswapV3").unwrap();
301 assert_eq!(uniswap_v3, DexType::UniswapV3);
302
303 let aerodrome_slipstream = DexType::from_dex_name("AerodromeSlipstream").unwrap();
305 assert_eq!(aerodrome_slipstream, DexType::AerodromeSlipstream);
306
307 let fluid_dex = DexType::from_dex_name("FluidDEX").unwrap();
309 assert_eq!(fluid_dex, DexType::FluidDEX);
310 }
311
312 #[rstest]
313 fn test_dex_type_from_dex_name_invalid() {
314 assert!(DexType::from_dex_name("InvalidDEX").is_none());
316 assert!(DexType::from_dex_name("").is_none());
317 assert!(DexType::from_dex_name("NonExistentDEX").is_none());
318 }
319
320 #[rstest]
321 fn test_dex_type_from_dex_name_case_sensitive() {
322 assert!(DexType::from_dex_name("UniswapV3").is_some());
324 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());
329 assert!(DexType::from_dex_name("sushiswapv2").is_none()); }
331
332 #[rstest]
333 fn test_dex_type_all_variants_mappable() {
334 let all_dex_names = vec![
336 "AerodromeSlipstream",
337 "AerodromeV1",
338 "BalancerV2",
339 "BalancerV3",
340 "BaseSwapV2",
341 "BaseX",
342 "CamelotV3",
343 "CurveFinance",
344 "FluidDEX",
345 "MaverickV1",
346 "MaverickV2",
347 "PancakeSwapV3",
348 "SushiSwapV2",
349 "SushiSwapV3",
350 "UniswapV2",
351 "UniswapV3",
352 "UniswapV4",
353 ];
354
355 for dex_name in all_dex_names {
356 assert!(
357 DexType::from_dex_name(dex_name).is_some(),
358 "DEX name '{dex_name}' should be valid but was not found",
359 );
360 }
361 }
362
363 #[rstest]
364 fn test_dex_type_display() {
365 assert_eq!(DexType::UniswapV3.to_string(), "UniswapV3");
367 assert_eq!(DexType::SushiSwapV2.to_string(), "SushiSwapV2");
368 assert_eq!(
369 DexType::AerodromeSlipstream.to_string(),
370 "AerodromeSlipstream"
371 );
372 assert_eq!(DexType::FluidDEX.to_string(), "FluidDEX");
373 }
374}