nautilus_blockchain/exchanges/parsing/uniswap_v3/
pool_created.rs1use alloy::primitives::{Address, U256};
17use nautilus_model::defi::{PoolIdentifier, rpc::RpcLog};
18use ustr::Ustr;
19
20use crate::{
21 events::pool_created::PoolCreatedEvent,
22 exchanges::parsing::core,
23 hypersync::{
24 HypersyncLog,
25 helpers::{
26 extract_address_from_topic, extract_block_number, validate_event_signature_hash,
27 },
28 },
29 rpc::helpers as rpc_helpers,
30};
31
32const POOL_CREATED_EVENT_SIGNATURE_HASH: &str =
33 "783cca1c0412dd0d695e784568c96da2e9c22ff989357a2e8b1d9b2b4e6b7118";
34
35pub fn parse_pool_created_event_hypersync(log: HypersyncLog) -> anyhow::Result<PoolCreatedEvent> {
45 validate_event_signature_hash("PoolCreatedEvent", POOL_CREATED_EVENT_SIGNATURE_HASH, &log)?;
46
47 let block_number = extract_block_number(&log)?;
48
49 let token = extract_address_from_topic(&log, 1, "token0")?;
50 let token1 = extract_address_from_topic(&log, 2, "token1")?;
51
52 let fee = if let Some(topic) = log.topics.get(3).and_then(|t| t.as_ref()) {
53 U256::from_be_slice(topic.as_ref()).as_limbs()[0] as u32
54 } else {
55 anyhow::bail!("Missing fee in topic3 when parsing pool created event");
56 };
57
58 if let Some(data) = log.data {
59 let data_bytes = data.as_ref();
61
62 let tick_spacing_bytes: [u8; 32] = data_bytes[0..32].try_into()?;
64 let tick_spacing = u32::from_be_bytes(tick_spacing_bytes[28..32].try_into()?);
65
66 let pool_address_bytes: [u8; 32] = data_bytes[32..64].try_into()?;
68 let pool_address = Address::from_slice(&pool_address_bytes[12..32]);
69
70 Ok(PoolCreatedEvent::new(
71 block_number,
72 token,
73 token1,
74 pool_address,
75 PoolIdentifier::Address(Ustr::from(&pool_address.to_string())), Some(fee),
77 Some(tick_spacing),
78 ))
79 } else {
80 Err(anyhow::anyhow!("Missing data in pool created event log"))
81 }
82}
83
84pub fn parse_pool_created_event_rpc(log: &RpcLog) -> anyhow::Result<PoolCreatedEvent> {
90 rpc_helpers::validate_event_signature(
91 log,
92 POOL_CREATED_EVENT_SIGNATURE_HASH,
93 "PoolCreatedEvent",
94 )?;
95
96 let block_number = rpc_helpers::extract_block_number(log)?;
97 let token0 = rpc_helpers::extract_address_from_topic(log, 1, "token0")?;
98 let token1 = rpc_helpers::extract_address_from_topic(log, 2, "token1")?;
99
100 let fee_bytes = rpc_helpers::extract_topic_bytes(log, 3)?;
102 let fee = core::extract_u32_from_bytes(&fee_bytes)?;
103
104 let data_bytes = rpc_helpers::extract_data_bytes(log)?;
106
107 anyhow::ensure!(
108 data_bytes.len() >= 64,
109 "Pool created event data too short: expected at least 64 bytes, got {}",
110 data_bytes.len()
111 );
112
113 let tick_spacing = u32::from_be_bytes(data_bytes[28..32].try_into()?);
114 let pool_address = Address::from_slice(&data_bytes[44..64]);
115
116 Ok(PoolCreatedEvent::new(
117 block_number,
118 token0,
119 token1,
120 pool_address,
121 PoolIdentifier::Address(Ustr::from(&pool_address.to_string())), Some(fee),
123 Some(tick_spacing),
124 ))
125}
126
127#[cfg(test)]
128mod tests {
129 use rstest::{fixture, rstest};
130 use serde_json::json;
131
132 use super::*;
133
134 #[fixture]
141 fn hypersync_log_block_185() -> HypersyncLog {
142 let log_json = json!({
143 "removed": null,
144 "log_index": "0x0",
145 "transaction_index": "0x0",
146 "transaction_hash": "0x24058dde7caf5b8b70041de8b27731f20f927365f210247c3e720e947b9098e7",
147 "block_hash": null,
148 "block_number": "0xb9",
149 "address": "0x1f98431c8ad98523631ae4a59f267346ea31f984",
150 "data": "0x000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000b9fc136980d98c034a529aadbd5651c087365d5f",
151 "topics": [
152 "0x783cca1c0412dd0d695e784568c96da2e9c22ff989357a2e8b1d9b2b4e6b7118",
153 "0x0000000000000000000000002e5353426c89f4ecd52d1036da822d47e73376c4",
154 "0x000000000000000000000000838930cfe7502dd36b0b1ebbef8001fbf94f3bfb",
155 "0x0000000000000000000000000000000000000000000000000000000000000bb8"
156 ]
157 });
158 serde_json::from_value(log_json).expect("Failed to deserialize HyperSync log")
159 }
160
161 #[fixture]
162 fn rpc_log_block_185() -> RpcLog {
163 RpcLog {
164 removed: false,
165 log_index: Some("0x0".to_string()),
166 transaction_index: Some("0x0".to_string()),
167 transaction_hash: Some(
168 "0x24058dde7caf5b8b70041de8b27731f20f927365f210247c3e720e947b9098e7".to_string(),
169 ),
170 block_hash: Some(
171 "0xd371b6c7b04ec33d6470f067a82e87d7b294b952bea7a46d7b939b4c7addc275".to_string(),
172 ),
173 block_number: Some("0xb9".to_string()),
174 address: "0x1f98431c8ad98523631ae4a59f267346ea31f984".to_string(),
175 data: "0x000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000b9fc136980d98c034a529aadbd5651c087365d5f".to_string(),
176 topics: vec![
177 "0x783cca1c0412dd0d695e784568c96da2e9c22ff989357a2e8b1d9b2b4e6b7118".to_string(),
178 "0x0000000000000000000000002e5353426c89f4ecd52d1036da822d47e73376c4".to_string(),
179 "0x000000000000000000000000838930cfe7502dd36b0b1ebbef8001fbf94f3bfb".to_string(),
180 "0x0000000000000000000000000000000000000000000000000000000000000bb8".to_string(),
181 ],
182 }
183 }
184
185 #[fixture]
192 fn hypersync_log_block_540() -> HypersyncLog {
193 let log_json = json!({
194 "removed": null,
195 "log_index": "0x0",
196 "transaction_index": "0x0",
197 "transaction_hash": "0x0810b3488eba9b0264d3544b4548b70d0c8667e05ac4a5d90686f4a9f70509df",
198 "block_hash": null,
199 "block_number": "0x21c",
200 "address": "0x1f98431c8ad98523631ae4a59f267346ea31f984",
201 "data": "0x000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000007d25de0bb3e4e4d5f7b399db5a0bca9f60dd66e4",
202 "topics": [
203 "0x783cca1c0412dd0d695e784568c96da2e9c22ff989357a2e8b1d9b2b4e6b7118",
204 "0x0000000000000000000000008dd7c686b11c115ffaba245cbfc418b371087f68",
205 "0x000000000000000000000000be5381d826375492e55e05039a541eb2cb978e76",
206 "0x00000000000000000000000000000000000000000000000000000000000001f4"
207 ]
208 });
209 serde_json::from_value(log_json).expect("Failed to deserialize HyperSync log")
210 }
211
212 #[fixture]
213 fn rpc_log_block_540() -> RpcLog {
214 RpcLog {
215 removed: false,
216 log_index: Some("0x0".to_string()),
217 transaction_index: Some("0x0".to_string()),
218 transaction_hash: Some(
219 "0x0810b3488eba9b0264d3544b4548b70d0c8667e05ac4a5d90686f4a9f70509df".to_string(),
220 ),
221 block_hash: Some(
222 "0x59bb10cdfd586affc6aa4a0b12f0662ec04599a1a459ac5b33129bc2c8705ccd".to_string(),
223 ),
224 block_number: Some("0x21c".to_string()),
225 address: "0x1f98431c8ad98523631ae4a59f267346ea31f984".to_string(),
226 data: "0x000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000007d25de0bb3e4e4d5f7b399db5a0bca9f60dd66e4".to_string(),
227 topics: vec![
228 "0x783cca1c0412dd0d695e784568c96da2e9c22ff989357a2e8b1d9b2b4e6b7118".to_string(),
229 "0x0000000000000000000000008dd7c686b11c115ffaba245cbfc418b371087f68".to_string(),
230 "0x000000000000000000000000be5381d826375492e55e05039a541eb2cb978e76".to_string(),
231 "0x00000000000000000000000000000000000000000000000000000000000001f4".to_string(),
232 ],
233 }
234 }
235
236 #[rstest]
239 fn test_parse_pool_created_hypersync_block_185(hypersync_log_block_185: HypersyncLog) {
240 let event =
241 parse_pool_created_event_hypersync(hypersync_log_block_185).expect("Failed to parse");
242
243 assert_eq!(event.block_number, 185);
244 assert_eq!(
245 event.token0.to_string().to_lowercase(),
246 "0x2e5353426c89f4ecd52d1036da822d47e73376c4"
247 );
248 assert_eq!(
249 event.token1.to_string().to_lowercase(),
250 "0x838930cfe7502dd36b0b1ebbef8001fbf94f3bfb"
251 );
252 assert_eq!(
253 event.pool_identifier.to_string(),
254 "0xB9Fc136980D98C034a529AadbD5651c087365D5f"
255 );
256 assert_eq!(event.fee, Some(3000));
257 assert_eq!(event.tick_spacing, Some(60));
258 }
259
260 #[rstest]
261 fn test_parse_pool_created_hypersync_block_540(hypersync_log_block_540: HypersyncLog) {
262 let event =
263 parse_pool_created_event_hypersync(hypersync_log_block_540).expect("Failed to parse");
264
265 assert_eq!(event.block_number, 540);
266 assert_eq!(
267 event.token0.to_string().to_lowercase(),
268 "0x8dd7c686b11c115ffaba245cbfc418b371087f68"
269 );
270 assert_eq!(
271 event.token1.to_string().to_lowercase(),
272 "0xbe5381d826375492e55e05039a541eb2cb978e76"
273 );
274 assert_eq!(
275 event.pool_identifier.to_string(),
276 "0x7d25DE0bB3e4E4d5F7b399db5A0BCa9F60dD66e4"
277 );
278 assert_eq!(event.fee, Some(500));
279 assert_eq!(event.tick_spacing, Some(10));
280 }
281
282 #[rstest]
285 fn test_parse_pool_created_rpc_block_185(rpc_log_block_185: RpcLog) {
286 let event = parse_pool_created_event_rpc(&rpc_log_block_185).expect("Failed to parse");
287
288 assert_eq!(event.block_number, 185);
289 assert_eq!(
290 event.token0.to_string().to_lowercase(),
291 "0x2e5353426c89f4ecd52d1036da822d47e73376c4"
292 );
293 assert_eq!(
294 event.token1.to_string().to_lowercase(),
295 "0x838930cfe7502dd36b0b1ebbef8001fbf94f3bfb"
296 );
297 assert_eq!(
298 event.pool_identifier.to_string(),
299 "0xB9Fc136980D98C034a529AadbD5651c087365D5f"
300 );
301 assert_eq!(event.fee, Some(3000));
302 assert_eq!(event.tick_spacing, Some(60));
303 }
304
305 #[rstest]
306 fn test_parse_pool_created_rpc_block_540(rpc_log_block_540: RpcLog) {
307 let event = parse_pool_created_event_rpc(&rpc_log_block_540).expect("Failed to parse");
308
309 assert_eq!(event.block_number, 540);
310 assert_eq!(
311 event.token0.to_string().to_lowercase(),
312 "0x8dd7c686b11c115ffaba245cbfc418b371087f68"
313 );
314 assert_eq!(
315 event.token1.to_string().to_lowercase(),
316 "0xbe5381d826375492e55e05039a541eb2cb978e76"
317 );
318 assert_eq!(
319 event.pool_identifier.to_string(),
320 "0x7d25DE0bB3e4E4d5F7b399db5A0BCa9F60dD66e4"
321 );
322 assert_eq!(event.fee, Some(500));
323 assert_eq!(event.tick_spacing, Some(10));
324 }
325
326 #[rstest]
329 fn test_hypersync_rpc_match_block_185(
330 hypersync_log_block_185: HypersyncLog,
331 rpc_log_block_185: RpcLog,
332 ) {
333 let hypersync_event =
334 parse_pool_created_event_hypersync(hypersync_log_block_185).expect("HyperSync parse");
335 let rpc_event = parse_pool_created_event_rpc(&rpc_log_block_185).expect("RPC parse");
336
337 assert_eq!(hypersync_event.block_number, rpc_event.block_number);
338 assert_eq!(hypersync_event.token0, rpc_event.token0);
339 assert_eq!(hypersync_event.token1, rpc_event.token1);
340 assert_eq!(hypersync_event.pool_identifier, rpc_event.pool_identifier);
341 assert_eq!(hypersync_event.fee, rpc_event.fee);
342 assert_eq!(hypersync_event.tick_spacing, rpc_event.tick_spacing);
343 }
344
345 #[rstest]
346 fn test_hypersync_rpc_match_block_540(
347 hypersync_log_block_540: HypersyncLog,
348 rpc_log_block_540: RpcLog,
349 ) {
350 let hypersync_event =
351 parse_pool_created_event_hypersync(hypersync_log_block_540).expect("HyperSync parse");
352 let rpc_event = parse_pool_created_event_rpc(&rpc_log_block_540).expect("RPC parse");
353
354 assert_eq!(hypersync_event.block_number, rpc_event.block_number);
355 assert_eq!(hypersync_event.token0, rpc_event.token0);
356 assert_eq!(hypersync_event.token1, rpc_event.token1);
357 assert_eq!(hypersync_event.pool_identifier, rpc_event.pool_identifier);
358 assert_eq!(hypersync_event.fee, rpc_event.fee);
359 assert_eq!(hypersync_event.tick_spacing, rpc_event.tick_spacing);
360 }
361}