1use std::{collections::VecDeque, sync::Arc};
2
3use alloy_primitives::{
4 Address, U256,
5 map::{AddressMap, HashMap, U256Map},
6};
7use itertools::Itertools;
8use parking_lot::RwLock;
9use reth_primitives_traits::{BlockHeader, SealedHeader};
10use reth_provider::{
11 ChainSpecProvider, ExecutionOutcome, HeaderProvider, ProviderError, ProviderResult,
12 StateProvider, StateProviderFactory,
13};
14use tempo_chainspec::{TempoChainSpec, hardfork::TempoHardforks};
15use tempo_precompiles::{
16 DEFAULT_FEE_TOKEN, TIP_FEE_MANAGER_ADDRESS,
17 tip_fee_manager::{
18 TipFeeManager,
19 amm::{Pool, PoolKey, compute_amount_out},
20 },
21};
22use tempo_primitives::TempoReceipt;
23use tempo_revm::IntoAddress;
24
25const LAST_SEEN_WINDOW: usize = 10;
27
28#[derive(Debug, Clone)]
29pub struct AmmLiquidityCache {
30 inner: Arc<RwLock<AmmLiquidityCacheInner>>,
31}
32
33impl AmmLiquidityCache {
34 pub fn new<Client>(client: Client) -> ProviderResult<Self>
37 where
38 Client: StateProviderFactory
39 + HeaderProvider<Header: BlockHeader>
40 + ChainSpecProvider<ChainSpec = TempoChainSpec>,
41 {
42 let this = Self {
43 inner: Default::default(),
44 };
45 let tip = client.best_block_number()?;
46
47 for header in
48 client.sealed_headers_range(tip.saturating_sub(LAST_SEEN_WINDOW as u64 + 1)..=tip)?
49 {
50 this.on_new_block(&header, &client)?;
51 }
52
53 Ok(this)
54 }
55
56 pub fn has_enough_liquidity(
59 &self,
60 user_token: Address,
61 fee: U256,
62 state_provider: &(impl StateProvider + ?Sized),
63 ) -> Result<bool, ProviderError> {
64 let amount_out = compute_amount_out(fee).map_err(ProviderError::other)?;
65
66 let mut missing_in_cache = Vec::new();
67
68 {
70 let inner = self.inner.read();
71 for validator_token in &inner.unique_tokens {
72 if validator_token == &user_token {
76 return Ok(true);
77 }
78
79 if let Some(validator_reserve) = inner.cache.get(&(user_token, *validator_token)) {
80 if *validator_reserve >= amount_out {
81 return Ok(true);
82 }
83 } else {
84 missing_in_cache.push(*validator_token);
85 }
86 }
87 }
88
89 if missing_in_cache.is_empty() {
91 return Ok(false);
92 }
93
94 for validator_token in missing_in_cache {
96 let pool_key = PoolKey::new(user_token, validator_token).get_id();
98 let slot = TipFeeManager::new().pools[pool_key].base_slot();
99 let pool = state_provider
100 .storage(TIP_FEE_MANAGER_ADDRESS, slot.into())?
101 .unwrap_or_default();
102 let reserve = U256::from(Pool::decode_from_slot(pool).reserve_validator_token);
103
104 let mut inner = self.inner.write();
105 inner.cache.insert((user_token, validator_token), reserve);
106 inner
107 .slot_to_pool
108 .insert(slot, (user_token, validator_token));
109
110 if reserve >= amount_out {
112 return Ok(true);
113 }
114 }
115
116 Ok(false)
117 }
118
119 pub fn clear(&self) {
122 *self.inner.write() = AmmLiquidityCacheInner::default();
123 }
124
125 pub fn repopulate<Client>(&self, client: &Client) -> ProviderResult<()>
130 where
131 Client: StateProviderFactory
132 + HeaderProvider<Header: BlockHeader>
133 + ChainSpecProvider<ChainSpec = TempoChainSpec>,
134 {
135 self.clear();
136 let tip = client.best_block_number()?;
137 for header in
138 client.sealed_headers_range(tip.saturating_sub(LAST_SEEN_WINDOW as u64 + 1)..=tip)?
139 {
140 self.on_new_block(&header, client)?;
141 }
142 Ok(())
143 }
144
145 pub fn on_new_state(&self, execution_outcome: &ExecutionOutcome<TempoReceipt>) {
148 let Some(storage) = execution_outcome
149 .account_state(&TIP_FEE_MANAGER_ADDRESS)
150 .map(|acc| &acc.storage)
151 else {
152 return;
153 };
154
155 let mut inner = self.inner.write();
156
157 for (slot, value) in storage.iter() {
159 if let Some(pool) = inner.slot_to_pool.get(slot).copied() {
160 let validator_reserve =
162 U256::from(Pool::decode_from_slot(value.present_value).reserve_validator_token);
163 inner.cache.insert(pool, validator_reserve);
164 } else if let Some(validator) = inner.slot_to_validator.get(slot).copied() {
165 inner
167 .validator_preferences
168 .insert(validator, value.present_value().into_address());
169 }
170 }
171 }
172
173 pub fn on_new_block<P>(
175 &self,
176 header: &SealedHeader<impl BlockHeader>,
177 state: P,
178 ) -> ProviderResult<()>
179 where
180 P: StateProviderFactory + ChainSpecProvider<ChainSpec: TempoHardforks>,
181 {
182 let beneficiary = header.beneficiary();
183 let validator_token_slot = TipFeeManager::new().validator_tokens[beneficiary].slot();
184
185 let cached_preference = self
186 .inner
187 .read()
188 .validator_preferences
189 .get(&beneficiary)
190 .copied();
191
192 let preference = if let Some(cached) = cached_preference {
193 cached
194 } else {
195 state
197 .state_by_block_hash(header.hash())?
198 .storage(TIP_FEE_MANAGER_ADDRESS, validator_token_slot.into())?
199 .unwrap_or_default()
200 .into_address()
201 };
202
203 let fee_token = if preference.is_zero() {
205 DEFAULT_FEE_TOKEN
206 } else {
207 preference
208 };
209
210 let mut inner = self.inner.write();
211
212 if cached_preference.is_none() {
214 inner.validator_preferences.insert(beneficiary, preference);
215 inner
216 .slot_to_validator
217 .insert(validator_token_slot, beneficiary);
218 }
219
220 inner.last_seen_tokens.push_back(fee_token);
222 if inner.last_seen_tokens.len() > LAST_SEEN_WINDOW {
223 inner.last_seen_tokens.pop_front();
224 }
225 inner.unique_tokens = inner.last_seen_tokens.iter().copied().unique().collect();
226
227 inner.last_seen_validators.push_back(beneficiary);
229 if inner.last_seen_validators.len() > LAST_SEEN_WINDOW {
230 inner.last_seen_validators.pop_front();
231 }
232 inner.unique_validators = inner
233 .last_seen_validators
234 .iter()
235 .copied()
236 .unique()
237 .collect();
238
239 Ok(())
240 }
241}
242
243#[derive(Debug, Default)]
244struct AmmLiquidityCacheInner {
245 cache: HashMap<(Address, Address), U256>,
247
248 slot_to_pool: U256Map<(Address, Address)>,
250
251 last_seen_tokens: VecDeque<Address>,
253
254 unique_tokens: Vec<Address>,
256
257 last_seen_validators: VecDeque<Address>,
259
260 unique_validators: Vec<Address>,
262
263 validator_preferences: AddressMap<Address>,
265
266 slot_to_validator: U256Map<Address>,
268}
269
270impl AmmLiquidityCache {
271 pub fn is_active_validator(&self, validator: &Address) -> bool {
277 self.inner.read().unique_validators.contains(validator)
278 }
279
280 pub fn is_active_validator_token(&self, token: &Address) -> bool {
283 self.inner.read().unique_tokens.contains(token)
284 }
285
286 pub fn track_tokens(&self, tokens: &[Address]) -> bool {
292 let mut updated = false;
293 if tokens.is_empty() {
294 return updated;
295 }
296
297 let mut inner = self.inner.write();
298 for &token in tokens {
299 if !inner.unique_tokens.contains(&token) {
300 inner.unique_tokens.push(token);
301 updated = true;
302 }
303 }
304 updated
305 }
306}
307
308#[cfg(any(test, feature = "test-utils"))]
309impl AmmLiquidityCache {
310 pub fn with_unique_tokens(unique_tokens: Vec<Address>) -> Self {
312 Self {
313 inner: Arc::new(RwLock::new(AmmLiquidityCacheInner {
314 unique_tokens,
315 ..Default::default()
316 })),
317 }
318 }
319
320 pub fn with_unique_validators(unique_validators: Vec<Address>) -> Self {
322 Self {
323 inner: Arc::new(RwLock::new(AmmLiquidityCacheInner {
324 unique_validators,
325 ..Default::default()
326 })),
327 }
328 }
329}
330
331#[cfg(test)]
332mod tests {
333 use super::*;
334 use crate::test_utils::create_mock_provider;
335 use alloy_primitives::address;
336
337 #[test]
342 fn test_has_enough_liquidity_user_token_matches_validator_token() {
343 let cache = AmmLiquidityCache {
344 inner: Arc::new(RwLock::new(AmmLiquidityCacheInner {
345 unique_tokens: vec![address!("1111111111111111111111111111111111111111")],
346 ..Default::default()
347 })),
348 };
349
350 let provider = create_mock_provider();
351 let state = provider.latest().unwrap();
352
353 let user_token = address!("1111111111111111111111111111111111111111");
354 let result = cache.has_enough_liquidity(user_token, U256::from(100), &state);
355
356 assert!(result.is_ok());
357 assert!(
358 result.unwrap(),
359 "Should return true when user token matches validator token"
360 );
361 }
362
363 #[test]
364 fn test_has_enough_liquidity_cached_pool_sufficient() {
365 let user_token = address!("2222222222222222222222222222222222222222");
366 let validator_token = address!("3333333333333333333333333333333333333333");
367
368 let cache = AmmLiquidityCache {
369 inner: Arc::new(RwLock::new(AmmLiquidityCacheInner {
370 unique_tokens: vec![validator_token],
371 cache: {
372 let mut m = HashMap::default();
373 m.insert((user_token, validator_token), U256::MAX);
374 m
375 },
376 ..Default::default()
377 })),
378 };
379
380 let provider = create_mock_provider();
381 let state = provider.latest().unwrap();
382
383 let result = cache.has_enough_liquidity(user_token, U256::from(1000), &state);
384 assert!(result.is_ok());
385 assert!(
386 result.unwrap(),
387 "Should return true for sufficient cached reserve"
388 );
389 }
390
391 #[test]
392 fn test_has_enough_liquidity_cached_pool_insufficient() {
393 let user_token = address!("2222222222222222222222222222222222222222");
394 let validator_token = address!("3333333333333333333333333333333333333333");
395
396 let cache = AmmLiquidityCache {
397 inner: Arc::new(RwLock::new(AmmLiquidityCacheInner {
398 unique_tokens: vec![validator_token],
399 cache: {
400 let mut m = HashMap::default();
401 m.insert((user_token, validator_token), U256::ZERO);
402 m
403 },
404 ..Default::default()
405 })),
406 };
407
408 let provider = create_mock_provider();
409 let state = provider.latest().unwrap();
410
411 let result = cache.has_enough_liquidity(user_token, U256::from(1000), &state);
412 assert!(result.is_ok());
413 assert!(
414 !result.unwrap(),
415 "Should return false for insufficient cached reserve"
416 );
417 }
418
419 #[test]
420 fn test_has_enough_liquidity_no_unique_tokens() {
421 let cache = AmmLiquidityCache {
422 inner: Arc::new(RwLock::new(AmmLiquidityCacheInner::default())),
423 };
424
425 let provider = create_mock_provider();
426 let state = provider.latest().unwrap();
427
428 let user_token = address!("1111111111111111111111111111111111111111");
429 let result = cache.has_enough_liquidity(user_token, U256::from(1000), &state);
430 assert!(result.is_ok());
431 assert!(
432 !result.unwrap(),
433 "Should return false when no unique tokens"
434 );
435 }
436
437 #[test]
438 fn test_has_enough_liquidity_cache_miss_insufficient() {
439 let user_token = address!("2222222222222222222222222222222222222222");
440 let validator_token = address!("3333333333333333333333333333333333333333");
441
442 let cache = AmmLiquidityCache {
443 inner: Arc::new(RwLock::new(AmmLiquidityCacheInner {
444 unique_tokens: vec![validator_token],
445 cache: HashMap::default(),
446 ..Default::default()
447 })),
448 };
449
450 let provider = create_mock_provider();
451 let state = provider.latest().unwrap();
452
453 let result = cache.has_enough_liquidity(user_token, U256::from(1000), &state);
455 assert!(result.is_ok());
456 assert!(
457 !result.unwrap(),
458 "Should return false for insufficient reserve"
459 );
460 }
461
462 #[test]
467 fn test_on_new_state_early_return_no_fee_manager_account() {
468 use reth_provider::ExecutionOutcome;
469 use tempo_primitives::TempoReceipt;
470
471 let cache = AmmLiquidityCache {
472 inner: Arc::new(RwLock::new(AmmLiquidityCacheInner::default())),
473 };
474
475 let execution_outcome: ExecutionOutcome<TempoReceipt> = ExecutionOutcome::default();
476 cache.on_new_state(&execution_outcome);
477
478 let inner = cache.inner.read();
479 assert!(inner.cache.is_empty());
480 assert!(inner.validator_preferences.is_empty());
481 }
482
483 #[test]
488 fn test_sliding_window_max_size() {
489 let mut inner = AmmLiquidityCacheInner::default();
490
491 for i in 0..LAST_SEEN_WINDOW {
492 let token = Address::new([i as u8; 20]);
493 inner.last_seen_tokens.push_back(token);
494 }
495
496 assert_eq!(inner.last_seen_tokens.len(), LAST_SEEN_WINDOW);
497
498 let new_token = Address::new([0xFF; 20]);
499 inner.last_seen_tokens.push_back(new_token);
500 if inner.last_seen_tokens.len() > LAST_SEEN_WINDOW {
501 inner.last_seen_tokens.pop_front();
502 }
503
504 assert_eq!(inner.last_seen_tokens.len(), LAST_SEEN_WINDOW);
505 assert_eq!(inner.last_seen_tokens.back(), Some(&new_token));
506 assert_eq!(inner.last_seen_tokens.front(), Some(&Address::new([1; 20])));
507 }
508
509 #[test]
510 fn test_sliding_window_validators() {
511 let mut inner = AmmLiquidityCacheInner::default();
512
513 for i in 0..LAST_SEEN_WINDOW {
514 let validator = Address::new([i as u8; 20]);
515 inner.last_seen_validators.push_back(validator);
516 }
517
518 assert_eq!(inner.last_seen_validators.len(), LAST_SEEN_WINDOW);
519
520 let new_validator = Address::new([0xFF; 20]);
521 inner.last_seen_validators.push_back(new_validator);
522 if inner.last_seen_validators.len() > LAST_SEEN_WINDOW {
523 inner.last_seen_validators.pop_front();
524 }
525
526 assert_eq!(inner.last_seen_validators.len(), LAST_SEEN_WINDOW);
527 assert_eq!(inner.last_seen_validators.back(), Some(&new_validator));
528 assert_eq!(
529 inner.last_seen_validators.front(),
530 Some(&Address::new([1; 20]))
531 );
532
533 inner.unique_validators = inner
534 .last_seen_validators
535 .iter()
536 .copied()
537 .unique()
538 .collect();
539 assert!(inner.unique_validators.contains(&new_validator));
540 }
541
542 #[test]
543 fn test_unique_tokens_deduplication() {
544 let mut inner = AmmLiquidityCacheInner::default();
545
546 let token_a = address!("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
547 let token_b = address!("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB");
548
549 inner.last_seen_tokens.push_back(token_a);
550 inner.last_seen_tokens.push_back(token_b);
551 inner.last_seen_tokens.push_back(token_b);
552 inner.last_seen_tokens.push_back(token_b);
553
554 inner.unique_tokens = inner.last_seen_tokens.iter().copied().unique().collect();
555
556 assert_eq!(inner.unique_tokens.len(), 2, "duplicates must be removed");
557 assert_eq!(inner.unique_tokens[0], token_a);
558 assert_eq!(inner.unique_tokens[1], token_b);
559 }
560
561 #[test]
566 fn test_cache_insert_and_lookup() {
567 let mut inner = AmmLiquidityCacheInner::default();
568
569 let user_token = address!("1111111111111111111111111111111111111111");
570 let validator_token = address!("2222222222222222222222222222222222222222");
571 let reserve = U256::from(5000);
572
573 inner.cache.insert((user_token, validator_token), reserve);
574
575 assert_eq!(
576 inner.cache.get(&(user_token, validator_token)),
577 Some(&reserve)
578 );
579 }
580
581 #[test]
582 fn test_slot_to_pool_mapping() {
583 let mut inner = AmmLiquidityCacheInner::default();
584
585 let user_token = address!("1111111111111111111111111111111111111111");
586 let validator_token = address!("2222222222222222222222222222222222222222");
587 let slot = U256::from(12345);
588
589 inner
590 .slot_to_pool
591 .insert(slot, (user_token, validator_token));
592
593 assert_eq!(
594 inner.slot_to_pool.get(&slot),
595 Some(&(user_token, validator_token))
596 );
597 }
598
599 #[test]
600 fn test_validator_preferences_mapping() {
601 let mut inner = AmmLiquidityCacheInner::default();
602
603 let validator = address!("3333333333333333333333333333333333333333");
604 let fee_token = address!("4444444444444444444444444444444444444444");
605
606 inner.validator_preferences.insert(validator, fee_token);
607
608 assert_eq!(
609 inner.validator_preferences.get(&validator),
610 Some(&fee_token)
611 );
612 }
613
614 #[test]
615 fn test_slot_to_validator_mapping() {
616 let mut inner = AmmLiquidityCacheInner::default();
617
618 let validator = address!("3333333333333333333333333333333333333333");
619 let slot = U256::from(67890);
620
621 inner.slot_to_validator.insert(slot, validator);
622
623 assert_eq!(inner.slot_to_validator.get(&slot), Some(&validator));
624 }
625
626 #[test]
627 fn test_clear_resets_all_state() {
628 let user_token = Address::random();
629 let validator_token = Address::random();
630 let validator = Address::random();
631
632 let cache = AmmLiquidityCache {
633 inner: Arc::new(RwLock::new(AmmLiquidityCacheInner {
634 cache: {
635 let mut m = HashMap::default();
636 m.insert((user_token, validator_token), U256::from(1000));
637 m
638 },
639 slot_to_pool: {
640 let mut m = U256Map::default();
641 m.insert(U256::from(1), (user_token, validator_token));
642 m
643 },
644 last_seen_tokens: VecDeque::from(vec![validator_token]),
645 unique_tokens: vec![validator_token],
646 last_seen_validators: VecDeque::from(vec![validator]),
647 unique_validators: vec![validator],
648 validator_preferences: {
649 let mut m = AddressMap::default();
650 m.insert(validator, validator_token);
651 m
652 },
653 slot_to_validator: {
654 let mut m = U256Map::default();
655 m.insert(U256::from(2), validator);
656 m
657 },
658 })),
659 };
660
661 cache.clear();
662
663 let inner = cache.inner.read();
664 assert!(inner.cache.is_empty(), "cache should be empty after clear");
665 assert!(
666 inner.slot_to_pool.is_empty(),
667 "slot_to_pool should be empty after clear"
668 );
669 assert!(
670 inner.last_seen_tokens.is_empty(),
671 "last_seen_tokens should be empty after clear"
672 );
673 assert!(
674 inner.unique_tokens.is_empty(),
675 "unique_tokens should be empty after clear"
676 );
677 assert!(
678 inner.last_seen_validators.is_empty(),
679 "last_seen_validators should be empty after clear"
680 );
681 assert!(
682 inner.unique_validators.is_empty(),
683 "unique_validators should be empty after clear"
684 );
685 assert!(
686 inner.validator_preferences.is_empty(),
687 "validator_preferences should be empty after clear"
688 );
689 assert!(
690 inner.slot_to_validator.is_empty(),
691 "slot_to_validator should be empty after clear"
692 );
693 }
694
695 #[test]
696 fn test_repopulate_clears_stale_data_and_rebuilds_from_canonical_chain() {
697 use alloy_consensus::Header;
698
699 let stale_validator = Address::random();
700 let stale_token = Address::random();
701 let stale_user_token = Address::random();
702
703 let cache = AmmLiquidityCache {
704 inner: Arc::new(RwLock::new(AmmLiquidityCacheInner {
705 cache: {
706 let mut m = HashMap::default();
707 m.insert((stale_user_token, stale_token), U256::from(9999));
708 m
709 },
710 slot_to_pool: {
711 let mut m = U256Map::default();
712 m.insert(U256::from(42), (stale_user_token, stale_token));
713 m
714 },
715 last_seen_tokens: VecDeque::from(vec![stale_token]),
716 unique_tokens: vec![stale_token],
717 last_seen_validators: VecDeque::from(vec![stale_validator]),
718 unique_validators: vec![stale_validator],
719 validator_preferences: {
720 let mut m = AddressMap::default();
721 m.insert(stale_validator, stale_token);
722 m
723 },
724 slot_to_validator: {
725 let mut m = U256Map::default();
726 m.insert(U256::from(99), stale_validator);
727 m
728 },
729 })),
730 };
731
732 {
733 let inner = cache.inner.read();
734 assert!(inner.unique_validators.contains(&stale_validator));
735 assert!(inner.unique_tokens.contains(&stale_token));
736 assert_eq!(
737 inner.cache.get(&(stale_user_token, stale_token)),
738 Some(&U256::from(9999))
739 );
740 }
741
742 let new_validator = Address::random();
743 let provider = create_mock_provider();
744 for i in 0..3u64 {
745 let header = Header {
746 number: i,
747 beneficiary: new_validator,
748 ..Default::default()
749 };
750 provider.add_header(alloy_primitives::B256::random(), header);
751 }
752
753 cache
754 .repopulate(&provider)
755 .expect("repopulate should succeed");
756
757 let inner = cache.inner.read();
758
759 assert!(
760 !inner.unique_validators.contains(&stale_validator),
761 "stale validator should be gone after repopulate"
762 );
763 assert!(
764 !inner.unique_tokens.contains(&stale_token),
765 "stale token should be gone after repopulate"
766 );
767 assert!(
768 !inner.cache.contains_key(&(stale_user_token, stale_token)),
769 "stale liquidity entry should be gone after repopulate"
770 );
771 assert!(
772 inner.slot_to_pool.is_empty(),
773 "stale slot_to_pool should be gone after repopulate"
774 );
775
776 assert!(
777 inner.unique_validators.contains(&new_validator),
778 "new canonical validator should be present after repopulate"
779 );
780 assert_eq!(
781 inner.last_seen_validators.len(),
782 3,
783 "should have 3 validators from new canonical headers"
784 );
785 }
786
787 #[test]
788 fn test_is_active_validator() {
789 let active = address!("1111111111111111111111111111111111111111");
790 let inactive = address!("DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF");
791
792 let cases = [
793 (vec![active], active, true, "active validator in set"),
794 (
795 vec![active],
796 inactive,
797 false,
798 "inactive validator not in set",
799 ),
800 (vec![], active, false, "empty set"),
801 ];
802
803 for (unique_validators, query, expected, desc) in cases {
804 let cache = AmmLiquidityCache::with_unique_validators(unique_validators);
805 assert_eq!(cache.is_active_validator(&query), expected, "{desc}");
806 }
807 }
808
809 #[test]
810 fn test_track_tokens() {
811 let token_a = address!("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
812 let token_b = address!("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB");
813
814 let cache = AmmLiquidityCache::with_unique_tokens(vec![]);
816 assert!(!cache.track_tokens(&[]));
817 assert!(cache.inner.read().unique_tokens.is_empty());
818
819 let cache = AmmLiquidityCache::with_unique_tokens(vec![token_a]);
821 assert!(cache.track_tokens(&[token_b]));
822 assert_eq!(cache.inner.read().unique_tokens, vec![token_a, token_b]);
823
824 let cache = AmmLiquidityCache::with_unique_tokens(vec![token_a]);
826 assert!(!cache.track_tokens(&[token_a]));
827 assert_eq!(cache.inner.read().unique_tokens.len(), 1);
828
829 let cache = AmmLiquidityCache::with_unique_tokens(vec![token_a]);
831 assert!(cache.track_tokens(&[token_b, token_b]));
832 assert_eq!(cache.inner.read().unique_tokens.len(), 2);
833 }
834}