1use std::{fmt::Display, sync::Arc};
19
20use alloy_primitives::Address;
21use nautilus_core::UnixNanos;
22use serde::{Deserialize, Serialize};
23
24use crate::{
25 data::HasTsInit,
26 defi::{Blockchain, SharedDex, chain::SharedChain, dex::Dex, token::Token},
27 identifiers::{InstrumentId, Symbol, Venue},
28};
29
30#[cfg_attr(
42 feature = "python",
43 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
44)]
45#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
46pub struct Pool {
47 pub chain: SharedChain,
49 pub dex: SharedDex,
51 pub address: Address,
53 pub instrument_id: InstrumentId,
55 pub creation_block: u64,
57 pub token0: Token,
59 pub token1: Token,
61 pub fee: Option<u32>,
69 pub tick_spacing: Option<u32>,
71 pub ts_init: UnixNanos,
73}
74
75pub type SharedPool = Arc<Pool>;
77
78impl Pool {
79 #[must_use]
81 #[allow(clippy::too_many_arguments)]
82 pub fn new(
83 chain: SharedChain,
84 dex: SharedDex,
85 address: Address,
86 creation_block: u64,
87 token0: Token,
88 token1: Token,
89 fee: Option<u32>,
90 tick_spacing: Option<u32>,
91 ts_init: UnixNanos,
92 ) -> Self {
93 let instrument_id = Self::create_instrument_id(chain.name, &dex, &address);
94
95 Self {
96 chain,
97 dex,
98 address,
99 instrument_id,
100 creation_block,
101 token0,
102 token1,
103 fee,
104 tick_spacing,
105 ts_init,
106 }
107 }
108
109 pub fn to_full_spec_string(&self) -> String {
111 format!(
112 "{}/{}-{}.{}",
113 self.token0.symbol,
114 self.token1.symbol,
115 self.fee.unwrap_or(0),
116 self.instrument_id.venue
117 )
118 }
119
120 pub fn create_instrument_id(chain: Blockchain, dex: &Dex, address: &Address) -> InstrumentId {
121 let symbol = Symbol::new(address.to_string());
122 let venue = Venue::new(format!("{}:{}", chain, dex.name));
123 InstrumentId::new(symbol, venue)
124 }
125}
126
127impl Display for Pool {
128 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129 write!(
130 f,
131 "Pool(instrument_id={}, dex={}, fee={}, address={})",
132 self.instrument_id,
133 self.dex.name,
134 self.fee
135 .map_or("None".to_string(), |fee| format!("fee={}, ", fee)),
136 self.address
137 )
138 }
139}
140
141impl HasTsInit for Pool {
142 fn ts_init(&self) -> UnixNanos {
143 self.ts_init
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use std::sync::Arc;
150
151 use rstest::rstest;
152
153 use super::*;
154 use crate::defi::{
155 chain::chains,
156 dex::{AmmType, Dex, DexType},
157 token::Token,
158 };
159
160 #[rstest]
161 fn test_pool_constructor_and_methods() {
162 let chain = Arc::new(chains::ETHEREUM.clone());
163 let dex = Dex::new(
164 chains::ETHEREUM.clone(),
165 DexType::UniswapV3,
166 "0x1F98431c8aD98523631AE4a59f267346ea31F984",
167 0,
168 AmmType::CLAMM,
169 "PoolCreated(address,address,uint24,int24,address)",
170 "Swap(address,address,int256,int256,uint160,uint128,int24)",
171 "Mint(address,address,int24,int24,uint128,uint256,uint256)",
172 "Burn(address,int24,int24,uint128,uint256,uint256)",
173 "Collect(address,address,int24,int24,uint128,uint128)",
174 );
175
176 let token0 = Token::new(
177 chain.clone(),
178 "0xA0b86a33E6441b936662bb6B5d1F8Fb0E2b57A5D"
179 .parse()
180 .unwrap(),
181 "Wrapped Ether".to_string(),
182 "WETH".to_string(),
183 18,
184 );
185
186 let token1 = Token::new(
187 chain.clone(),
188 "0xdAC17F958D2ee523a2206206994597C13D831ec7"
189 .parse()
190 .unwrap(),
191 "Tether USD".to_string(),
192 "USDT".to_string(),
193 6,
194 );
195
196 let pool_address = "0x11b815efB8f581194ae79006d24E0d814B7697F6"
197 .parse()
198 .unwrap();
199 let ts_init = UnixNanos::from(1_234_567_890_000_000_000u64);
200
201 let pool = Pool::new(
202 chain.clone(),
203 Arc::new(dex),
204 pool_address,
205 12345678,
206 token0,
207 token1,
208 Some(3000),
209 Some(60),
210 ts_init,
211 );
212
213 assert_eq!(pool.chain.chain_id, chain.chain_id);
214 assert_eq!(pool.dex.name, DexType::UniswapV3);
215 assert_eq!(pool.address, pool_address);
216 assert_eq!(pool.creation_block, 12345678);
217 assert_eq!(pool.token0.symbol, "WETH");
218 assert_eq!(pool.token1.symbol, "USDT");
219 assert_eq!(pool.fee.unwrap(), 3000);
220 assert_eq!(pool.tick_spacing.unwrap(), 60);
221 assert_eq!(pool.ts_init, ts_init);
222 assert_eq!(
223 pool.instrument_id.symbol.as_str(),
224 "0x11b815efB8f581194ae79006d24E0d814B7697F6"
225 );
226 assert_eq!(pool.instrument_id.venue.as_str(), "Ethereum:UniswapV3");
227 }
228
229 #[rstest]
230 fn test_pool_instrument_id_format() {
231 let chain = Arc::new(chains::ETHEREUM.clone());
232 let factory_address = "0x1F98431c8aD98523631AE4a59f267346ea31F984";
233
234 let dex = Dex::new(
235 chains::ETHEREUM.clone(),
236 DexType::UniswapV3,
237 factory_address,
238 0,
239 AmmType::CLAMM,
240 "PoolCreated(address,address,uint24,int24,address)",
241 "Swap(address,address,int256,int256,uint160,uint128,int24)",
242 "Mint(address,address,int24,int24,uint128,uint256,uint256)",
243 "Burn(address,int24,int24,uint128,uint256,uint256)",
244 "Collect(address,address,int24,int24,uint128,uint128)",
245 );
246
247 let token0 = Token::new(
248 chain.clone(),
249 "0xA0b86a33E6441b936662bb6B5d1F8Fb0E2b57A5D"
250 .parse()
251 .unwrap(),
252 "Wrapped Ether".to_string(),
253 "WETH".to_string(),
254 18,
255 );
256
257 let token1 = Token::new(
258 chain.clone(),
259 "0xdAC17F958D2ee523a2206206994597C13D831ec7"
260 .parse()
261 .unwrap(),
262 "Tether USD".to_string(),
263 "USDT".to_string(),
264 6,
265 );
266
267 let pool = Pool::new(
268 chain.clone(),
269 Arc::new(dex),
270 "0x11b815efB8f581194ae79006d24E0d814B7697F6"
271 .parse()
272 .unwrap(),
273 0,
274 token0,
275 token1,
276 Some(3000),
277 Some(60),
278 UnixNanos::default(),
279 );
280
281 assert_eq!(
282 pool.instrument_id.to_string(),
283 "0x11b815efB8f581194ae79006d24E0d814B7697F6.Ethereum:UniswapV3"
284 );
285 }
286}