nautilus_blockchain/hypersync/
helpers.rs1use alloy::primitives::Address;
17
18use super::HypersyncLog;
19use crate::exchanges::parsing::core;
20
21pub fn extract_address_from_topic(
27 log: &HypersyncLog,
28 topic_index: usize,
29 description: &str,
30) -> anyhow::Result<Address> {
31 match log.topics.get(topic_index).and_then(|t| t.as_ref()) {
32 Some(topic) => core::extract_address_from_bytes(topic.as_ref()),
33 None => {
34 anyhow::bail!("Missing {description} address in topic{topic_index} when parsing event")
35 }
36 }
37}
38
39pub fn extract_transaction_hash(log: &HypersyncLog) -> anyhow::Result<String> {
45 log.transaction_hash
46 .as_ref()
47 .map(ToString::to_string)
48 .ok_or_else(|| anyhow::anyhow!("Missing transaction hash in log"))
49}
50
51pub fn extract_transaction_index(log: &HypersyncLog) -> anyhow::Result<u32> {
57 log.transaction_index
58 .as_ref()
59 .map(|index| **index as u32)
60 .ok_or_else(|| anyhow::anyhow!("Missing transaction index in the log"))
61}
62
63pub fn extract_log_index(log: &HypersyncLog) -> anyhow::Result<u32> {
69 log.log_index
70 .as_ref()
71 .map(|index| **index as u32)
72 .ok_or_else(|| anyhow::anyhow!("Missing log index in the log"))
73}
74
75pub fn extract_block_number(log: &HypersyncLog) -> anyhow::Result<u64> {
81 log.block_number
82 .as_ref()
83 .map(|number| **number)
84 .ok_or_else(|| anyhow::anyhow!("Missing block number in the log"))
85}
86
87pub fn extract_event_signature(log: &HypersyncLog) -> anyhow::Result<String> {
93 if let Some(topic) = log.topics.first().and_then(|t| t.as_ref()) {
94 Ok(hex::encode(topic))
95 } else {
96 anyhow::bail!("Missing event signature in topic0");
97 }
98}
99
100pub fn extract_event_signature_bytes(log: &HypersyncLog) -> anyhow::Result<&[u8]> {
106 if let Some(topic) = log.topics.first().and_then(|t| t.as_ref()) {
107 Ok(topic.as_ref())
108 } else {
109 anyhow::bail!("Missing event signature in topic0");
110 }
111}
112
113pub fn validate_event_signature_hash(
119 event_name: &str,
120 target_event_signature_hash: &str,
121 log: &HypersyncLog,
122) -> anyhow::Result<()> {
123 let sig_bytes = extract_event_signature_bytes(log)?;
124 core::validate_signature_bytes(sig_bytes, target_event_signature_hash, event_name)
125}
126
127#[cfg(test)]
128mod tests {
129 use rstest::*;
130 use serde_json::json;
131
132 use super::*;
133
134 #[fixture]
135 fn swap_log_1() -> HypersyncLog {
136 let log_json = json!({
137 "removed": null,
138 "log_index": null,
139 "transaction_index": null,
140 "transaction_hash": null,
141 "block_hash": null,
142 "block_number": "0x1581b7e",
143 "address": "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640",
144 "data": "0x",
145 "topics": [
146 "0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67",
147 "0x0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad",
148 "0x0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad",
149 null
150 ]
151 });
152 serde_json::from_value(log_json).expect("Failed to deserialize log")
153 }
154
155 #[fixture]
156 fn swap_log_2() -> HypersyncLog {
157 let log_json = json!({
158 "removed": null,
159 "log_index": null,
160 "transaction_index": null,
161 "transaction_hash": null,
162 "block_hash": null,
163 "block_number": "0x1581b82",
164 "address": "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640",
165 "data": "0x",
166 "topics": [
167 "0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67",
168 "0x00000000000000000000000066a9893cc07d91d95644aedd05d03f95e1dba8af",
169 "0x000000000000000000000000f90321d0ecad58ab2b0c8c79db8aaeeefa023578",
170 null
171 ]
172 });
173 serde_json::from_value(log_json).expect("Failed to deserialize log")
174 }
175
176 #[fixture]
177 fn log_without_topics() -> HypersyncLog {
178 let log_json = json!({
179 "removed": null,
180 "log_index": null,
181 "transaction_index": null,
182 "transaction_hash": null,
183 "block_hash": null,
184 "block_number": "0x1581b82",
185 "address": "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640",
186 "data": "0x",
187 "topics": []
188 });
189 serde_json::from_value(log_json).expect("Failed to deserialize log")
190 }
191
192 #[fixture]
193 fn log_with_none_topic0() -> HypersyncLog {
194 let log_json = json!({
195 "removed": null,
196 "log_index": null,
197 "transaction_index": null,
198 "transaction_hash": null,
199 "block_hash": null,
200 "block_number": "0x1581b82",
201 "address": "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640",
202 "data": "0x",
203 "topics": [null]
204 });
205 serde_json::from_value(log_json).expect("Failed to deserialize log")
206 }
207
208 #[rstest]
209 fn test_validate_event_signature_hash_success(swap_log_1: HypersyncLog) {
210 let expected_hash = "c42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67";
212
213 let result = validate_event_signature_hash("Swap", expected_hash, &swap_log_1);
214 assert!(result.is_ok());
215 }
216
217 #[rstest]
218 fn test_validate_event_signature_hash_success_log2(swap_log_2: HypersyncLog) {
219 let expected_hash = "c42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67";
221
222 let result = validate_event_signature_hash("Swap", expected_hash, &swap_log_2);
223 assert!(result.is_ok());
224 }
225
226 #[rstest]
227 fn test_validate_event_signature_hash_mismatch(swap_log_1: HypersyncLog) {
228 let wrong_hash = "ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
230
231 let result = validate_event_signature_hash("Transfer", wrong_hash, &swap_log_1);
232 assert!(result.is_err());
233 assert!(
234 result
235 .unwrap_err()
236 .to_string()
237 .contains("Invalid event signature for 'Transfer'")
238 );
239 }
240
241 #[rstest]
242 fn test_validate_event_signature_hash_missing_topic0(log_without_topics: HypersyncLog) {
243 let expected_hash = "c42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67";
244
245 let result = validate_event_signature_hash("Swap", expected_hash, &log_without_topics);
246 assert!(result.is_err());
247 assert_eq!(
248 result.unwrap_err().to_string(),
249 "Missing event signature in topic0"
250 );
251 }
252
253 #[rstest]
254 fn test_validate_event_signature_hash_none_topic0(log_with_none_topic0: HypersyncLog) {
255 let expected_hash = "c42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67";
256
257 let result = validate_event_signature_hash("Swap", expected_hash, &log_with_none_topic0);
258 assert!(result.is_err());
259 assert_eq!(
260 result.unwrap_err().to_string(),
261 "Missing event signature in topic0"
262 );
263 }
264
265 #[rstest]
266 fn test_extract_transaction_hash_success() {
267 let log_json = json!({
268 "removed": null,
269 "log_index": null,
270 "transaction_index": null,
271 "transaction_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
272 "block_hash": null,
273 "block_number": "0x1581b82",
274 "address": "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640",
275 "data": "0x",
276 "topics": []
277 });
278 let log: HypersyncLog =
279 serde_json::from_value(log_json).expect("Failed to deserialize log");
280
281 let result = extract_transaction_hash(&log);
282 assert!(result.is_ok());
283 assert_eq!(
284 result.unwrap(),
285 "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
286 );
287 }
288
289 #[rstest]
290 fn test_extract_transaction_hash_missing() {
291 let log_json = json!({
292 "removed": null,
293 "log_index": null,
294 "transaction_index": null,
295 "transaction_hash": null,
296 "block_hash": null,
297 "block_number": "0x1581b82",
298 "address": "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640",
299 "data": "0x",
300 "topics": []
301 });
302 let log: HypersyncLog =
303 serde_json::from_value(log_json).expect("Failed to deserialize log");
304
305 let result = extract_transaction_hash(&log);
306 assert!(result.is_err());
307 assert_eq!(
308 result.unwrap_err().to_string(),
309 "Missing transaction hash in log"
310 );
311 }
312
313 #[rstest]
314 fn test_extract_transaction_index_success() {
315 let log_json = json!({
316 "removed": null,
317 "log_index": null,
318 "transaction_index": "0x5",
319 "transaction_hash": null,
320 "block_hash": null,
321 "block_number": "0x1581b82",
322 "address": "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640",
323 "data": "0x",
324 "topics": []
325 });
326 let log: HypersyncLog =
327 serde_json::from_value(log_json).expect("Failed to deserialize log");
328
329 let result = extract_transaction_index(&log);
330 assert!(result.is_ok());
331 assert_eq!(result.unwrap(), 5u32);
332 }
333
334 #[rstest]
335 fn test_extract_transaction_index_missing() {
336 let log_json = json!({
337 "removed": null,
338 "log_index": null,
339 "transaction_index": null,
340 "transaction_hash": null,
341 "block_hash": null,
342 "block_number": "0x1581b82",
343 "address": "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640",
344 "data": "0x",
345 "topics": []
346 });
347 let log: HypersyncLog =
348 serde_json::from_value(log_json).expect("Failed to deserialize log");
349
350 let result = extract_transaction_index(&log);
351 assert!(result.is_err());
352 assert_eq!(
353 result.unwrap_err().to_string(),
354 "Missing transaction index in the log"
355 );
356 }
357
358 #[rstest]
359 fn test_extract_log_index_success() {
360 let log_json = json!({
361 "removed": null,
362 "log_index": "0xa",
363 "transaction_index": null,
364 "transaction_hash": null,
365 "block_hash": null,
366 "block_number": "0x1581b82",
367 "address": "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640",
368 "data": "0x",
369 "topics": []
370 });
371 let log: HypersyncLog =
372 serde_json::from_value(log_json).expect("Failed to deserialize log");
373
374 let result = extract_log_index(&log);
375 assert!(result.is_ok());
376 assert_eq!(result.unwrap(), 10u32);
377 }
378
379 #[rstest]
380 fn test_extract_log_index_missing() {
381 let log_json = json!({
382 "removed": null,
383 "log_index": null,
384 "transaction_index": null,
385 "transaction_hash": null,
386 "block_hash": null,
387 "block_number": "0x1581b82",
388 "address": "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640",
389 "data": "0x",
390 "topics": []
391 });
392 let log: HypersyncLog =
393 serde_json::from_value(log_json).expect("Failed to deserialize log");
394
395 let result = extract_log_index(&log);
396 assert!(result.is_err());
397 assert_eq!(
398 result.unwrap_err().to_string(),
399 "Missing log index in the log"
400 );
401 }
402
403 #[rstest]
404 fn test_extract_block_number_success() {
405 let log_json = json!({
406 "removed": null,
407 "log_index": null,
408 "transaction_index": null,
409 "transaction_hash": null,
410 "block_hash": null,
411 "block_number": "0x1581b82",
412 "address": "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640",
413 "data": "0x",
414 "topics": []
415 });
416 let log: HypersyncLog =
417 serde_json::from_value(log_json).expect("Failed to deserialize log");
418
419 let result = extract_block_number(&log);
420 assert!(result.is_ok());
421 assert_eq!(result.unwrap(), 22551426u64); }
423
424 #[rstest]
425 fn test_extract_block_number_missing() {
426 let log_json = json!({
427 "removed": null,
428 "log_index": null,
429 "transaction_index": null,
430 "transaction_hash": null,
431 "block_hash": null,
432 "block_number": null,
433 "address": "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640",
434 "data": "0x",
435 "topics": []
436 });
437 let log: HypersyncLog =
438 serde_json::from_value(log_json).expect("Failed to deserialize log");
439
440 let result = extract_block_number(&log);
441 assert!(result.is_err());
442 assert_eq!(
443 result.unwrap_err().to_string(),
444 "Missing block number in the log"
445 );
446 }
447
448 #[rstest]
449 fn test_extract_address_from_topic_success(swap_log_1: HypersyncLog) {
450 let result = extract_address_from_topic(&swap_log_1, 1, "sender");
452 assert!(result.is_ok());
453 let address = result.unwrap();
454 assert_eq!(
455 address.to_string().to_lowercase(),
456 "0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad"
457 );
458 }
459
460 #[rstest]
461 fn test_extract_address_from_topic_success_log2(swap_log_2: HypersyncLog) {
462 let result = extract_address_from_topic(&swap_log_2, 1, "sender");
464 assert!(result.is_ok());
465 let address = result.unwrap();
466 assert_eq!(
467 address.to_string().to_lowercase(),
468 "0x66a9893cc07d91d95644aedd05d03f95e1dba8af"
469 );
470
471 let result = extract_address_from_topic(&swap_log_2, 2, "recipient");
473 assert!(result.is_ok());
474 let address = result.unwrap();
475 assert_eq!(
476 address.to_string().to_lowercase(),
477 "0xf90321d0ecad58ab2b0c8c79db8aaeeefa023578"
478 );
479 }
480
481 #[rstest]
482 fn test_extract_address_from_topic_missing_topic(swap_log_1: HypersyncLog) {
483 let result = extract_address_from_topic(&swap_log_1, 5, "nonexistent");
485 assert!(result.is_err());
486 assert_eq!(
487 result.unwrap_err().to_string(),
488 "Missing nonexistent address in topic5 when parsing event"
489 );
490 }
491
492 #[rstest]
493 fn test_extract_address_from_topic_none_topic(swap_log_1: HypersyncLog) {
494 let result = extract_address_from_topic(&swap_log_1, 3, "null_topic");
496 assert!(result.is_err());
497 assert_eq!(
498 result.unwrap_err().to_string(),
499 "Missing null_topic address in topic3 when parsing event"
500 );
501 }
502
503 #[rstest]
504 fn test_extract_address_from_topic_no_topics(log_without_topics: HypersyncLog) {
505 let result = extract_address_from_topic(&log_without_topics, 1, "sender");
506 assert!(result.is_err());
507 assert_eq!(
508 result.unwrap_err().to_string(),
509 "Missing sender address in topic1 when parsing event"
510 );
511 }
512}