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