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