1use std::{borrow::Cow, fmt::Display, str::FromStr, sync::Arc};
17
18use alloy_primitives::{Address, keccak256};
19use serde::{Deserialize, Serialize};
20use strum::{Display, EnumIter, EnumString};
21
22use crate::{
23 defi::{amm::Pool, chain::Chain, validation::validate_address},
24 identifiers::{InstrumentId, Symbol, Venue},
25 instruments::{Instrument, any::InstrumentAny, currency_pair::CurrencyPair},
26 types::{currency::Currency, fixed::FIXED_PRECISION, price::Price, quantity::Quantity},
27};
28
29#[derive(
31 Debug,
32 Clone,
33 Copy,
34 PartialEq,
35 Serialize,
36 Deserialize,
37 strum::EnumString,
38 strum::Display,
39 strum::EnumIter,
40)]
41#[cfg_attr(feature = "python", pyo3::pyclass(module = "nautilus_trader.model"))]
42#[cfg_attr(feature = "python", pyo3_stub_gen::derive::gen_stub_pyclass_enum)]
43#[non_exhaustive]
44pub enum AmmType {
45 CPAMM,
47 CLAMM,
49 CLAMEnhanced,
51 StableSwap,
53 WeightedPool,
55 ComposablePool,
57}
58
59#[derive(
61 Debug,
62 Clone,
63 Copy,
64 Hash,
65 PartialOrd,
66 PartialEq,
67 Ord,
68 Eq,
69 Display,
70 EnumIter,
71 EnumString,
72 Serialize,
73 Deserialize,
74)]
75#[cfg_attr(feature = "python", pyo3::pyclass(module = "nautilus_trader.model"))]
76#[cfg_attr(feature = "python", pyo3_stub_gen::derive::gen_stub_pyclass_enum)]
77pub enum DexType {
78 AerodromeSlipstream,
79 AerodromeV1,
80 BalancerV2,
81 BalancerV3,
82 BaseSwapV2,
83 BaseX,
84 CamelotV3,
85 CurveFinance,
86 FluidDEX,
87 MaverickV1,
88 MaverickV2,
89 PancakeSwapV3,
90 SushiSwapV2,
91 SushiSwapV3,
92 UniswapV2,
93 UniswapV3,
94 UniswapV4,
95}
96
97impl DexType {
98 pub fn from_dex_name(dex_name: &str) -> Option<DexType> {
100 DexType::from_str(dex_name).ok()
101 }
102}
103
104#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
106#[cfg_attr(feature = "python", pyo3::pyclass(module = "nautilus_trader.model"))]
107#[cfg_attr(feature = "python", pyo3_stub_gen::derive::gen_stub_pyclass)]
108pub struct Dex {
109 pub chain: Chain,
111 pub name: DexType,
113 pub factory: Address,
115 pub factory_creation_block: u64,
117 pub pool_created_event: Cow<'static, str>,
119 pub initialize_event: Option<Cow<'static, str>>,
121 pub swap_created_event: Cow<'static, str>,
123 pub mint_created_event: Cow<'static, str>,
125 pub burn_created_event: Cow<'static, str>,
127 pub collect_created_event: Cow<'static, str>,
129 pub flash_created_event: Option<Cow<'static, str>>,
131 pub amm_type: AmmType,
133 #[allow(dead_code, reason = "TBD")]
135 pairs: Vec<Pool>,
136}
137
138pub type SharedDex = Arc<Dex>;
140
141impl Dex {
142 #[must_use]
148 #[allow(clippy::too_many_arguments)]
149 pub fn new(
150 chain: Chain,
151 name: DexType,
152 factory: &str,
153 factory_creation_block: u64,
154 amm_type: AmmType,
155 pool_created_event: &str,
156 swap_event: &str,
157 mint_event: &str,
158 burn_event: &str,
159 collect_event: &str,
160 ) -> Self {
161 let pool_created_event_hash = keccak256(pool_created_event.as_bytes());
162 let encoded_pool_created_event = format!(
163 "0x{encoded_hash}",
164 encoded_hash = hex::encode(pool_created_event_hash)
165 );
166 let swap_event_hash: alloy_primitives::FixedBytes<32> = keccak256(swap_event.as_bytes());
167 let encoded_swap_event = format!(
168 "0x{encoded_hash}",
169 encoded_hash = hex::encode(swap_event_hash)
170 );
171 let mint_event_hash = keccak256(mint_event.as_bytes());
172 let encoded_mint_event = format!(
173 "0x{encoded_hash}",
174 encoded_hash = hex::encode(mint_event_hash)
175 );
176 let burn_event_hash = keccak256(burn_event.as_bytes());
177 let encoded_burn_event = format!(
178 "0x{encoded_hash}",
179 encoded_hash = hex::encode(burn_event_hash)
180 );
181 let collect_event_hash = keccak256(collect_event.as_bytes());
182 let encoded_collect_event = format!(
183 "0x{encoded_hash}",
184 encoded_hash = hex::encode(collect_event_hash)
185 );
186 let factory_address = validate_address(factory).unwrap();
187 Self {
188 chain,
189 name,
190 factory: factory_address,
191 factory_creation_block,
192 pool_created_event: encoded_pool_created_event.into(),
193 initialize_event: None,
194 swap_created_event: encoded_swap_event.into(),
195 mint_created_event: encoded_mint_event.into(),
196 burn_created_event: encoded_burn_event.into(),
197 collect_created_event: encoded_collect_event.into(),
198 flash_created_event: None,
199 amm_type,
200 pairs: vec![],
201 }
202 }
203
204 pub fn id(&self) -> String {
206 format!("{}:{}", self.chain.name, self.name)
207 }
208
209 pub fn set_initialize_event(&mut self, event: &str) {
211 let initialize_event_hash = keccak256(event.as_bytes());
212 let encoded_initialized_event = format!(
213 "0x{encoded_hash}",
214 encoded_hash = hex::encode(initialize_event_hash)
215 );
216 self.initialize_event = Some(encoded_initialized_event.into());
217 }
218
219 pub fn set_flash_event(&mut self, event: &str) {
221 let flash_event_hash = keccak256(event.as_bytes());
222 let encoded_flash_event = format!(
223 "0x{encoded_hash}",
224 encoded_hash = hex::encode(flash_event_hash)
225 );
226 self.flash_created_event = Some(encoded_flash_event.into());
227 }
228}
229
230impl Display for Dex {
231 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
232 write!(f, "Dex(chain={}, name={})", self.chain, self.name)
233 }
234}
235
236impl From<Pool> for CurrencyPair {
237 fn from(p: Pool) -> Self {
238 let symbol = Symbol::from(format!("{}/{}", p.token0.symbol, p.token1.symbol));
239 let id = InstrumentId::new(symbol, Venue::from(p.dex.id()));
240
241 let size_precision = p.token0.decimals.min(FIXED_PRECISION);
242 let price_precision = p.token1.decimals.min(FIXED_PRECISION);
243
244 let price_increment = Price::new(10f64.powi(-(price_precision as i32)), price_precision);
245 let size_increment = Quantity::new(10f64.powi(-(size_precision as i32)), size_precision);
246
247 CurrencyPair::new(
248 id,
249 symbol,
250 Currency::from(p.token0.symbol.as_str()),
251 Currency::from(p.token1.symbol.as_str()),
252 price_precision,
253 size_precision,
254 price_increment,
255 size_increment,
256 None,
257 None,
258 None,
259 None,
260 None,
261 None,
262 None,
263 None,
264 None,
265 None,
266 None,
267 None,
268 0.into(),
269 0.into(),
270 )
271 }
272}
273
274impl From<Pool> for InstrumentAny {
275 fn from(p: Pool) -> Self {
276 CurrencyPair::from(p).into_any()
277 }
278}
279
280#[cfg(test)]
285mod tests {
286 use rstest::rstest;
287
288 use super::DexType;
289
290 #[rstest]
291 fn test_dex_type_from_dex_name_valid() {
292 assert!(DexType::from_dex_name("UniswapV3").is_some());
294 assert!(DexType::from_dex_name("SushiSwapV2").is_some());
295 assert!(DexType::from_dex_name("BalancerV2").is_some());
296 assert!(DexType::from_dex_name("CamelotV3").is_some());
297
298 let uniswap_v3 = DexType::from_dex_name("UniswapV3").unwrap();
300 assert_eq!(uniswap_v3, DexType::UniswapV3);
301
302 let aerodrome_slipstream = DexType::from_dex_name("AerodromeSlipstream").unwrap();
304 assert_eq!(aerodrome_slipstream, DexType::AerodromeSlipstream);
305
306 let fluid_dex = DexType::from_dex_name("FluidDEX").unwrap();
308 assert_eq!(fluid_dex, DexType::FluidDEX);
309 }
310
311 #[rstest]
312 fn test_dex_type_from_dex_name_invalid() {
313 assert!(DexType::from_dex_name("InvalidDEX").is_none());
315 assert!(DexType::from_dex_name("").is_none());
316 assert!(DexType::from_dex_name("NonExistentDEX").is_none());
317 }
318
319 #[rstest]
320 fn test_dex_type_from_dex_name_case_sensitive() {
321 assert!(DexType::from_dex_name("UniswapV3").is_some());
323 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());
328 assert!(DexType::from_dex_name("sushiswapv2").is_none()); }
330
331 #[rstest]
332 fn test_dex_type_all_variants_mappable() {
333 let all_dex_names = vec![
335 "AerodromeSlipstream",
336 "AerodromeV1",
337 "BalancerV2",
338 "BalancerV3",
339 "BaseSwapV2",
340 "BaseX",
341 "CamelotV3",
342 "CurveFinance",
343 "FluidDEX",
344 "MaverickV1",
345 "MaverickV2",
346 "PancakeSwapV3",
347 "SushiSwapV2",
348 "SushiSwapV3",
349 "UniswapV2",
350 "UniswapV3",
351 "UniswapV4",
352 ];
353
354 for dex_name in all_dex_names {
355 assert!(
356 DexType::from_dex_name(dex_name).is_some(),
357 "DEX name '{dex_name}' should be valid but was not found",
358 );
359 }
360 }
361
362 #[rstest]
363 fn test_dex_type_display() {
364 assert_eq!(DexType::UniswapV3.to_string(), "UniswapV3");
366 assert_eq!(DexType::SushiSwapV2.to_string(), "SushiSwapV2");
367 assert_eq!(
368 DexType::AerodromeSlipstream.to_string(),
369 "AerodromeSlipstream"
370 );
371 assert_eq!(DexType::FluidDEX.to_string(), "FluidDEX");
372 }
373}