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