1use std::sync::Arc;
18
19use rustc_hash::{FxHashMap, FxHashSet};
20use tokio::sync::RwLock;
21use tycho_simulation::{
22 tycho_client::feed::SynchronizerState,
23 tycho_common::{
24 models::{protocol::ProtocolComponent, token::Token, Address},
25 simulation::protocol_sim::ProtocolSim,
26 },
27 tycho_ethereum::gas::BlockGasPrice,
28};
29
30use crate::types::{BlockInfo, ComponentId};
31
32pub type StateLabel = String;
37
38pub type OverlayStates = Arc<FxHashMap<ComponentId, Box<dyn ProtocolSim>>>;
40
41pub struct OverlayEntry {
43 pub states: OverlayStates,
45 pub valid_until: u64,
48}
49
50type OverlayRegistry = Arc<RwLock<FxHashMap<StateLabel, OverlayEntry>>>;
52
53#[derive(Debug, thiserror::Error)]
55pub enum ReadLabeledError {
56 #[error("label not found: {0}")]
58 NotFound(StateLabel),
59}
60
61#[derive(Clone)]
66pub struct MarketData {
67 data: Arc<RwLock<MarketState>>,
68 overlays: OverlayRegistry,
71}
72
73impl MarketData {
74 pub fn new(data: Arc<RwLock<MarketState>>) -> Self {
76 Self { data, overlays: Arc::new(RwLock::new(FxHashMap::default())) }
77 }
78
79 pub fn new_shared() -> Self {
81 Self::new(Arc::new(RwLock::new(MarketState::new())))
82 }
83
84 pub async fn read(&self) -> MarketDataView<'_> {
86 MarketDataView { guard: self.data.read().await, overlay: None }
87 }
88
89 pub async fn read_labeled(
98 &self,
99 label: &StateLabel,
100 ) -> Result<MarketDataView<'_>, ReadLabeledError> {
101 let guard = self.data.read().await;
102 if let Some(e) = self.overlays.read().await.get(label) {
103 let states = Arc::clone(&e.states);
104 return Ok(MarketDataView { guard, overlay: Some((label.clone(), states)) });
105 }
106 if &guard.label == label {
107 return Ok(MarketDataView { guard, overlay: None });
108 }
109 Err(ReadLabeledError::NotFound(label.clone()))
110 }
111
112 pub async fn write(&self) -> tokio::sync::RwLockWriteGuard<'_, MarketState> {
114 self.data.write().await
115 }
116
117 pub fn try_read(&self) -> Option<tokio::sync::RwLockReadGuard<'_, MarketState>> {
121 self.data.try_read().ok()
122 }
123
124 pub fn try_write(&self) -> Option<tokio::sync::RwLockWriteGuard<'_, MarketState>> {
128 self.data.try_write().ok()
129 }
130
131 pub fn try_read_blocking(&self) -> Option<MarketDataView<'_>> {
138 self.data
139 .try_read()
140 .ok()
141 .map(|guard| MarketDataView { guard, overlay: None })
142 }
143
144 pub async fn register_labeled_state(
148 &self,
149 label: StateLabel,
150 states: FxHashMap<ComponentId, Box<dyn ProtocolSim>>,
151 valid_until: u64,
152 ) {
153 self.overlays
154 .write()
155 .await
156 .insert(label, OverlayEntry { states: Arc::new(states), valid_until });
157 }
158
159 pub async fn remove_labeled_state(&self, label: &StateLabel) {
161 self.overlays
162 .write()
163 .await
164 .remove(label);
165 }
166
167 pub async fn clear_labeled_states(&self) {
169 self.overlays.write().await.clear();
170 }
171
172 pub async fn apply_block_update(
179 &self,
180 new_block_number: u64,
181 update: impl FnOnce(&mut MarketState),
182 ) {
183 self.overlays
184 .write()
185 .await
186 .retain(|_, entry| entry.valid_until >= new_block_number);
187 let mut data = self.data.write().await;
188 data.label = new_block_number.to_string();
189 update(&mut data);
190 }
191
192 pub async fn labeled_state_ids(&self) -> Vec<StateLabel> {
194 self.overlays
195 .read()
196 .await
197 .keys()
198 .cloned()
199 .collect()
200 }
201}
202
203pub struct MarketDataView<'a> {
209 guard: tokio::sync::RwLockReadGuard<'a, MarketState>,
210 overlay: Option<(StateLabel, OverlayStates)>,
211}
212
213impl<'a> MarketDataView<'a> {
214 pub fn state_label(&self) -> Option<&StateLabel> {
216 self.overlay
217 .as_ref()
218 .map(|(label, _)| label)
219 }
220
221 pub fn get_simulation_state(&self, id: &str) -> Option<&dyn ProtocolSim> {
223 if let Some((_, ref states)) = self.overlay {
224 if let Some(s) = states.get(id) {
225 return Some(s.as_ref());
226 }
227 }
228 self.guard.get_simulation_state(id)
229 }
230
231 pub fn extract_subset_with_overlay(
236 &self,
237 component_ids: &FxHashSet<&ComponentId>,
238 ) -> MarketState {
239 let mut subset = self.guard.extract_subset(component_ids);
240 if let Some((ref label, ref states)) = self.overlay {
241 for (id, state) in states.iter() {
242 if subset
243 .simulation_states
244 .contains_key(id)
245 {
246 subset
247 .simulation_states
248 .insert(id.clone(), state.clone_box());
249 }
250 }
251 subset.label = label.clone();
252 }
253 subset
254 }
255
256 pub fn component_topology(&self) -> FxHashMap<ComponentId, Vec<Address>> {
258 self.guard.component_topology()
259 }
260
261 pub fn extract_subset(&self, component_ids: &FxHashSet<&ComponentId>) -> MarketState {
263 self.guard.extract_subset(component_ids)
264 }
265
266 pub fn token_registry_ref(&self) -> &FxHashMap<Address, Arc<Token>> {
268 self.guard.token_registry_ref()
269 }
270
271 pub fn gas_price(&self) -> Option<&BlockGasPrice> {
273 self.guard.gas_price()
274 }
275
276 pub fn last_updated(&self) -> Option<&BlockInfo> {
278 self.guard.last_updated()
279 }
280
281 pub fn get_token(&self, address: &Address) -> Option<&Token> {
283 self.guard.get_token(address)
284 }
285
286 pub fn get_token_shared(&self, address: &Address) -> Option<&Arc<Token>> {
288 self.guard.get_token_shared(address)
289 }
290
291 pub fn get_component(&self, id: &str) -> Option<&ProtocolComponent> {
293 self.guard.get_component(id)
294 }
295
296 pub fn base_market_state(&self) -> &MarketState {
298 &self.guard
299 }
300}
301
302#[derive(Debug, Default)]
307pub struct MarketState {
308 label: StateLabel,
314 components: FxHashMap<ComponentId, Arc<ProtocolComponent>>,
316 simulation_states: FxHashMap<ComponentId, Box<dyn ProtocolSim>>,
318 tokens: FxHashMap<Address, Arc<Token>>,
320 gas_price: Option<BlockGasPrice>,
322 protocol_sync_status: FxHashMap<String, SynchronizerState>,
324 last_updated: Option<BlockInfo>,
327 component_counts: FxHashMap<String, u64>,
330}
331
332impl MarketState {
333 pub fn new() -> Self {
335 Self {
336 label: String::new(),
337 components: FxHashMap::default(),
338 simulation_states: FxHashMap::default(),
339 tokens: FxHashMap::default(),
340 gas_price: None,
341 protocol_sync_status: FxHashMap::default(),
342 last_updated: None,
343 component_counts: FxHashMap::default(),
344 }
345 }
346
347 pub fn label(&self) -> &StateLabel {
349 &self.label
350 }
351
352 pub fn last_updated(&self) -> Option<&BlockInfo> {
354 self.last_updated.as_ref()
355 }
356
357 pub fn component_count(&self) -> usize {
359 self.components.len()
360 }
361
362 pub fn token_count(&self) -> usize {
364 self.tokens.len()
365 }
366
367 pub fn component_counts_by_protocol(&self) -> &FxHashMap<String, u64> {
372 &self.component_counts
373 }
374
375 pub fn protocol_sync_states(&self) -> &FxHashMap<String, SynchronizerState> {
377 &self.protocol_sync_status
378 }
379
380 pub fn get_protocol_sync_status(&self, protocol_system: &String) -> Option<&SynchronizerState> {
382 self.protocol_sync_status
383 .get(protocol_system)
384 }
385
386 pub fn component_topology(&self) -> FxHashMap<ComponentId, Vec<Address>> {
389 self.components
390 .iter()
391 .map(|(id, component)| (id.clone(), component.tokens.clone()))
392 .collect()
393 }
394
395 pub fn get_component(&self, id: &str) -> Option<&ProtocolComponent> {
397 self.components.get(id).map(Arc::as_ref)
398 }
399
400 pub fn get_component_shared(&self, id: &str) -> Option<&Arc<ProtocolComponent>> {
402 self.components.get(id)
403 }
404
405 pub fn get_simulation_state(&self, id: &str) -> Option<&dyn ProtocolSim> {
407 self.simulation_states
408 .get(id)
409 .map(|b| b.as_ref())
410 }
411
412 pub fn get_token(&self, address: &Address) -> Option<&Token> {
414 self.tokens
415 .get(address)
416 .map(Arc::as_ref)
417 }
418
419 pub fn get_token_shared(&self, address: &Address) -> Option<&Arc<Token>> {
421 self.tokens.get(address)
422 }
423
424 pub fn gas_price(&self) -> Option<&BlockGasPrice> {
426 self.gas_price.as_ref()
427 }
428
429 pub fn token_registry_ref(&self) -> &FxHashMap<Address, Arc<Token>> {
431 &self.tokens
432 }
433
434 pub fn upsert_components(&mut self, components: impl IntoIterator<Item = ProtocolComponent>) {
436 for component in components {
437 let protocol_system = component.protocol_system.clone();
438 let previous = self
439 .components
440 .insert(component.id.clone(), Arc::new(component));
441 if previous.is_none() {
442 *self
443 .component_counts
444 .entry(protocol_system)
445 .or_default() += 1;
446 }
447 }
448 }
449
450 pub fn upsert_tokens(&mut self, tokens: impl IntoIterator<Item = Token>) {
452 for token in tokens {
453 self.tokens
454 .insert(token.address.clone(), Arc::new(token));
455 }
456 }
457
458 pub fn update_protocol_sync_status(
460 &mut self,
461 sync_states: impl IntoIterator<Item = (String, SynchronizerState)>,
462 ) {
463 for (protocol_system, status) in sync_states {
464 self.protocol_sync_status
465 .insert(protocol_system, status);
466 }
467 }
468
469 pub fn remove_components<'a>(&mut self, ids: impl IntoIterator<Item = &'a ComponentId>) {
471 for id in ids {
472 if let Some(component) = self.components.remove(id) {
473 if let Some(count) = self
474 .component_counts
475 .get_mut(&component.protocol_system)
476 {
477 *count = count.saturating_sub(1);
478 }
479 }
480 self.simulation_states.remove(id);
481 }
482 }
483
484 pub fn update_states(
486 &mut self,
487 states: impl IntoIterator<Item = (ComponentId, Box<dyn ProtocolSim>)>,
488 ) {
489 for (id, state) in states {
490 self.simulation_states.insert(id, state);
491 }
492 }
493
494 pub fn update_gas_price(&mut self, gas_price: BlockGasPrice) {
496 self.gas_price = Some(gas_price);
497 }
498
499 pub fn update_last_updated(&mut self, block_info: BlockInfo) {
501 self.last_updated = Some(block_info);
502 }
503
504 pub fn extract_subset(&self, component_ids: &FxHashSet<&ComponentId>) -> MarketState {
513 let mut components =
514 FxHashMap::with_capacity_and_hasher(component_ids.len(), rustc_hash::FxBuildHasher);
515 let mut simulation_states =
516 FxHashMap::with_capacity_and_hasher(component_ids.len(), rustc_hash::FxBuildHasher);
517 let mut token_addresses: FxHashSet<&Address> =
520 FxHashSet::with_capacity_and_hasher(component_ids.len() * 2, rustc_hash::FxBuildHasher);
521
522 for &id in component_ids {
523 if let Some(component) = self.components.get(id) {
524 token_addresses.extend(&component.tokens);
525 components.insert(id.clone(), component.clone());
526 }
527 if let Some(state) = self.simulation_states.get(id) {
530 simulation_states.insert(id.clone(), state.clone_box());
531 }
532 }
533
534 let mut tokens =
535 FxHashMap::with_capacity_and_hasher(token_addresses.len(), rustc_hash::FxBuildHasher);
536 for address in token_addresses {
537 if let Some(token) = self.tokens.get(address) {
538 tokens.insert(address.clone(), token.clone());
539 }
540 }
541
542 MarketState {
543 label: self.label.clone(),
544 components,
545 simulation_states,
546 tokens,
547 gas_price: self.gas_price.clone(),
548 protocol_sync_status: FxHashMap::default(), last_updated: self.last_updated.clone(),
550 component_counts: FxHashMap::default(), }
552 }
553}
554
555#[cfg(test)]
556mod tests {
557 use num_bigint::BigUint;
558 use tycho_simulation::tycho_ethereum::gas::GasPrice;
559
560 use super::*;
561 use crate::algorithm::test_utils::{
562 component, component_with_protocol, token, MockProtocolSim,
563 };
564
565 #[test]
566 fn component_counts_by_protocol_tracks_upserts_and_removals() {
567 let mut market = MarketState::new();
568 let component_tokens = [token(0x0A, "A"), token(0x0B, "B")];
569
570 market.upsert_components([
571 component_with_protocol("component_1", "uniswap_v2", &component_tokens),
572 component_with_protocol("component_2", "uniswap_v2", &component_tokens),
573 component_with_protocol("component_3", "uniswap_v3", &component_tokens),
574 ]);
575 let counts = market.component_counts_by_protocol();
576 assert_eq!(counts.get("uniswap_v2"), Some(&2));
577 assert_eq!(counts.get("uniswap_v3"), Some(&1));
578
579 market.upsert_components([component_with_protocol(
581 "component_1",
582 "uniswap_v2",
583 &component_tokens,
584 )]);
585 assert_eq!(
586 market
587 .component_counts_by_protocol()
588 .get("uniswap_v2"),
589 Some(&2)
590 );
591
592 let removed_ids = ["component_1".to_string(), "component_3".to_string()];
595 market.remove_components(removed_ids.iter());
596 let counts = market.component_counts_by_protocol();
597 assert_eq!(counts.get("uniswap_v2"), Some(&1));
598 assert_eq!(counts.get("uniswap_v3"), Some(&0));
599
600 let unknown_ids = ["unknown_component".to_string()];
602 market.remove_components(unknown_ids.iter());
603 assert_eq!(
604 market
605 .component_counts_by_protocol()
606 .get("uniswap_v2"),
607 Some(&1)
608 );
609 }
610
611 #[test]
612 fn extract_subset_filters_by_component_ids() {
613 let mut market = MarketState::new();
615
616 let token_a = token(0x0A, "A");
617 let token_b = token(0x0B, "B");
618 let token_c = token(0x0C, "C");
619
620 market.upsert_components([
621 component("component_ab", &[token_a.clone(), token_b.clone()]),
622 component("component_bc", &[token_b.clone(), token_c.clone()]),
623 ]);
624 market.upsert_tokens([token_a.clone(), token_b.clone(), token_c.clone()]);
625 market.update_states([
626 (
627 "component_ab".to_string(),
628 Box::new(MockProtocolSim::new(2.0)) as Box<dyn ProtocolSim>,
629 ),
630 (
631 "component_bc".to_string(),
632 Box::new(MockProtocolSim::new(3.0)) as Box<dyn ProtocolSim>,
633 ),
634 ]);
635 market.update_gas_price(BlockGasPrice {
636 block_number: 1,
637 block_hash: Default::default(),
638 block_timestamp: 0,
639 pricing: GasPrice::Legacy { gas_price: BigUint::from(1u64) },
640 });
641 market.update_last_updated(BlockInfo::new(12345, "0xabc".to_string(), 0));
642
643 let component_ab = "component_ab".to_string();
645 let ids: FxHashSet<&ComponentId> = [&component_ab].into_iter().collect();
646 let subset = market.extract_subset(&ids);
647
648 assert_eq!(subset.components.len(), 1);
650 assert!(subset
651 .components
652 .contains_key("component_ab"));
653
654 assert_eq!(subset.tokens.len(), 2);
656 assert!(subset
657 .tokens
658 .contains_key(&token_a.address));
659 assert!(subset
660 .tokens
661 .contains_key(&token_b.address));
662 assert!(!subset
663 .tokens
664 .contains_key(&token_c.address));
665
666 assert_eq!(subset.simulation_states.len(), 1);
668 assert!(subset
669 .simulation_states
670 .contains_key("component_ab"));
671
672 assert_eq!(subset.gas_price, market.gas_price);
674 assert!(subset.last_updated.is_some());
675
676 let empty_subset = market.extract_subset(&FxHashSet::default());
678 assert!(empty_subset.components.is_empty());
679 assert!(empty_subset.tokens.is_empty());
680 assert!(empty_subset
681 .simulation_states
682 .is_empty());
683 }
684
685 #[tokio::test]
688 async fn register_and_retrieve_overlay_via_labeled_read() {
689 let market_ref = MarketData::new_shared();
690
691 let label = "test_label".to_string();
692 let mut states: FxHashMap<ComponentId, Box<dyn ProtocolSim>> = FxHashMap::default();
693 states.insert(
694 "component_ab".to_string(),
695 Box::new(MockProtocolSim::new(99.0)) as Box<dyn ProtocolSim>,
696 );
697
698 market_ref
699 .register_labeled_state(label.clone(), states, u64::MAX)
700 .await;
701
702 let guard = market_ref
703 .read_labeled(&label)
704 .await
705 .expect("label was just registered");
706 let sim = guard.get_simulation_state("component_ab");
708 assert!(sim.is_some());
709 }
710
711 #[tokio::test]
712 async fn read_without_label_returns_no_overlay() {
713 let market_ref = MarketData::new_shared();
714
715 market_ref
716 .register_labeled_state(
717 "my_label".to_string(),
718 FxHashMap::from_iter([(
719 "component1".to_string(),
720 Box::new(MockProtocolSim::new(5.0)) as Box<dyn ProtocolSim>,
721 )]),
722 u64::MAX,
723 )
724 .await;
725
726 let guard = market_ref.read().await;
728 assert!(guard
729 .get_simulation_state("component1")
730 .is_none());
731 }
732
733 #[tokio::test]
734 async fn remove_labeled_state_clears_overlay() {
735 let market_ref = MarketData::new_shared();
736 let label = "lbl".to_string();
737
738 market_ref
739 .register_labeled_state(
740 label.clone(),
741 FxHashMap::from_iter([(
742 "component".to_string(),
743 Box::new(MockProtocolSim::new(1.0)) as Box<dyn ProtocolSim>,
744 )]),
745 u64::MAX,
746 )
747 .await;
748
749 market_ref
750 .remove_labeled_state(&label)
751 .await;
752
753 let ids = market_ref.labeled_state_ids().await;
754 assert!(ids.is_empty());
755 }
756
757 #[tokio::test]
758 async fn clear_labeled_states_removes_all() {
759 let market_ref = MarketData::new_shared();
760
761 for i in 0..3u8 {
762 market_ref
763 .register_labeled_state(
764 format!("label_{i}"),
765 FxHashMap::from_iter([(
766 format!("component_{i}"),
767 Box::new(MockProtocolSim::new(f64::from(i))) as Box<dyn ProtocolSim>,
768 )]),
769 u64::MAX,
770 )
771 .await;
772 }
773
774 market_ref.clear_labeled_states().await;
775 assert!(market_ref
776 .labeled_state_ids()
777 .await
778 .is_empty());
779 }
780
781 #[tokio::test]
782 async fn clone_shares_overlay_registry() {
783 let base = MarketData::new_shared();
786 let clone_a = base.clone();
787 let clone_b = base.clone();
788
789 base.register_labeled_state(
790 "shared".to_string(),
791 FxHashMap::from_iter([(
792 "component_x".to_string(),
793 Box::new(MockProtocolSim::new(7.0)) as Box<dyn ProtocolSim>,
794 )]),
795 u64::MAX,
796 )
797 .await;
798
799 let label = "shared".to_string();
800 let guard_a = clone_a
801 .read_labeled(&label)
802 .await
803 .expect("label was just registered");
804 assert!(guard_a
805 .get_simulation_state("component_x")
806 .is_some());
807 drop(guard_a);
808
809 let guard_b = clone_b
810 .read_labeled(&label)
811 .await
812 .expect("label was just registered");
813 assert!(guard_b
814 .get_simulation_state("component_x")
815 .is_some());
816 }
817
818 #[tokio::test]
819 async fn extract_subset_with_overlay_replaces_matching_states() {
820 use crate::algorithm::test_utils::{component as mk_component, token as mk_token};
821
822 let market_ref = MarketData::new_shared();
823
824 let tok_a = mk_token(0x01, "A");
825 let tok_b = mk_token(0x02, "B");
826
827 {
828 let mut data = market_ref.write().await;
829 data.upsert_components([mk_component("component_ab", &[tok_a.clone(), tok_b.clone()])]);
830 data.upsert_tokens([tok_a.clone(), tok_b.clone()]);
831 data.update_states([(
832 "component_ab".to_string(),
833 Box::new(MockProtocolSim::new(2.0)) as Box<dyn ProtocolSim>,
834 )]);
835 }
836
837 let label = "overlay".to_string();
838 market_ref
839 .register_labeled_state(
840 label.clone(),
841 FxHashMap::from_iter([(
842 "component_ab".to_string(),
843 Box::new(MockProtocolSim::new(99.0)) as Box<dyn ProtocolSim>,
844 )]),
845 u64::MAX,
846 )
847 .await;
848
849 let guard = market_ref
850 .read_labeled(&label)
851 .await
852 .expect("label was just registered");
853 let component_ab = "component_ab".to_string();
854 let ids: FxHashSet<&ComponentId> = [&component_ab].into_iter().collect();
855 let subset = guard.extract_subset_with_overlay(&ids);
856
857 let sim = subset
858 .get_simulation_state("component_ab")
859 .unwrap();
860 let mock = sim
861 .as_any()
862 .downcast_ref::<MockProtocolSim>()
863 .unwrap();
864 assert_eq!(mock.spot_price, 99.0, "overlay state should replace base state");
865 }
866
867 #[tokio::test]
868 async fn apply_block_update_evicts_stale_overlays() {
869 let market_ref = MarketData::new_shared();
870
871 market_ref
873 .register_labeled_state(
874 "stale".to_string(),
875 FxHashMap::from_iter([(
876 "component_stale".to_string(),
877 Box::new(MockProtocolSim::new(1.0)) as Box<dyn ProtocolSim>,
878 )]),
879 10,
880 )
881 .await;
882 market_ref
883 .register_labeled_state(
884 "fresh".to_string(),
885 FxHashMap::from_iter([(
886 "component_fresh".to_string(),
887 Box::new(MockProtocolSim::new(2.0)) as Box<dyn ProtocolSim>,
888 )]),
889 20,
890 )
891 .await;
892
893 market_ref
895 .apply_block_update(11, |_data| {})
896 .await;
897
898 let ids = market_ref.labeled_state_ids().await;
899 assert!(!ids.contains(&"stale".to_string()), "stale overlay must be evicted");
900 assert!(ids.contains(&"fresh".to_string()), "fresh overlay must survive");
901 }
902
903 #[tokio::test]
904 async fn apply_block_update_applies_mutation() {
905 let market_ref = MarketData::new_shared();
906
907 market_ref
908 .apply_block_update(1, |data| {
909 data.update_last_updated(BlockInfo::new(1, "0xabc".to_string(), 0));
910 })
911 .await;
912
913 let guard = market_ref.read().await;
914 assert_eq!(
915 guard
916 .last_updated()
917 .expect("last_updated must be set")
918 .number(),
919 1
920 );
921 }
922
923 #[tokio::test]
924 async fn component_and_token_counts_track_upserts_and_removals() {
925 let market = MarketData::new_shared();
926 let tok_a = token(1, "A");
927 let tok_b = token(2, "B");
928
929 market
930 .apply_block_update(1, |data| {
931 data.upsert_components([component(
932 "component_ab",
933 &[tok_a.clone(), tok_b.clone()],
934 )]);
935 data.upsert_tokens([tok_a.clone(), tok_b.clone()]);
936 })
937 .await;
938 {
939 let data = market.read().await;
940 assert_eq!(
941 data.base_market_state()
942 .component_count(),
943 1
944 );
945 assert_eq!(data.base_market_state().token_count(), 2);
946 }
947
948 market
949 .apply_block_update(2, |data| {
950 data.remove_components(["component_ab".to_string()].iter());
951 })
952 .await;
953 let data = market.read().await;
954 assert_eq!(
955 data.base_market_state()
956 .component_count(),
957 0
958 );
959 assert_eq!(
960 data.base_market_state().token_count(),
961 2,
962 "tokens are not removed with their components"
963 );
964 }
965}