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