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.external_clients.contains(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(cmd.clone());
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.external_clients.contains(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.external_clients.contains(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 let request = RequestPoolSnapshot::new(
588 *instrument_id,
589 client_id.copied(),
590 request_id,
591 ts_init,
592 None,
593 );
594
595 if let Err(e) = self.execute_defi_request(DefiRequestCommand::PoolSnapshot(request))
596 {
597 log::warn!("Failed to request pool snapshot for {instrument_id}: {e}");
598 } else {
599 log::debug!("Requested pool snapshot for {instrument_id}");
600 self.pool_snapshot_pending.insert(*instrument_id);
601 self.pool_updaters_pending.insert(*instrument_id);
602 self.pool_event_buffers.entry(*instrument_id).or_default();
603 }
604 return;
605 }
606 }
607
608 let updater = Rc::new(PoolUpdater::new(instrument_id, self.cache.clone()));
610
611 self.subscribe_pool_updater_topics(*instrument_id, updater.clone());
612 self.pool_updaters.insert(*instrument_id, updater);
613
614 log::debug!("Created PoolUpdater for instrument ID {instrument_id}");
615 }
616}
617
618#[cfg(test)]
619mod tests {
620 use std::sync::Arc;
621
622 use alloy_primitives::{Address, I256, U160, U256};
623 use nautilus_core::UnixNanos;
624 use nautilus_model::{
625 defi::{
626 Chain, DefiData, PoolFeeCollect, PoolFeeProtocolUpdate, PoolFlash, PoolIdentifier,
627 PoolLiquidityUpdate, PoolLiquidityUpdateType, PoolSwap,
628 chain::chains,
629 data::DexPoolData,
630 dex::{AmmType, Dex, DexType},
631 },
632 identifiers::{InstrumentId, Symbol, Venue},
633 };
634 use rstest::*;
635
636 use super::*;
637
638 #[fixture]
639 fn test_instrument_id() -> InstrumentId {
640 InstrumentId::new(Symbol::from("ETH/USDC"), Venue::from("UNISWAPV3"))
641 }
642
643 #[fixture]
644 fn test_chain() -> Arc<Chain> {
645 Arc::new(chains::ETHEREUM.clone())
646 }
647
648 #[fixture]
649 fn test_dex(test_chain: Arc<Chain>) -> Arc<Dex> {
650 Arc::new(Dex::new(
651 (*test_chain).clone(),
652 DexType::UniswapV3,
653 "0x1F98431c8aD98523631AE4a59f267346ea31F984",
654 12369621,
655 AmmType::CLAMM,
656 "PoolCreated(address,address,uint24,int24,address)",
657 "Swap(address,address,int256,int256,uint160,uint128,int24)",
658 "Mint(address,address,int24,int24,uint128,uint256,uint256)",
659 "Burn(address,int24,int24,uint128,uint256,uint256)",
660 "Collect(address,address,int24,int24,uint128,uint128)",
661 ))
662 }
663
664 fn create_test_swap(
665 test_instrument_id: InstrumentId,
666 test_chain: Arc<Chain>,
667 test_dex: Arc<Dex>,
668 block: u64,
669 tx_index: u32,
670 log_index: u32,
671 ) -> PoolSwap {
672 PoolSwap::new(
673 test_chain,
674 test_dex,
675 test_instrument_id,
676 PoolIdentifier::from_address(Address::ZERO),
677 block,
678 format!("0x{block:064x}"),
679 tx_index,
680 log_index,
681 UnixNanos::default(),
682 UnixNanos::default(),
683 Address::ZERO,
684 Address::ZERO,
685 I256::ZERO,
686 I256::ZERO,
687 U160::ZERO,
688 0,
689 0,
690 )
691 }
692
693 fn create_test_liquidity_update(
694 test_instrument_id: InstrumentId,
695 test_chain: Arc<Chain>,
696 test_dex: Arc<Dex>,
697 block: u64,
698 tx_index: u32,
699 log_index: u32,
700 ) -> PoolLiquidityUpdate {
701 PoolLiquidityUpdate::new(
702 test_chain,
703 test_dex,
704 test_instrument_id,
705 PoolIdentifier::from_address(Address::ZERO),
706 PoolLiquidityUpdateType::Mint,
707 block,
708 format!("0x{block:064x}"),
709 tx_index,
710 log_index,
711 None,
712 Address::ZERO,
713 0,
714 U256::ZERO,
715 U256::ZERO,
716 0,
717 0,
718 UnixNanos::default(),
719 UnixNanos::default(),
720 )
721 }
722
723 fn create_test_fee_collect(
724 test_instrument_id: InstrumentId,
725 test_chain: Arc<Chain>,
726 test_dex: Arc<Dex>,
727 block: u64,
728 tx_index: u32,
729 log_index: u32,
730 ) -> PoolFeeCollect {
731 PoolFeeCollect::new(
732 test_chain,
733 test_dex,
734 test_instrument_id,
735 PoolIdentifier::from_address(Address::ZERO),
736 block,
737 format!("0x{block:064x}"),
738 tx_index,
739 log_index,
740 Address::ZERO,
741 0,
742 0,
743 0,
744 0,
745 UnixNanos::default(),
746 UnixNanos::default(),
747 )
748 }
749
750 fn create_test_flash(
751 test_instrument_id: InstrumentId,
752 test_chain: Arc<Chain>,
753 test_dex: Arc<Dex>,
754 block: u64,
755 tx_index: u32,
756 log_index: u32,
757 ) -> PoolFlash {
758 PoolFlash::new(
759 test_chain,
760 test_dex,
761 test_instrument_id,
762 PoolIdentifier::from_address(Address::ZERO),
763 block,
764 format!("0x{block:064x}"),
765 tx_index,
766 log_index,
767 UnixNanos::default(),
768 UnixNanos::default(),
769 Address::ZERO,
770 Address::ZERO,
771 U256::ZERO,
772 U256::ZERO,
773 U256::ZERO,
774 U256::ZERO,
775 )
776 }
777
778 fn create_test_fee_protocol_update(
779 test_instrument_id: InstrumentId,
780 test_chain: Arc<Chain>,
781 test_dex: Arc<Dex>,
782 block: u64,
783 tx_index: u32,
784 log_index: u32,
785 ) -> PoolFeeProtocolUpdate {
786 PoolFeeProtocolUpdate::new(
787 test_chain,
788 test_dex,
789 test_instrument_id,
790 PoolIdentifier::from_address(Address::ZERO),
791 block,
792 format!("0x{block:064x}"),
793 tx_index,
794 log_index,
795 4,
796 4,
797 UnixNanos::default(),
798 UnixNanos::default(),
799 )
800 }
801
802 #[rstest]
803 fn test_get_event_block_position_swap(
804 test_instrument_id: InstrumentId,
805 test_chain: Arc<Chain>,
806 test_dex: Arc<Dex>,
807 ) {
808 let swap = create_test_swap(test_instrument_id, test_chain, test_dex, 100, 5, 3);
809 let pos = get_event_block_position(&DexPoolData::Swap(swap));
810 assert_eq!(pos, (100, 5, 3));
811 }
812
813 #[rstest]
814 fn test_get_event_block_position_liquidity_update(
815 test_instrument_id: InstrumentId,
816 test_chain: Arc<Chain>,
817 test_dex: Arc<Dex>,
818 ) {
819 let update =
820 create_test_liquidity_update(test_instrument_id, test_chain, test_dex, 200, 10, 7);
821 let pos = get_event_block_position(&DexPoolData::LiquidityUpdate(update));
822 assert_eq!(pos, (200, 10, 7));
823 }
824
825 #[rstest]
826 fn test_get_event_block_position_fee_collect(
827 test_instrument_id: InstrumentId,
828 test_chain: Arc<Chain>,
829 test_dex: Arc<Dex>,
830 ) {
831 let collect = create_test_fee_collect(test_instrument_id, test_chain, test_dex, 300, 15, 2);
832 let pos = get_event_block_position(&DexPoolData::FeeCollect(collect));
833 assert_eq!(pos, (300, 15, 2));
834 }
835
836 #[rstest]
837 fn test_get_event_block_position_flash(
838 test_instrument_id: InstrumentId,
839 test_chain: Arc<Chain>,
840 test_dex: Arc<Dex>,
841 ) {
842 let flash = create_test_flash(test_instrument_id, test_chain, test_dex, 400, 20, 8);
843 let pos = get_event_block_position(&DexPoolData::Flash(flash));
844 assert_eq!(pos, (400, 20, 8));
845 }
846
847 #[rstest]
848 fn test_get_event_block_position_fee_protocol_update(
849 test_instrument_id: InstrumentId,
850 test_chain: Arc<Chain>,
851 test_dex: Arc<Dex>,
852 ) {
853 let update =
854 create_test_fee_protocol_update(test_instrument_id, test_chain, test_dex, 500, 25, 4);
855 let pos = get_event_block_position(&DexPoolData::FeeProtocolUpdate(update));
856 assert_eq!(pos, (500, 25, 4));
857 }
858
859 #[rstest]
860 fn test_convert_and_sort_empty_events() {
861 let events = convert_and_sort_buffered_events(vec![]);
862 assert!(events.is_empty());
863 }
864
865 #[rstest]
866 fn test_convert_and_sort_filters_non_pool_events(
867 test_instrument_id: InstrumentId,
868 test_chain: Arc<Chain>,
869 test_dex: Arc<Dex>,
870 ) {
871 let events = vec![
872 DefiData::PoolSwap(create_test_swap(
873 test_instrument_id,
874 test_chain,
875 test_dex,
876 100,
877 0,
878 0,
879 )),
880 ];
882 let sorted = convert_and_sort_buffered_events(events);
883 assert_eq!(sorted.len(), 1);
884 }
885
886 #[rstest]
887 fn test_convert_and_sort_single_event(
888 test_instrument_id: InstrumentId,
889 test_chain: Arc<Chain>,
890 test_dex: Arc<Dex>,
891 ) {
892 let swap = create_test_swap(test_instrument_id, test_chain, test_dex, 100, 5, 3);
893 let events = vec![DefiData::PoolSwap(swap)];
894 let sorted = convert_and_sort_buffered_events(events);
895 assert_eq!(sorted.len(), 1);
896 assert_eq!(get_event_block_position(&sorted[0]), (100, 5, 3));
897 }
898
899 #[rstest]
900 fn test_convert_and_sort_already_sorted(
901 test_instrument_id: InstrumentId,
902 test_chain: Arc<Chain>,
903 test_dex: Arc<Dex>,
904 ) {
905 let events = vec![
906 DefiData::PoolSwap(create_test_swap(
907 test_instrument_id,
908 test_chain.clone(),
909 test_dex.clone(),
910 100,
911 0,
912 0,
913 )),
914 DefiData::PoolSwap(create_test_swap(
915 test_instrument_id,
916 test_chain.clone(),
917 test_dex.clone(),
918 100,
919 0,
920 1,
921 )),
922 DefiData::PoolSwap(create_test_swap(
923 test_instrument_id,
924 test_chain,
925 test_dex,
926 100,
927 1,
928 0,
929 )),
930 ];
931 let sorted = convert_and_sort_buffered_events(events);
932 assert_eq!(sorted.len(), 3);
933 assert_eq!(get_event_block_position(&sorted[0]), (100, 0, 0));
934 assert_eq!(get_event_block_position(&sorted[1]), (100, 0, 1));
935 assert_eq!(get_event_block_position(&sorted[2]), (100, 1, 0));
936 }
937
938 #[rstest]
939 fn test_convert_and_sort_reverse_order(
940 test_instrument_id: InstrumentId,
941 test_chain: Arc<Chain>,
942 test_dex: Arc<Dex>,
943 ) {
944 let events = vec![
945 DefiData::PoolSwap(create_test_swap(
946 test_instrument_id,
947 test_chain.clone(),
948 test_dex.clone(),
949 100,
950 2,
951 5,
952 )),
953 DefiData::PoolSwap(create_test_swap(
954 test_instrument_id,
955 test_chain.clone(),
956 test_dex.clone(),
957 100,
958 1,
959 3,
960 )),
961 DefiData::PoolSwap(create_test_swap(
962 test_instrument_id,
963 test_chain,
964 test_dex,
965 100,
966 0,
967 1,
968 )),
969 ];
970 let sorted = convert_and_sort_buffered_events(events);
971 assert_eq!(sorted.len(), 3);
972 assert_eq!(get_event_block_position(&sorted[0]), (100, 0, 1));
973 assert_eq!(get_event_block_position(&sorted[1]), (100, 1, 3));
974 assert_eq!(get_event_block_position(&sorted[2]), (100, 2, 5));
975 }
976
977 #[rstest]
978 fn test_convert_and_sort_mixed_blocks(
979 test_instrument_id: InstrumentId,
980 test_chain: Arc<Chain>,
981 test_dex: Arc<Dex>,
982 ) {
983 let events = vec![
984 DefiData::PoolSwap(create_test_swap(
985 test_instrument_id,
986 test_chain.clone(),
987 test_dex.clone(),
988 102,
989 0,
990 0,
991 )),
992 DefiData::PoolSwap(create_test_swap(
993 test_instrument_id,
994 test_chain.clone(),
995 test_dex.clone(),
996 100,
997 5,
998 2,
999 )),
1000 DefiData::PoolSwap(create_test_swap(
1001 test_instrument_id,
1002 test_chain,
1003 test_dex,
1004 101,
1005 3,
1006 1,
1007 )),
1008 ];
1009 let sorted = convert_and_sort_buffered_events(events);
1010 assert_eq!(sorted.len(), 3);
1011 assert_eq!(get_event_block_position(&sorted[0]), (100, 5, 2));
1012 assert_eq!(get_event_block_position(&sorted[1]), (101, 3, 1));
1013 assert_eq!(get_event_block_position(&sorted[2]), (102, 0, 0));
1014 }
1015
1016 #[rstest]
1017 fn test_convert_and_sort_mixed_event_types(
1018 test_instrument_id: InstrumentId,
1019 test_chain: Arc<Chain>,
1020 test_dex: Arc<Dex>,
1021 ) {
1022 let events = vec![
1023 DefiData::PoolSwap(create_test_swap(
1024 test_instrument_id,
1025 test_chain.clone(),
1026 test_dex.clone(),
1027 100,
1028 2,
1029 0,
1030 )),
1031 DefiData::PoolLiquidityUpdate(create_test_liquidity_update(
1032 test_instrument_id,
1033 test_chain.clone(),
1034 test_dex.clone(),
1035 100,
1036 0,
1037 0,
1038 )),
1039 DefiData::PoolFeeCollect(create_test_fee_collect(
1040 test_instrument_id,
1041 test_chain.clone(),
1042 test_dex.clone(),
1043 100,
1044 1,
1045 0,
1046 )),
1047 DefiData::PoolFlash(create_test_flash(
1048 test_instrument_id,
1049 test_chain.clone(),
1050 test_dex.clone(),
1051 100,
1052 3,
1053 0,
1054 )),
1055 DefiData::PoolFeeProtocolUpdate(create_test_fee_protocol_update(
1056 test_instrument_id,
1057 test_chain,
1058 test_dex,
1059 100,
1060 4,
1061 0,
1062 )),
1063 ];
1064 let sorted = convert_and_sort_buffered_events(events);
1065 assert_eq!(sorted.len(), 5);
1066 assert_eq!(get_event_block_position(&sorted[0]), (100, 0, 0));
1067 assert_eq!(get_event_block_position(&sorted[1]), (100, 1, 0));
1068 assert_eq!(get_event_block_position(&sorted[2]), (100, 2, 0));
1069 assert_eq!(get_event_block_position(&sorted[3]), (100, 3, 0));
1070 assert_eq!(get_event_block_position(&sorted[4]), (100, 4, 0));
1071 assert!(matches!(sorted[4], DexPoolData::FeeProtocolUpdate(_)));
1072 }
1073
1074 #[rstest]
1075 fn test_convert_and_sort_same_block_and_tx_different_log_index(
1076 test_instrument_id: InstrumentId,
1077 test_chain: Arc<Chain>,
1078 test_dex: Arc<Dex>,
1079 ) {
1080 let events = vec![
1081 DefiData::PoolSwap(create_test_swap(
1082 test_instrument_id,
1083 test_chain.clone(),
1084 test_dex.clone(),
1085 100,
1086 5,
1087 10,
1088 )),
1089 DefiData::PoolSwap(create_test_swap(
1090 test_instrument_id,
1091 test_chain.clone(),
1092 test_dex.clone(),
1093 100,
1094 5,
1095 5,
1096 )),
1097 DefiData::PoolSwap(create_test_swap(
1098 test_instrument_id,
1099 test_chain,
1100 test_dex,
1101 100,
1102 5,
1103 1,
1104 )),
1105 ];
1106 let sorted = convert_and_sort_buffered_events(events);
1107 assert_eq!(sorted.len(), 3);
1108 assert_eq!(get_event_block_position(&sorted[0]), (100, 5, 1));
1109 assert_eq!(get_event_block_position(&sorted[1]), (100, 5, 5));
1110 assert_eq!(get_event_block_position(&sorted[2]), (100, 5, 10));
1111 }
1112}