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