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