1use std::{rc::Rc, sync::Arc};
22
23use nautilus_common::{
24 defi,
25 messages::defi::{
26 DefiRequestCommand, DefiSubscribeCommand, DefiUnsubscribeCommand, PoolSnapshotResponse,
27 RequestPoolSnapshot,
28 },
29 msgbus::{self, TypedHandler},
30};
31use nautilus_core::UUID4;
32use nautilus_model::{
33 defi::{
34 Blockchain, DefiData, PoolProfiler,
35 data::{DexPoolData, block::BlockPosition},
36 pool_analysis::PoolSnapshot,
37 },
38 identifiers::{ClientId, InstrumentId},
39 instruments::{CurrencyPair, InstrumentAny},
40};
41
42use crate::engine::{
43 DataEngine,
44 pool::{
45 PoolCollectHandler, PoolFlashHandler, PoolLiquidityHandler, PoolSwapHandler, PoolUpdater,
46 },
47};
48
49fn get_event_block_position(event: &DexPoolData) -> (u64, u32, u32) {
51 match event {
52 DexPoolData::Swap(s) => (s.block, s.transaction_index, s.log_index),
53 DexPoolData::LiquidityUpdate(u) => (u.block, u.transaction_index, u.log_index),
54 DexPoolData::FeeCollect(c) => (c.block, c.transaction_index, c.log_index),
55 DexPoolData::FeeProtocolUpdate(u) => (u.block, u.transaction_index, u.log_index),
56 DexPoolData::FeeProtocolCollect(c) => (c.block, c.transaction_index, c.log_index),
57 DexPoolData::Flash(f) => (f.block, f.transaction_index, f.log_index),
58 }
59}
60
61fn convert_and_sort_buffered_events(buffered_events: Vec<DefiData>) -> Vec<DexPoolData> {
63 let mut events: Vec<DexPoolData> = buffered_events
64 .into_iter()
65 .filter_map(|event| match event {
66 DefiData::PoolSwap(swap) => Some(DexPoolData::Swap(swap)),
67 DefiData::PoolLiquidityUpdate(update) => Some(DexPoolData::LiquidityUpdate(update)),
68 DefiData::PoolFeeCollect(collect) => Some(DexPoolData::FeeCollect(collect)),
69 DefiData::PoolFeeProtocolUpdate(update) => Some(DexPoolData::FeeProtocolUpdate(update)),
70 DefiData::PoolFeeProtocolCollect(collect) => {
71 Some(DexPoolData::FeeProtocolCollect(collect))
72 }
73 DefiData::PoolFlash(flash) => Some(DexPoolData::Flash(flash)),
74 _ => None,
75 })
76 .collect();
77
78 events.sort_by(|a, b| {
79 let pos_a = get_event_block_position(a);
80 let pos_b = get_event_block_position(b);
81 pos_a.cmp(&pos_b)
82 });
83
84 events
85}
86
87impl DataEngine {
88 #[must_use]
90 pub fn subscribed_blocks(&self) -> Vec<Blockchain> {
91 self.collect_subscriptions(|client| &client.subscriptions_blocks)
92 }
93
94 #[must_use]
96 pub fn subscribed_pools(&self) -> Vec<InstrumentId> {
97 self.collect_subscriptions(|client| &client.subscriptions_pools)
98 }
99
100 #[must_use]
102 pub fn subscribed_pool_swaps(&self) -> Vec<InstrumentId> {
103 self.collect_subscriptions(|client| &client.subscriptions_pool_swaps)
104 }
105
106 #[must_use]
108 pub fn subscribed_pool_liquidity_updates(&self) -> Vec<InstrumentId> {
109 self.collect_subscriptions(|client| &client.subscriptions_pool_liquidity_updates)
110 }
111
112 #[must_use]
114 pub fn subscribed_pool_fee_collects(&self) -> Vec<InstrumentId> {
115 self.collect_subscriptions(|client| &client.subscriptions_pool_fee_collects)
116 }
117
118 #[must_use]
120 pub fn subscribed_pool_flash(&self) -> Vec<InstrumentId> {
121 self.collect_subscriptions(|client| &client.subscriptions_pool_flash)
122 }
123
124 pub fn execute_defi_subscribe(&mut self, cmd: DefiSubscribeCommand) -> anyhow::Result<()> {
131 if let Some(client_id) = cmd.client_id()
132 && self.is_external_client(*client_id)
133 {
134 if self.config().debug {
135 log::debug!("Skipping defi subscribe for external client {client_id}: {cmd:?}");
136 }
137 return Ok(());
138 }
139
140 let Some(client) = self.get_client(cmd.client_id(), cmd.venue()) else {
141 log::error!(
142 "Cannot handle command: no client found for client_id={:?}, venue={:?}",
143 cmd.client_id(),
144 cmd.venue(),
145 );
146 return Ok(());
147 };
148
149 #[rustfmt::skip]
150 let pool = match &cmd {
151 DefiSubscribeCommand::Pool(cmd) => Some((cmd.instrument_id, cmd.client_id)),
152 DefiSubscribeCommand::PoolSwaps(cmd) => Some((cmd.instrument_id, cmd.client_id)),
153 DefiSubscribeCommand::PoolLiquidityUpdates(cmd) => Some((cmd.instrument_id, cmd.client_id)),
154 DefiSubscribeCommand::PoolFeeCollects(cmd) => Some((cmd.instrument_id, cmd.client_id)),
155 DefiSubscribeCommand::PoolFlashEvents(cmd) => Some((cmd.instrument_id, cmd.client_id)),
156 DefiSubscribeCommand::Blocks(_) => None, };
158
159 log::info!("Forwarding subscription to client {}", client.client_id);
160 client.execute_defi_subscribe_with_retained(cmd, true);
161
162 if let Some((instrument_id, client_id)) = pool {
163 self.setup_pool_updater(&instrument_id, client_id.as_ref());
164 }
165
166 Ok(())
167 }
168
169 pub fn execute_defi_unsubscribe(&mut self, cmd: &DefiUnsubscribeCommand) -> anyhow::Result<()> {
175 if let Some(client_id) = cmd.client_id()
176 && self.is_external_client(*client_id)
177 {
178 if self.config().debug {
179 log::debug!("Skipping defi unsubscribe for external client {client_id}: {cmd:?}");
180 }
181 return Ok(());
182 }
183
184 let Some(client) = self.get_client(cmd.client_id(), cmd.venue()) else {
185 log::error!(
186 "Cannot handle command: no client found for client_id={:?}, venue={:?}",
187 cmd.client_id(),
188 cmd.venue(),
189 );
190 return Ok(());
191 };
192
193 client.execute_defi_unsubscribe(cmd);
194
195 #[rustfmt::skip]
196 let instrument_id = match cmd {
197 DefiUnsubscribeCommand::Pool(cmd) => cmd.instrument_id,
198 DefiUnsubscribeCommand::PoolSwaps(cmd) => cmd.instrument_id,
199 DefiUnsubscribeCommand::PoolLiquidityUpdates(cmd) => cmd.instrument_id,
200 DefiUnsubscribeCommand::PoolFeeCollects(cmd) => cmd.instrument_id,
201 DefiUnsubscribeCommand::PoolFlashEvents(cmd) => cmd.instrument_id,
202 DefiUnsubscribeCommand::Blocks(_) => return Ok(()),
203 };
204
205 if !self
207 .get_clients()
208 .iter()
209 .any(|client| client.has_defi_pool_demand(&instrument_id))
210 {
211 self.release_pool_updater(&instrument_id);
212 }
213
214 Ok(())
215 }
216
217 pub fn execute_defi_request(&mut self, req: DefiRequestCommand) -> anyhow::Result<()> {
224 if let Some(cid) = req.client_id()
226 && self.is_external_client(*cid)
227 {
228 if self.config().debug {
229 log::debug!("Skipping defi data request for external client {cid}: {req:?}");
230 }
231 return Ok(());
232 }
233
234 if let Some(client) = self.get_client(req.client_id(), req.venue()) {
235 let client_id = client.client_id();
236 client
237 .execute_defi_request(req)
238 .map_err(|e| e.context(format!("Data client {client_id} request failed")))
239 } else {
240 anyhow::bail!(
241 "Cannot handle request: no client found for {:?} {:?}",
242 req.client_id(),
243 req.venue()
244 );
245 }
246 }
247
248 pub fn process_defi_data(&mut self, data: DefiData) {
250 self.increment_data_count();
251
252 match data {
253 DefiData::Block(block) => {
254 let topic = defi::switchboard::get_defi_blocks_topic(block.chain());
255 msgbus::publish_defi_block(topic, &block);
256 }
257 DefiData::Pool(pool) => {
258 if let Err(e) = self.cache().borrow_mut().add_pool(pool.clone()) {
259 log::error!("Failed to add Pool to cache: {e}");
260 }
261
262 match CurrencyPair::try_from(&pool) {
263 Ok(instrument) => {
264 self.handle_instrument(&InstrumentAny::CurrencyPair(instrument));
265 }
266 Err(e) => {
267 log::error!(
268 "Failed to create instrument for Pool {}: {e}",
269 pool.instrument_id
270 );
271 }
272 }
273
274 let topic = defi::switchboard::get_defi_pool_topic(pool.instrument_id);
275 msgbus::publish_defi_pool(topic, &pool);
276 }
277 DefiData::PoolSnapshot(snapshot) => {
278 if !self
280 .pool_snapshot_pending
281 .contains_key(&snapshot.instrument_id)
282 {
283 log::warn!(
284 "Received unexpected pool snapshot for {} (not in pending set)",
285 snapshot.instrument_id
286 );
287 return;
288 }
289
290 self.hydrate_pool_profiler(&snapshot);
291 }
292 DefiData::PoolSwap(swap) => {
293 let instrument_id = swap.instrument_id;
294 if self.pool_snapshot_pending.contains_key(&instrument_id) {
297 log::debug!("Buffering swap event for {instrument_id} (waiting for snapshot)");
298 self.pool_event_buffers
299 .entry(instrument_id)
300 .or_default()
301 .push(DefiData::PoolSwap(swap.clone()));
302 }
303
304 let topic = defi::switchboard::get_defi_pool_swaps_topic(instrument_id);
305 msgbus::publish_defi_swap(topic, &swap);
306 }
307 DefiData::PoolLiquidityUpdate(update) => {
308 let instrument_id = update.instrument_id;
309 if self.pool_snapshot_pending.contains_key(&instrument_id) {
312 log::debug!(
313 "Buffering liquidity update event for {instrument_id} (waiting for snapshot)"
314 );
315 self.pool_event_buffers
316 .entry(instrument_id)
317 .or_default()
318 .push(DefiData::PoolLiquidityUpdate(update.clone()));
319 }
320
321 let topic = defi::switchboard::get_defi_liquidity_topic(instrument_id);
322 msgbus::publish_defi_liquidity(topic, &update);
323 }
324 DefiData::PoolFeeCollect(collect) => {
325 let instrument_id = collect.instrument_id;
326 if self.pool_snapshot_pending.contains_key(&instrument_id) {
329 log::debug!(
330 "Buffering fee collect event for {instrument_id} (waiting for snapshot)"
331 );
332 self.pool_event_buffers
333 .entry(instrument_id)
334 .or_default()
335 .push(DefiData::PoolFeeCollect(collect.clone()));
336 }
337
338 let topic = defi::switchboard::get_defi_collect_topic(instrument_id);
339 msgbus::publish_defi_collect(topic, &collect);
340 }
341 DefiData::PoolFeeProtocolUpdate(update) => {
342 let instrument_id = update.instrument_id;
343 if self.pool_snapshot_pending.contains_key(&instrument_id) {
347 log::debug!(
348 "Buffering fee protocol update for {instrument_id} (waiting for snapshot)"
349 );
350 self.pool_event_buffers
351 .entry(instrument_id)
352 .or_default()
353 .push(DefiData::PoolFeeProtocolUpdate(update));
354 } else if let Some(profiler) =
355 self.cache().borrow_mut().pool_profiler_mut(&instrument_id)
356 && let Err(e) = profiler.process_fee_protocol_update(&update)
357 {
358 log::error!("Failed to process pool fee protocol update: {e}");
359 }
360 }
361 DefiData::PoolFeeProtocolCollect(collect) => {
362 let instrument_id = collect.instrument_id;
363 if self.pool_snapshot_pending.contains_key(&instrument_id) {
367 log::debug!(
368 "Buffering fee protocol collect event for {instrument_id} (waiting for snapshot)"
369 );
370 self.pool_event_buffers
371 .entry(instrument_id)
372 .or_default()
373 .push(DefiData::PoolFeeProtocolCollect(collect));
374 } else if let Some(profiler) =
375 self.cache().borrow_mut().pool_profiler_mut(&instrument_id)
376 && let Err(e) = profiler.process_fee_protocol_collect(&collect)
377 {
378 log::error!("Failed to process pool fee protocol collect event: {e}");
379 }
380 }
381 DefiData::PoolFlash(flash) => {
382 let instrument_id = flash.instrument_id;
383 if self.pool_snapshot_pending.contains_key(&instrument_id) {
386 log::debug!("Buffering flash event for {instrument_id} (waiting for snapshot)");
387 self.pool_event_buffers
388 .entry(instrument_id)
389 .or_default()
390 .push(DefiData::PoolFlash(flash.clone()));
391 }
392
393 let topic = defi::switchboard::get_defi_flash_topic(instrument_id);
394 msgbus::publish_defi_flash(topic, &flash);
395 }
396 }
397 }
398
399 pub(crate) fn handle_pool_snapshot_response(&mut self, response: &PoolSnapshotResponse) {
400 let instrument_id = response.data.instrument_id;
401
402 if self.pool_snapshot_pending.get(&instrument_id) != Some(&response.correlation_id) {
403 log::warn!(
404 "Discarding pool snapshot for {instrument_id} from stale request {}",
405 response.correlation_id
406 );
407 return;
408 }
409
410 self.hydrate_pool_profiler(&response.data);
411 }
412
413 fn hydrate_pool_profiler(&mut self, snapshot: &PoolSnapshot) {
414 let instrument_id = snapshot.instrument_id;
415 log::info!(
416 "Received pool snapshot for {instrument_id} at block {} with {} positions and {} ticks",
417 snapshot.block_position.number,
418 snapshot.positions.len(),
419 snapshot.ticks.len()
420 );
421
422 let pool = self
424 .cache()
425 .borrow()
426 .pool(&instrument_id)
427 .map(|pool| Arc::new(pool.clone()));
428
429 let Some(pool) = pool else {
430 log::error!("Pool {instrument_id} not found in cache when processing snapshot");
431 self.abandon_pool_snapshot(&instrument_id);
432 return;
433 };
434
435 if snapshot.positions.is_empty()
440 && snapshot.ticks.is_empty()
441 && snapshot.block_position.number == pool.creation_block
442 {
443 log::warn!(
444 "Refusing empty stub snapshot for {instrument_id} at pool creation block {}; pool will remain without profiler",
445 snapshot.block_position.number,
446 );
447 self.abandon_pool_snapshot(&instrument_id);
448 return;
449 }
450
451 let mut profiler = PoolProfiler::new(pool);
453 if let Err(e) = profiler.restore_from_snapshot(snapshot.clone()) {
454 log::error!("Failed to restore profiler from snapshot for {instrument_id}: {e}");
455 self.abandon_pool_snapshot(&instrument_id);
456 return;
457 }
458
459 log::debug!("Restored pool profiler for {instrument_id} from snapshot");
460
461 let buffered_events = self
463 .pool_event_buffers
464 .remove(&instrument_id)
465 .unwrap_or_default();
466
467 if !buffered_events.is_empty() {
468 log::info!(
469 "Processing {} buffered events for {instrument_id}",
470 buffered_events.len()
471 );
472
473 let events_to_apply = convert_and_sort_buffered_events(buffered_events);
474 let applied_count = Self::apply_buffered_events_to_profiler(
475 &mut profiler,
476 events_to_apply,
477 &snapshot.block_position,
478 instrument_id,
479 );
480
481 log::info!("Applied {applied_count} buffered events to profiler for {instrument_id}");
482 }
483
484 let result = self.cache().borrow_mut().add_pool_profiler(profiler);
486 if let Err(e) = result {
487 log::error!("Failed to add pool profiler to cache for {instrument_id}: {e}");
488 self.abandon_pool_snapshot(&instrument_id);
489 return;
490 }
491
492 self.pool_snapshot_pending.remove(&instrument_id);
494 let updater = Rc::new(PoolUpdater::new(&instrument_id, Rc::clone(self.cache())));
495
496 self.subscribe_pool_updater_topics(instrument_id, Rc::clone(&updater));
497 self.pool_updaters.insert(instrument_id, updater);
498
499 log::info!("Pool profiler setup completed for {instrument_id}, now processing live events");
500 }
501
502 fn abandon_pool_snapshot(&mut self, instrument_id: &InstrumentId) {
503 self.pool_snapshot_pending.remove(instrument_id);
504 self.pool_event_buffers.remove(instrument_id);
505 }
506
507 fn subscribe_pool_updater_topics(&self, instrument_id: InstrumentId, updater: Rc<PoolUpdater>) {
509 let priority = Some(self.msgbus_priority());
510
511 let swap_topic = defi::switchboard::get_defi_pool_swaps_topic(instrument_id);
513 let swap_handler = TypedHandler(Rc::new(PoolSwapHandler::new(Rc::clone(&updater))));
514 msgbus::subscribe_defi_swaps(swap_topic.into(), swap_handler, priority);
515
516 let liq_topic = defi::switchboard::get_defi_liquidity_topic(instrument_id);
518 let liq_handler = TypedHandler(Rc::new(PoolLiquidityHandler::new(Rc::clone(&updater))));
519 msgbus::subscribe_defi_liquidity(liq_topic.into(), liq_handler, priority);
520
521 let collect_topic = defi::switchboard::get_defi_collect_topic(instrument_id);
523 let collect_handler = TypedHandler(Rc::new(PoolCollectHandler::new(Rc::clone(&updater))));
524 msgbus::subscribe_defi_collects(collect_topic.into(), collect_handler, priority);
525
526 let flash_topic = defi::switchboard::get_defi_flash_topic(instrument_id);
528 let flash_handler = TypedHandler(Rc::new(PoolFlashHandler::new(updater)));
529 msgbus::subscribe_defi_flash(flash_topic.into(), flash_handler, priority);
530 }
531
532 fn apply_buffered_events_to_profiler(
536 profiler: &mut PoolProfiler,
537 events: Vec<DexPoolData>,
538 snapshot_block: &BlockPosition,
539 instrument_id: InstrumentId,
540 ) -> usize {
541 let mut applied_count = 0;
542
543 for event in events {
544 let event_block = get_event_block_position(&event);
545
546 let is_after_snapshot = event_block.0 > snapshot_block.number
548 || (event_block.0 == snapshot_block.number
549 && event_block.1 > snapshot_block.transaction_index)
550 || (event_block.0 == snapshot_block.number
551 && event_block.1 == snapshot_block.transaction_index
552 && event_block.2 > snapshot_block.log_index);
553
554 if is_after_snapshot {
555 if let Err(e) = profiler.process(&event) {
556 log::error!(
557 "Failed to apply buffered event to profiler for {instrument_id}: {e}"
558 );
559 } else {
560 applied_count += 1;
561 }
562 }
563 }
564
565 applied_count
566 }
567
568 fn setup_pool_updater(&mut self, instrument_id: &InstrumentId, client_id: Option<&ClientId>) {
569 if self.pool_updaters.contains_key(instrument_id)
571 || self.pool_snapshot_pending.contains_key(instrument_id)
572 {
573 log::debug!("Pool updater for {instrument_id} already exists");
574 return;
575 }
576
577 log::info!("Setting up pool updater for {instrument_id}");
578
579 {
581 let mut cache = self.cache().borrow_mut();
582
583 if cache.pool_profiler(instrument_id).is_some() {
584 log::debug!("Pool profiler already exists for {instrument_id}");
586 } else if let Some(pool) = cache.pool(instrument_id) {
587 let pool = Arc::new(pool.clone());
589 let mut pool_profiler = PoolProfiler::new(Arc::clone(&pool));
590
591 if let Some(initial_sqrt_price_x96) = pool.initial_sqrt_price_x96 {
592 if let Err(e) = pool_profiler.initialize(initial_sqrt_price_x96) {
593 log::error!("Failed to initialize pool profiler for {instrument_id}: {e}");
594 drop(cache);
595 return;
596 }
597 log::debug!(
598 "Initialized pool profiler for {instrument_id} with sqrt_price {initial_sqrt_price_x96}"
599 );
600 } else {
601 log::debug!("Created pool profiler for {instrument_id}");
602 }
603
604 if let Err(e) = cache.add_pool_profiler(pool_profiler) {
605 log::error!("Failed to add pool profiler for {instrument_id}: {e}");
606 drop(cache);
607 return;
608 }
609 drop(cache);
610 } else {
611 drop(cache);
613
614 let request_id = UUID4::new();
615 let ts_init = self.clock().borrow().timestamp_ns();
616
617 let request = RequestPoolSnapshot::new(
618 *instrument_id,
619 client_id.copied(),
620 request_id,
621 ts_init,
622 None,
623 );
624
625 if let Err(e) = self.execute_defi_request(DefiRequestCommand::PoolSnapshot(request))
626 {
627 log::warn!("Failed to request pool snapshot for {instrument_id}: {e}");
628 } else {
629 log::debug!("Requested pool snapshot for {instrument_id}");
630 self.pool_snapshot_pending
631 .insert(*instrument_id, request_id);
632 self.pool_event_buffers.entry(*instrument_id).or_default();
633 }
634 return;
635 }
636 }
637
638 let updater = Rc::new(PoolUpdater::new(instrument_id, Rc::clone(self.cache())));
640
641 self.subscribe_pool_updater_topics(*instrument_id, Rc::clone(&updater));
642 self.pool_updaters.insert(*instrument_id, updater);
643
644 log::debug!("Created PoolUpdater for instrument ID {instrument_id}");
645 }
646
647 fn release_pool_updater(&mut self, instrument_id: &InstrumentId) {
648 if let Some(updater) = self.pool_updaters.remove(instrument_id) {
649 Self::unsubscribe_pool_updater_topics(*instrument_id, updater);
650 log::debug!("Removed PoolUpdater for instrument ID {instrument_id}");
651 }
652
653 self.abandon_pool_snapshot(instrument_id);
654 }
655
656 pub(crate) fn clear_pool_updaters(&mut self) {
657 let updaters: Vec<_> = self.pool_updaters.drain().collect();
658 for (instrument_id, updater) in updaters {
659 Self::unsubscribe_pool_updater_topics(instrument_id, updater);
660 }
661
662 self.abandon_pool_snapshots();
663 }
664
665 pub(crate) fn abandon_pool_snapshots(&mut self) {
666 self.pool_snapshot_pending.clear();
667 self.pool_event_buffers.clear();
668 }
669
670 fn unsubscribe_pool_updater_topics(instrument_id: InstrumentId, updater: Rc<PoolUpdater>) {
671 let swap_topic = defi::switchboard::get_defi_pool_swaps_topic(instrument_id);
672 let swap_handler = TypedHandler(Rc::new(PoolSwapHandler::new(Rc::clone(&updater))));
673 msgbus::unsubscribe_defi_swaps(swap_topic.into(), &swap_handler);
674
675 let liq_topic = defi::switchboard::get_defi_liquidity_topic(instrument_id);
676 let liq_handler = TypedHandler(Rc::new(PoolLiquidityHandler::new(Rc::clone(&updater))));
677 msgbus::unsubscribe_defi_liquidity(liq_topic.into(), &liq_handler);
678
679 let collect_topic = defi::switchboard::get_defi_collect_topic(instrument_id);
680 let collect_handler = TypedHandler(Rc::new(PoolCollectHandler::new(Rc::clone(&updater))));
681 msgbus::unsubscribe_defi_collects(collect_topic.into(), &collect_handler);
682
683 let flash_topic = defi::switchboard::get_defi_flash_topic(instrument_id);
684 let flash_handler = TypedHandler(Rc::new(PoolFlashHandler::new(updater)));
685 msgbus::unsubscribe_defi_flash(flash_topic.into(), &flash_handler);
686 }
687}
688
689#[cfg(test)]
690mod tests {
691 use std::sync::Arc;
692
693 use alloy_primitives::{Address, I256, U160, U256};
694 use nautilus_core::UnixNanos;
695 use nautilus_model::{
696 defi::{
697 Chain, DefiData, PoolFeeCollect, PoolFeeProtocolUpdate, PoolFlash, PoolIdentifier,
698 PoolLiquidityUpdate, PoolLiquidityUpdateType, PoolSwap,
699 chain::chains,
700 data::DexPoolData,
701 dex::{AmmType, Dex, DexType},
702 },
703 identifiers::{InstrumentId, Symbol, Venue},
704 };
705 use rstest::*;
706
707 use super::*;
708
709 #[fixture]
710 fn test_instrument_id() -> InstrumentId {
711 InstrumentId::new(Symbol::from("ETH/USDC"), Venue::from("UNISWAPV3"))
712 }
713
714 #[fixture]
715 fn test_chain() -> Arc<Chain> {
716 Arc::new(chains::ETHEREUM.clone())
717 }
718
719 #[fixture]
720 fn test_dex(test_chain: Arc<Chain>) -> Arc<Dex> {
721 Arc::new(Dex::new(
722 (*test_chain).clone(),
723 DexType::UniswapV3,
724 "0x1F98431c8aD98523631AE4a59f267346ea31F984",
725 12369621,
726 AmmType::CLAMM,
727 "PoolCreated(address,address,uint24,int24,address)",
728 "Swap(address,address,int256,int256,uint160,uint128,int24)",
729 "Mint(address,address,int24,int24,uint128,uint256,uint256)",
730 "Burn(address,int24,int24,uint128,uint256,uint256)",
731 "Collect(address,address,int24,int24,uint128,uint128)",
732 ))
733 }
734
735 fn create_test_swap(
736 test_instrument_id: InstrumentId,
737 test_chain: Arc<Chain>,
738 test_dex: Arc<Dex>,
739 block: u64,
740 tx_index: u32,
741 log_index: u32,
742 ) -> PoolSwap {
743 PoolSwap::new(
744 test_chain,
745 test_dex,
746 test_instrument_id,
747 PoolIdentifier::from_address(Address::ZERO),
748 block,
749 format!("0x{block:064x}"),
750 tx_index,
751 log_index,
752 UnixNanos::default(),
753 UnixNanos::default(),
754 Address::ZERO,
755 Address::ZERO,
756 I256::ZERO,
757 I256::ZERO,
758 U160::ZERO,
759 0,
760 0,
761 )
762 }
763
764 fn create_test_liquidity_update(
765 test_instrument_id: InstrumentId,
766 test_chain: Arc<Chain>,
767 test_dex: Arc<Dex>,
768 block: u64,
769 tx_index: u32,
770 log_index: u32,
771 ) -> PoolLiquidityUpdate {
772 PoolLiquidityUpdate::new(
773 test_chain,
774 test_dex,
775 test_instrument_id,
776 PoolIdentifier::from_address(Address::ZERO),
777 PoolLiquidityUpdateType::Mint,
778 block,
779 format!("0x{block:064x}"),
780 tx_index,
781 log_index,
782 None,
783 Address::ZERO,
784 0,
785 U256::ZERO,
786 U256::ZERO,
787 0,
788 0,
789 UnixNanos::default(),
790 UnixNanos::default(),
791 )
792 }
793
794 fn create_test_fee_collect(
795 test_instrument_id: InstrumentId,
796 test_chain: Arc<Chain>,
797 test_dex: Arc<Dex>,
798 block: u64,
799 tx_index: u32,
800 log_index: u32,
801 ) -> PoolFeeCollect {
802 PoolFeeCollect::new(
803 test_chain,
804 test_dex,
805 test_instrument_id,
806 PoolIdentifier::from_address(Address::ZERO),
807 block,
808 format!("0x{block:064x}"),
809 tx_index,
810 log_index,
811 Address::ZERO,
812 0,
813 0,
814 0,
815 0,
816 UnixNanos::default(),
817 UnixNanos::default(),
818 )
819 }
820
821 fn create_test_flash(
822 test_instrument_id: InstrumentId,
823 test_chain: Arc<Chain>,
824 test_dex: Arc<Dex>,
825 block: u64,
826 tx_index: u32,
827 log_index: u32,
828 ) -> PoolFlash {
829 PoolFlash::new(
830 test_chain,
831 test_dex,
832 test_instrument_id,
833 PoolIdentifier::from_address(Address::ZERO),
834 block,
835 format!("0x{block:064x}"),
836 tx_index,
837 log_index,
838 UnixNanos::default(),
839 UnixNanos::default(),
840 Address::ZERO,
841 Address::ZERO,
842 U256::ZERO,
843 U256::ZERO,
844 U256::ZERO,
845 U256::ZERO,
846 )
847 }
848
849 fn create_test_fee_protocol_update(
850 test_instrument_id: InstrumentId,
851 test_chain: Arc<Chain>,
852 test_dex: Arc<Dex>,
853 block: u64,
854 tx_index: u32,
855 log_index: u32,
856 ) -> PoolFeeProtocolUpdate {
857 PoolFeeProtocolUpdate::new(
858 test_chain,
859 test_dex,
860 test_instrument_id,
861 PoolIdentifier::from_address(Address::ZERO),
862 block,
863 format!("0x{block:064x}"),
864 tx_index,
865 log_index,
866 4,
867 4,
868 UnixNanos::default(),
869 UnixNanos::default(),
870 )
871 }
872
873 #[rstest]
874 fn test_get_event_block_position_swap(
875 test_instrument_id: InstrumentId,
876 test_chain: Arc<Chain>,
877 test_dex: Arc<Dex>,
878 ) {
879 let swap = create_test_swap(test_instrument_id, test_chain, test_dex, 100, 5, 3);
880 let pos = get_event_block_position(&DexPoolData::Swap(swap));
881 assert_eq!(pos, (100, 5, 3));
882 }
883
884 #[rstest]
885 fn test_get_event_block_position_liquidity_update(
886 test_instrument_id: InstrumentId,
887 test_chain: Arc<Chain>,
888 test_dex: Arc<Dex>,
889 ) {
890 let update =
891 create_test_liquidity_update(test_instrument_id, test_chain, test_dex, 200, 10, 7);
892 let pos = get_event_block_position(&DexPoolData::LiquidityUpdate(update));
893 assert_eq!(pos, (200, 10, 7));
894 }
895
896 #[rstest]
897 fn test_get_event_block_position_fee_collect(
898 test_instrument_id: InstrumentId,
899 test_chain: Arc<Chain>,
900 test_dex: Arc<Dex>,
901 ) {
902 let collect = create_test_fee_collect(test_instrument_id, test_chain, test_dex, 300, 15, 2);
903 let pos = get_event_block_position(&DexPoolData::FeeCollect(collect));
904 assert_eq!(pos, (300, 15, 2));
905 }
906
907 #[rstest]
908 fn test_get_event_block_position_flash(
909 test_instrument_id: InstrumentId,
910 test_chain: Arc<Chain>,
911 test_dex: Arc<Dex>,
912 ) {
913 let flash = create_test_flash(test_instrument_id, test_chain, test_dex, 400, 20, 8);
914 let pos = get_event_block_position(&DexPoolData::Flash(flash));
915 assert_eq!(pos, (400, 20, 8));
916 }
917
918 #[rstest]
919 fn test_get_event_block_position_fee_protocol_update(
920 test_instrument_id: InstrumentId,
921 test_chain: Arc<Chain>,
922 test_dex: Arc<Dex>,
923 ) {
924 let update =
925 create_test_fee_protocol_update(test_instrument_id, test_chain, test_dex, 500, 25, 4);
926 let pos = get_event_block_position(&DexPoolData::FeeProtocolUpdate(update));
927 assert_eq!(pos, (500, 25, 4));
928 }
929
930 #[rstest]
931 fn test_convert_and_sort_empty_events() {
932 let events = convert_and_sort_buffered_events(vec![]);
933 assert!(events.is_empty());
934 }
935
936 #[rstest]
937 fn test_convert_and_sort_filters_non_pool_events(
938 test_instrument_id: InstrumentId,
939 test_chain: Arc<Chain>,
940 test_dex: Arc<Dex>,
941 ) {
942 let events = vec![
943 DefiData::PoolSwap(create_test_swap(
944 test_instrument_id,
945 test_chain,
946 test_dex,
947 100,
948 0,
949 0,
950 )),
951 ];
953 let sorted = convert_and_sort_buffered_events(events);
954 assert_eq!(sorted.len(), 1);
955 }
956
957 #[rstest]
958 fn test_convert_and_sort_single_event(
959 test_instrument_id: InstrumentId,
960 test_chain: Arc<Chain>,
961 test_dex: Arc<Dex>,
962 ) {
963 let swap = create_test_swap(test_instrument_id, test_chain, test_dex, 100, 5, 3);
964 let events = vec![DefiData::PoolSwap(swap)];
965 let sorted = convert_and_sort_buffered_events(events);
966 assert_eq!(sorted.len(), 1);
967 assert_eq!(get_event_block_position(&sorted[0]), (100, 5, 3));
968 }
969
970 #[rstest]
971 fn test_convert_and_sort_already_sorted(
972 test_instrument_id: InstrumentId,
973 test_chain: Arc<Chain>,
974 test_dex: Arc<Dex>,
975 ) {
976 let events = vec![
977 DefiData::PoolSwap(create_test_swap(
978 test_instrument_id,
979 Arc::clone(&test_chain),
980 Arc::clone(&test_dex),
981 100,
982 0,
983 0,
984 )),
985 DefiData::PoolSwap(create_test_swap(
986 test_instrument_id,
987 Arc::clone(&test_chain),
988 Arc::clone(&test_dex),
989 100,
990 0,
991 1,
992 )),
993 DefiData::PoolSwap(create_test_swap(
994 test_instrument_id,
995 test_chain,
996 test_dex,
997 100,
998 1,
999 0,
1000 )),
1001 ];
1002 let sorted = convert_and_sort_buffered_events(events);
1003 assert_eq!(sorted.len(), 3);
1004 assert_eq!(get_event_block_position(&sorted[0]), (100, 0, 0));
1005 assert_eq!(get_event_block_position(&sorted[1]), (100, 0, 1));
1006 assert_eq!(get_event_block_position(&sorted[2]), (100, 1, 0));
1007 }
1008
1009 #[rstest]
1010 fn test_convert_and_sort_reverse_order(
1011 test_instrument_id: InstrumentId,
1012 test_chain: Arc<Chain>,
1013 test_dex: Arc<Dex>,
1014 ) {
1015 let events = vec![
1016 DefiData::PoolSwap(create_test_swap(
1017 test_instrument_id,
1018 Arc::clone(&test_chain),
1019 Arc::clone(&test_dex),
1020 100,
1021 2,
1022 5,
1023 )),
1024 DefiData::PoolSwap(create_test_swap(
1025 test_instrument_id,
1026 Arc::clone(&test_chain),
1027 Arc::clone(&test_dex),
1028 100,
1029 1,
1030 3,
1031 )),
1032 DefiData::PoolSwap(create_test_swap(
1033 test_instrument_id,
1034 test_chain,
1035 test_dex,
1036 100,
1037 0,
1038 1,
1039 )),
1040 ];
1041 let sorted = convert_and_sort_buffered_events(events);
1042 assert_eq!(sorted.len(), 3);
1043 assert_eq!(get_event_block_position(&sorted[0]), (100, 0, 1));
1044 assert_eq!(get_event_block_position(&sorted[1]), (100, 1, 3));
1045 assert_eq!(get_event_block_position(&sorted[2]), (100, 2, 5));
1046 }
1047
1048 #[rstest]
1049 fn test_convert_and_sort_mixed_blocks(
1050 test_instrument_id: InstrumentId,
1051 test_chain: Arc<Chain>,
1052 test_dex: Arc<Dex>,
1053 ) {
1054 let events = vec![
1055 DefiData::PoolSwap(create_test_swap(
1056 test_instrument_id,
1057 Arc::clone(&test_chain),
1058 Arc::clone(&test_dex),
1059 102,
1060 0,
1061 0,
1062 )),
1063 DefiData::PoolSwap(create_test_swap(
1064 test_instrument_id,
1065 Arc::clone(&test_chain),
1066 Arc::clone(&test_dex),
1067 100,
1068 5,
1069 2,
1070 )),
1071 DefiData::PoolSwap(create_test_swap(
1072 test_instrument_id,
1073 test_chain,
1074 test_dex,
1075 101,
1076 3,
1077 1,
1078 )),
1079 ];
1080 let sorted = convert_and_sort_buffered_events(events);
1081 assert_eq!(sorted.len(), 3);
1082 assert_eq!(get_event_block_position(&sorted[0]), (100, 5, 2));
1083 assert_eq!(get_event_block_position(&sorted[1]), (101, 3, 1));
1084 assert_eq!(get_event_block_position(&sorted[2]), (102, 0, 0));
1085 }
1086
1087 #[rstest]
1088 fn test_convert_and_sort_mixed_event_types(
1089 test_instrument_id: InstrumentId,
1090 test_chain: Arc<Chain>,
1091 test_dex: Arc<Dex>,
1092 ) {
1093 let events = vec![
1094 DefiData::PoolSwap(create_test_swap(
1095 test_instrument_id,
1096 Arc::clone(&test_chain),
1097 Arc::clone(&test_dex),
1098 100,
1099 2,
1100 0,
1101 )),
1102 DefiData::PoolLiquidityUpdate(create_test_liquidity_update(
1103 test_instrument_id,
1104 Arc::clone(&test_chain),
1105 Arc::clone(&test_dex),
1106 100,
1107 0,
1108 0,
1109 )),
1110 DefiData::PoolFeeCollect(create_test_fee_collect(
1111 test_instrument_id,
1112 Arc::clone(&test_chain),
1113 Arc::clone(&test_dex),
1114 100,
1115 1,
1116 0,
1117 )),
1118 DefiData::PoolFlash(create_test_flash(
1119 test_instrument_id,
1120 Arc::clone(&test_chain),
1121 Arc::clone(&test_dex),
1122 100,
1123 3,
1124 0,
1125 )),
1126 DefiData::PoolFeeProtocolUpdate(create_test_fee_protocol_update(
1127 test_instrument_id,
1128 test_chain,
1129 test_dex,
1130 100,
1131 4,
1132 0,
1133 )),
1134 ];
1135 let sorted = convert_and_sort_buffered_events(events);
1136 assert_eq!(sorted.len(), 5);
1137 assert_eq!(get_event_block_position(&sorted[0]), (100, 0, 0));
1138 assert_eq!(get_event_block_position(&sorted[1]), (100, 1, 0));
1139 assert_eq!(get_event_block_position(&sorted[2]), (100, 2, 0));
1140 assert_eq!(get_event_block_position(&sorted[3]), (100, 3, 0));
1141 assert_eq!(get_event_block_position(&sorted[4]), (100, 4, 0));
1142 assert!(matches!(sorted[4], DexPoolData::FeeProtocolUpdate(_)));
1143 }
1144
1145 #[rstest]
1146 fn test_convert_and_sort_same_block_and_tx_different_log_index(
1147 test_instrument_id: InstrumentId,
1148 test_chain: Arc<Chain>,
1149 test_dex: Arc<Dex>,
1150 ) {
1151 let events = vec![
1152 DefiData::PoolSwap(create_test_swap(
1153 test_instrument_id,
1154 Arc::clone(&test_chain),
1155 Arc::clone(&test_dex),
1156 100,
1157 5,
1158 10,
1159 )),
1160 DefiData::PoolSwap(create_test_swap(
1161 test_instrument_id,
1162 Arc::clone(&test_chain),
1163 Arc::clone(&test_dex),
1164 100,
1165 5,
1166 5,
1167 )),
1168 DefiData::PoolSwap(create_test_swap(
1169 test_instrument_id,
1170 test_chain,
1171 test_dex,
1172 100,
1173 5,
1174 1,
1175 )),
1176 ];
1177 let sorted = convert_and_sort_buffered_events(events);
1178 assert_eq!(sorted.len(), 3);
1179 assert_eq!(get_event_block_position(&sorted[0]), (100, 5, 1));
1180 assert_eq!(get_event_block_position(&sorted[1]), (100, 5, 5));
1181 assert_eq!(get_event_block_position(&sorted[2]), (100, 5, 10));
1182 }
1183}