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<Self> {
100 Self::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)]
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 = match validate_address(factory) {
187 Ok(address) => address,
188 Err(e) => panic!(
189 "Invalid factory address for DEX {name} on chain {chain} for factory address {factory}: {e}"
190 ),
191 };
192 Self {
193 chain,
194 name,
195 factory: factory_address,
196 factory_creation_block,
197 pool_created_event: encoded_pool_created_event.into(),
198 initialize_event: None,
199 swap_created_event: encoded_swap_event.into(),
200 mint_created_event: encoded_mint_event.into(),
201 burn_created_event: encoded_burn_event.into(),
202 collect_created_event: encoded_collect_event.into(),
203 flash_created_event: None,
204 amm_type,
205 pairs: vec![],
206 }
207 }
208
209 pub fn id(&self) -> String {
211 format!("{}:{}", self.chain.name, self.name)
212 }
213
214 pub fn set_initialize_event(&mut self, event: &str) {
216 let initialize_event_hash = keccak256(event.as_bytes());
217 let encoded_initialized_event = format!(
218 "0x{encoded_hash}",
219 encoded_hash = hex::encode(initialize_event_hash)
220 );
221 self.initialize_event = Some(encoded_initialized_event.into());
222 }
223
224 pub fn set_flash_event(&mut self, event: &str) {
226 let flash_event_hash = keccak256(event.as_bytes());
227 let encoded_flash_event = format!(
228 "0x{encoded_hash}",
229 encoded_hash = hex::encode(flash_event_hash)
230 );
231 self.flash_created_event = Some(encoded_flash_event.into());
232 }
233}
234
235impl Display for Dex {
236 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237 write!(f, "Dex(chain={}, name={})", self.chain, self.name)
238 }
239}
240
241impl From<Pool> for CurrencyPair {
242 fn from(p: Pool) -> Self {
243 let symbol = Symbol::from(format!("{}/{}", p.token0.symbol, p.token1.symbol));
244 let id = InstrumentId::new(symbol, Venue::from(p.dex.id()));
245
246 let size_precision = p.token0.decimals.min(FIXED_PRECISION);
247 let price_precision = p.token1.decimals.min(FIXED_PRECISION);
248
249 let price_increment = Price::new(10f64.powi(-(price_precision as i32)), price_precision);
250 let size_increment = Quantity::new(10f64.powi(-(size_precision as i32)), size_precision);
251
252 Self::new(
253 id,
254 symbol,
255 Currency::from(p.token0.symbol.as_str()),
256 Currency::from(p.token1.symbol.as_str()),
257 price_precision,
258 size_precision,
259 price_increment,
260 size_increment,
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}