1use std::{
2 collections::{hash_map::Entry, HashMap, HashSet},
3 future::Future,
4 pin::Pin,
5 sync::Arc,
6};
7
8use alloy::primitives::{Address, U256};
9use thiserror::Error;
10use tokio::sync::{watch, RwLock, RwLockReadGuard};
11use tracing::{debug, error, info, warn};
12use tycho_client::feed::{synchronizer::ComponentWithState, BlockHeader, FeedMessage, HeaderLike};
13use tycho_common::{
14 dto::{ChangeType, ProtocolStateDelta},
15 models::{blockchain::BlockAggregatedChanges, token::Token, Chain},
16 simulation::protocol_sim::{Balances, BlockContext, ProtocolSim},
17 Bytes,
18};
19#[cfg(test)]
20use {
21 mockall::mock,
22 num_bigint::BigUint,
23 std::any::Any,
24 tycho_common::simulation::{
25 errors::{SimulationError, TransitionError},
26 protocol_sim::GetAmountOutResult,
27 },
28};
29
30use crate::{
31 evm::{
32 engine_db::{update_engine, SHARED_TYCHO_DB},
33 override_stream::{OverrideSnapshot, StateOverrideProvider},
34 protocol::{
35 utils::bytes_to_address,
36 vm::{constants::ERC20_PROXY_BYTECODE, erc20_token::IMPLEMENTATION_SLOT},
37 },
38 tycho_models::{AccountUpdate, ResponseAccount},
39 },
40 protocol::{
41 errors::InvalidSnapshotError,
42 models::{DecoderContext, ProtocolComponent, TryFromWithBlock, Update},
43 },
44};
45
46#[derive(Error, Debug)]
47pub enum StreamDecodeError {
48 #[error("{0}")]
49 Fatal(String),
50}
51
52#[derive(Default)]
53struct DecoderState {
54 tokens: HashMap<Bytes, Token>,
55 states: HashMap<String, Box<dyn ProtocolSim>>,
56 components: HashMap<String, ProtocolComponent>,
57 contracts_map: HashMap<Bytes, HashSet<String>>,
59 proxy_token_addresses: HashMap<Address, Address>,
61 failed_components: HashSet<String>,
65 current_block_number: u64,
67}
68
69type DecodeFut =
70 Pin<Box<dyn Future<Output = Result<Box<dyn ProtocolSim>, InvalidSnapshotError>> + Send + Sync>>;
71type AccountBalances = HashMap<Bytes, HashMap<Bytes, Bytes>>;
72type RegistryFn<H> = dyn Fn(
73 ComponentWithState,
74 H,
75 AccountBalances,
76 Arc<RwLock<DecoderState>>,
77 Option<watch::Receiver<OverrideSnapshot>>,
78 ) -> DecodeFut
79 + Send
80 + Sync;
81type FilterFn = fn(&ComponentWithState) -> bool;
82
83pub struct TychoStreamDecoder<H>
96where
97 H: HeaderLike,
98{
99 state: Arc<RwLock<DecoderState>>,
100 skip_state_decode_failures: bool,
101 min_token_quality: u32,
102 registry: HashMap<String, Box<RegistryFn<H>>>,
103 inclusion_filters: HashMap<String, Vec<FilterFn>>,
104 override_providers: HashMap<String, Arc<dyn StateOverrideProvider>>,
107 block_time_secs: u64,
110}
111
112fn is_deprecated_curve_registration<T: 'static>(exchange: &str) -> bool {
116 exchange == "vm:curve" &&
117 std::any::type_name::<T>() !=
118 std::any::type_name::<crate::evm::protocol::curve::CurveState>()
119}
120
121impl<H> TychoStreamDecoder<H>
122where
123 H: HeaderLike + Clone + Sync + Send + 'static + std::fmt::Debug,
124{
125 pub fn new(chain: Chain) -> Self {
131 Self {
132 state: Arc::new(RwLock::new(DecoderState::default())),
133 skip_state_decode_failures: false,
134 min_token_quality: 100,
135 registry: HashMap::new(),
136 inclusion_filters: HashMap::new(),
137 override_providers: HashMap::new(),
138 block_time_secs: chain.block_time_secs(),
139 }
140 }
141
142 fn execution_block(&self, header: &BlockHeader) -> BlockContext {
147 if header.partial_block_index.is_some() {
148 BlockContext::new(header.number, header.timestamp)
149 } else {
150 BlockContext::new(header.number + 1, header.timestamp + self.block_time_secs)
151 }
152 }
153
154 fn refresh_execution_block<C>(
163 updated_states: &mut HashMap<String, Box<dyn ProtocolSim>>,
164 stored_states: &mut HashMap<String, Box<dyn ProtocolSim>>,
165 failed_components: &HashSet<String>,
166 removed_components: &HashMap<String, C>,
167 execution_block: &BlockContext,
168 ) {
169 for state in updated_states.values_mut() {
170 state.apply_block(execution_block);
171 }
172 for (id, state) in stored_states.iter_mut() {
173 if failed_components.contains(id) ||
174 removed_components.contains_key(id) ||
175 updated_states.contains_key(id)
176 {
177 continue;
178 }
179 if state.apply_block(execution_block) {
180 updated_states.insert(id.clone(), state.clone_box());
181 }
182 }
183 }
184
185 pub fn set_override_provider(
191 &mut self,
192 protocol_system: String,
193 provider: Arc<dyn StateOverrideProvider>,
194 ) {
195 self.override_providers
196 .insert(protocol_system, provider);
197 }
198
199 pub async fn set_tokens(&self, tokens: HashMap<Bytes, Token>) {
204 let mut guard = self.state.write().await;
205 guard.tokens = tokens;
206 }
207
208 pub fn skip_state_decode_failures(&mut self, skip: bool) {
209 self.skip_state_decode_failures = skip;
210 }
211
212 pub fn min_token_quality(&mut self, quality: u32) {
218 self.min_token_quality = quality;
219 }
220
221 pub fn register_decoder_with_context<T>(&mut self, exchange: &str, context: DecoderContext)
234 where
235 T: ProtocolSim
236 + TryFromWithBlock<ComponentWithState, H, Error = InvalidSnapshotError>
237 + Send
238 + 'static,
239 {
240 if is_deprecated_curve_registration::<T>(exchange) {
241 warn!(
242 registered_type = std::any::type_name::<T>(),
243 "Registering \"vm:curve\" with the generic VM adapter is deprecated; register the \
244 native `CurveState` decoder instead (`exchange::<CurveState>(\"vm:curve\", ...)`). \
245 The VM-adapter path still works but will be removed in a future release."
246 );
247 }
248 let decoder = Box::new(
249 move |component: ComponentWithState,
250 header: H,
251 account_balances: AccountBalances,
252 state: Arc<RwLock<DecoderState>>,
253 live_override: Option<watch::Receiver<OverrideSnapshot>>| {
254 let mut context = context.clone();
255 context.live_override = live_override;
256 Box::pin(async move {
257 let guard = state.read().await;
258 T::try_from_with_header(
259 component,
260 header,
261 &account_balances,
262 &guard.tokens,
263 &context,
264 )
265 .await
266 .map(|c| Box::new(c) as Box<dyn ProtocolSim>)
267 }) as DecodeFut
268 },
269 );
270 self.registry
271 .insert(exchange.to_string(), decoder);
272 }
273
274 pub fn register_decoder<T>(&mut self, exchange: &str)
286 where
287 T: ProtocolSim
288 + TryFromWithBlock<ComponentWithState, H, Error = InvalidSnapshotError>
289 + Send
290 + 'static,
291 {
292 let context = DecoderContext::new();
293 self.register_decoder_with_context::<T>(exchange, context);
294 }
295
296 pub fn register_filter(&mut self, exchange: &str, predicate: FilterFn) {
315 self.inclusion_filters
316 .entry(exchange.to_string())
317 .or_default()
318 .push(predicate);
319 }
320
321 fn admits(&self, exchange: &str, snapshot: &ComponentWithState) -> bool {
324 let Some(predicates) = self.inclusion_filters.get(exchange) else { return true };
325 predicates
326 .iter()
327 .all(|predicate| predicate(snapshot))
328 }
329
330 pub async fn decode(&self, msg: &FeedMessage<H>) -> Result<Update, StreamDecodeError> {
333 let mut updated_states = HashMap::new();
335 let mut new_pairs = HashMap::new();
336 let mut removed_pairs = HashMap::new();
337 let mut contracts_map = HashMap::new();
338 let mut msg_failed_components = HashSet::new();
339
340 let header = msg
341 .state_msgs
342 .values()
343 .next()
344 .ok_or_else(|| StreamDecodeError::Fatal("Missing block!".into()))?
345 .header
346 .clone();
347
348 let block_number_or_timestamp = header
349 .clone()
350 .block_number_or_timestamp();
351 let current_block = header.clone().block();
352 let is_partial = current_block
353 .as_ref()
354 .map(|h| h.partial_block_index.is_some())
355 .unwrap_or(false);
356
357 for (protocol, protocol_msg) in msg.state_msgs.iter() {
358 if let Some(deltas) = protocol_msg.deltas.as_ref() {
360 let mut state_guard = self.state.write().await;
361
362 let new_tokens = deltas
363 .new_tokens
364 .iter()
365 .filter(|(addr, t)| {
366 t.quality >= self.min_token_quality &&
367 !state_guard.tokens.contains_key(*addr)
368 })
369 .map(|(addr, t)| (addr.clone(), t.clone()))
370 .collect::<HashMap<Bytes, Token>>();
371
372 if !new_tokens.is_empty() {
373 debug!(n = new_tokens.len(), "NewTokens");
374 state_guard.tokens.extend(new_tokens);
375 }
376 }
377
378 {
380 let mut state_guard = self.state.write().await;
381 let removed_components: Vec<(String, ProtocolComponent)> = protocol_msg
382 .removed_components
383 .iter()
384 .map(|(id, comp)| {
385 if *id != comp.id {
386 error!(
387 "Component id mismatch in removed components {id} != {}",
388 comp.id
389 );
390 return Err(StreamDecodeError::Fatal("Component id mismatch".into()));
391 }
392
393 let tokens = comp
394 .tokens
395 .iter()
396 .flat_map(|addr| state_guard.tokens.get(addr).cloned())
397 .collect::<Vec<_>>();
398
399 if tokens.len() == comp.tokens.len() {
400 Ok(Some((
401 id.clone(),
402 ProtocolComponent::from_with_tokens(comp.clone(), tokens),
403 )))
404 } else {
405 Ok(None)
406 }
407 })
408 .collect::<Result<Vec<Option<(String, ProtocolComponent)>>, StreamDecodeError>>(
409 )?
410 .into_iter()
411 .flatten()
412 .collect();
413
414 for (id, component) in removed_components {
416 state_guard.components.remove(&id);
417 state_guard.states.remove(&id);
418 removed_pairs.insert(id, component);
419 }
420
421 info!(
423 "Processing {} contracts from snapshots",
424 protocol_msg
425 .snapshots
426 .get_vm_storage()
427 .len()
428 );
429
430 let mut proxy_token_accounts: HashMap<Address, AccountUpdate> = HashMap::new();
431 let mut storage_by_address: HashMap<Address, ResponseAccount> = HashMap::new();
432 for (key, value) in protocol_msg
433 .snapshots
434 .get_vm_storage()
435 .iter()
436 {
437 let account: ResponseAccount = value.clone().into();
438
439 if state_guard.tokens.contains_key(key) {
440 let original_address = account.address;
441 let (impl_addr, proxy_state) = match state_guard
450 .proxy_token_addresses
451 .get(&original_address)
452 {
453 Some(impl_addr) => {
454 let proxy_state = AccountUpdate::new(
461 original_address,
462 value.chain,
463 account.slots.clone(),
464 Some(account.native_balance),
465 None,
466 ChangeType::Update,
467 );
468 (*impl_addr, proxy_state)
469 }
470 None => {
471 let impl_addr = generate_proxy_token_address(
475 state_guard.proxy_token_addresses.len() as u32,
476 )?;
477 state_guard
478 .proxy_token_addresses
479 .insert(original_address, impl_addr);
480
481 let proxy_state = create_proxy_token_account(
483 original_address,
484 Some(impl_addr),
485 &account.slots,
486 value.chain,
487 Some(account.native_balance),
488 );
489
490 (impl_addr, proxy_state)
491 }
492 };
493
494 proxy_token_accounts.insert(original_address, proxy_state);
495
496 let impl_update = ResponseAccount {
498 address: impl_addr,
499 slots: HashMap::new(),
500 ..account.clone()
501 };
502 storage_by_address.insert(impl_addr, impl_update);
503 } else {
504 storage_by_address.insert(account.address, account);
506 }
507 }
508
509 let mut proxy_creates: Vec<AccountUpdate> = Vec::new();
513 let mut proxy_updates: HashMap<Address, AccountUpdate> = HashMap::new();
514 for (addr, update) in proxy_token_accounts {
515 if matches!(update.change, ChangeType::Creation) {
516 proxy_creates.push(update);
517 } else {
518 proxy_updates.insert(addr, update);
519 }
520 }
521
522 info!("Updating engine with {} contracts from snapshots", storage_by_address.len());
523 update_engine(
524 SHARED_TYCHO_DB.clone(),
525 header.clone().block(),
526 Some(storage_by_address),
527 proxy_updates,
528 )
529 .map_err(|e| StreamDecodeError::Fatal(e.to_string()))?;
530
531 if !proxy_creates.is_empty() {
535 SHARED_TYCHO_DB
536 .force_update_accounts(proxy_creates)
537 .map_err(|e| StreamDecodeError::Fatal(e.to_string()))?;
538 }
539 info!("Engine updated");
540 drop(state_guard);
541 }
542
543 let account_balances = protocol_msg
546 .clone()
547 .snapshots
548 .get_vm_storage()
549 .iter()
550 .filter_map(|(addr, acc)| {
551 if acc.token_balances.is_empty() {
552 return None;
553 }
554 let balances = acc
555 .token_balances
556 .iter()
557 .map(|(token_addr, ab)| (token_addr.clone(), ab.balance.clone()))
558 .collect::<HashMap<Bytes, Bytes>>();
559 Some((addr.clone(), balances))
560 })
561 .collect::<AccountBalances>();
562
563 let mut new_components = HashMap::new();
564 let mut count_token_skips = 0;
565 let mut components_to_store = HashMap::new();
566 {
567 let state_guard = self.state.read().await;
568
569 'snapshot_loop: for (id, snapshot) in protocol_msg
571 .snapshots
572 .get_states()
573 .clone()
574 {
575 if !self.admits(protocol.as_str(), &snapshot) {
577 continue;
578 }
579
580 let mut component_tokens = Vec::new();
582 let mut new_tokens_accounts = HashMap::new();
583 for token in snapshot.component.tokens.clone() {
584 match state_guard.tokens.get(&token) {
585 Some(token) => {
586 component_tokens.push(token.clone());
587
588 let token_address = match bytes_to_address(&token.address) {
591 Ok(addr) => addr,
592 Err(_) => {
593 count_token_skips += 1;
594 msg_failed_components.insert(id.clone());
595 warn!(
596 "Token address could not be decoded {}, ignoring pool {:x?}",
597 token.address, id
598 );
599 continue 'snapshot_loop;
600 }
601 };
602 if !state_guard
604 .proxy_token_addresses
605 .contains_key(&token_address)
606 {
607 new_tokens_accounts.insert(
608 token_address,
609 create_proxy_token_account(
610 token_address,
611 None,
612 &HashMap::new(),
613 snapshot.component.chain,
614 None,
615 ),
616 );
617 }
618 }
619 None => {
620 count_token_skips += 1;
621 msg_failed_components.insert(id.clone());
622 debug!("Token not found {}, ignoring pool {:x?}", token, id);
623 continue 'snapshot_loop;
624 }
625 }
626 }
627 let component = ProtocolComponent::from_with_tokens(
628 snapshot.component.clone(),
629 component_tokens,
630 );
631
632 if !new_tokens_accounts.is_empty() {
634 update_engine(
635 SHARED_TYCHO_DB.clone(),
636 header.clone().block(),
637 None,
638 new_tokens_accounts,
639 )
640 .map_err(|e| StreamDecodeError::Fatal(e.to_string()))?;
641 }
642
643 if !component
646 .static_attributes
647 .contains_key("manual_updates")
648 {
649 for contract in &component.contract_ids {
650 contracts_map
651 .entry(contract.clone())
652 .or_insert_with(HashSet::new)
653 .insert(id.clone());
654 }
655 for (_, tracing) in snapshot.entrypoints.iter() {
658 for contract in tracing.accessed_slots.keys().cloned() {
659 contracts_map
660 .entry(contract)
661 .or_insert_with(HashSet::new)
662 .insert(id.clone());
663 }
664 }
665 }
666
667 new_pairs.insert(id.clone(), component.clone());
669
670 components_to_store.insert(id.clone(), component);
672
673 if let Some(state_decode_f) = self.registry.get(protocol.as_str()) {
675 let live_override = self
676 .override_providers
677 .get(protocol.as_str())
678 .and_then(|provider| provider.subscribe(protocol.as_str()));
679 match state_decode_f(
680 snapshot,
681 header.clone(),
682 account_balances.clone(),
683 self.state.clone(),
684 live_override,
685 )
686 .await
687 {
688 Ok(state) => {
689 new_components.insert(id.clone(), state);
690 }
691 Err(e) => {
692 if self.skip_state_decode_failures {
693 warn!(pool = id, error = %e, "StateDecodingFailure");
694 msg_failed_components.insert(id.clone());
695 continue 'snapshot_loop;
696 } else {
697 error!(pool = id, error = %e, "StateDecodingFailure");
698 return Err(StreamDecodeError::Fatal(format!("{e}")));
699 }
700 }
701 }
702 } else if self.skip_state_decode_failures {
703 warn!(pool = id, "MissingDecoderRegistration");
704 msg_failed_components.insert(id.clone());
705 continue 'snapshot_loop;
706 } else {
707 error!(pool = id, "MissingDecoderRegistration");
708 return Err(StreamDecodeError::Fatal(format!(
709 "Missing decoder registration for: {id}"
710 )));
711 }
712 }
713 }
714
715 if !components_to_store.is_empty() {
717 let mut state_guard = self.state.write().await;
718 for (id, component) in components_to_store {
719 state_guard
720 .components
721 .insert(id, component);
722 }
723 }
724
725 if !protocol_msg.snapshots.states.is_empty() {
726 info!("Decoded {} snapshots for protocol {protocol}", new_components.len());
727 }
728 if count_token_skips > 0 {
729 info!("Skipped {count_token_skips} pools due to missing tokens");
730 }
731
732 updated_states.extend(new_components);
734
735 if let Some(deltas) = protocol_msg.deltas.clone() {
737 let mut state_guard = self.state.write().await;
739
740 let mut account_update_by_address: HashMap<Address, AccountUpdate> = HashMap::new();
741 let mut new_proxy_accounts: Vec<AccountUpdate> = Vec::new();
743 for (key, value) in deltas.account_deltas.iter() {
744 let mut update: AccountUpdate = value.clone().into();
745
746 if update.code.is_none() && matches!(update.change, ChangeType::Creation) {
752 error!(
753 update = ?update,
754 "FaultyCreationDelta"
755 );
756 update.code = Some(vec![]);
757 }
758
759 if state_guard.tokens.contains_key(key) {
760 let original_address = update.address;
761 let impl_addr = match state_guard
768 .proxy_token_addresses
769 .get(&original_address)
770 {
771 Some(impl_addr) => {
772 let proxy_update = AccountUpdate {
777 code: None,
778 change: ChangeType::Update,
779 ..update.clone()
780 };
781 account_update_by_address.insert(original_address, proxy_update);
782
783 *impl_addr
784 }
785 None => {
786 let impl_addr = generate_proxy_token_address(
791 state_guard.proxy_token_addresses.len() as u32,
792 )?;
793 state_guard
794 .proxy_token_addresses
795 .insert(original_address, impl_addr);
796
797 let proxy_state = create_proxy_token_account(
802 original_address,
803 Some(impl_addr),
804 &update.slots,
805 update.chain,
806 update.balance,
807 );
808 new_proxy_accounts.push(proxy_state);
809
810 impl_addr
811 }
812 };
813
814 if update.code.is_some() {
816 let impl_update = AccountUpdate {
817 address: impl_addr,
818 slots: HashMap::new(),
819 ..update.clone()
820 };
821 account_update_by_address.insert(impl_addr, impl_update);
822 }
823 } else {
824 account_update_by_address.insert(update.address, update);
826 }
827 }
828 drop(state_guard);
829
830 let state_guard = self.state.read().await;
831 info!("Updating engine with {} contract deltas", deltas.account_deltas.len());
832 update_engine(
833 SHARED_TYCHO_DB.clone(),
834 header.clone().block(),
835 None,
836 account_update_by_address,
837 )
838 .map_err(|e| StreamDecodeError::Fatal(e.to_string()))?;
839
840 if !new_proxy_accounts.is_empty() {
843 SHARED_TYCHO_DB
844 .force_update_accounts(new_proxy_accounts)
845 .map_err(|e| StreamDecodeError::Fatal(e.to_string()))?;
846 }
847 info!("Engine updated");
848
849 let mut pools_to_update = HashSet::new();
851 for (account, _update) in deltas.account_deltas {
852 pools_to_update.extend(
854 contracts_map
855 .get(&account)
856 .cloned()
857 .unwrap_or_default(),
858 );
859 pools_to_update.extend(
861 state_guard
862 .contracts_map
863 .get(&account)
864 .cloned()
865 .unwrap_or_default(),
866 );
867 }
868
869 let all_balances = Balances {
871 component_balances: deltas
872 .component_balances
873 .iter()
874 .map(|(pool_id, bals)| {
875 let mut balances = HashMap::new();
876 for (t, b) in bals {
877 balances.insert(t.clone(), b.balance.clone());
878 }
879 pools_to_update.insert(pool_id.clone());
880 (pool_id.clone(), balances)
881 })
882 .collect(),
883 account_balances: deltas
884 .account_balances
885 .iter()
886 .map(|(account, bals)| {
887 let mut balances = HashMap::new();
888 for (t, b) in bals {
889 balances.insert(t.clone(), b.balance.clone());
890 }
891 pools_to_update.extend(
892 contracts_map
893 .get(account)
894 .cloned()
895 .unwrap_or_default(),
896 );
897 (account.clone(), balances)
898 })
899 .collect(),
900 };
901
902 for (id, update) in deltas.state_deltas {
904 let update_with_block = Self::add_block_info_to_delta(
906 ProtocolStateDelta::from(update),
907 current_block.clone(),
908 );
909 match Self::apply_update(
910 &id,
911 update_with_block,
912 &mut updated_states,
913 &state_guard,
914 &all_balances,
915 ) {
916 Ok(_) => {
917 pools_to_update.remove(&id);
918 }
919 Err(e) => {
920 if self.skip_state_decode_failures {
921 warn!(pool = id, error = %e, "Failed to apply state update, marking component as removed");
922 updated_states.remove(&id);
924 if let Some(component) = new_pairs.remove(&id) {
926 removed_pairs.insert(id.clone(), component);
927 } else if let Some(component) = state_guard.components.get(&id) {
928 removed_pairs.insert(id.clone(), component.clone());
929 } else {
930 warn!(pool = id, "Component not found in new_pairs or state, cannot add to removed_pairs");
933 }
934 pools_to_update.remove(&id);
935
936 msg_failed_components.insert(id.clone());
938 } else {
939 return Err(e);
940 }
941 }
942 }
943 }
944
945 for pool in pools_to_update {
947 let default_delta_with_block = Self::add_block_info_to_delta(
949 ProtocolStateDelta::default(),
950 current_block.clone(),
951 );
952 match Self::apply_update(
953 &pool,
954 default_delta_with_block,
955 &mut updated_states,
956 &state_guard,
957 &all_balances,
958 ) {
959 Ok(_) => {}
960 Err(e) => {
961 if self.skip_state_decode_failures {
962 warn!(pool = pool, error = %e, "Failed to apply contract/balance update, marking component as removed");
963 updated_states.remove(&pool);
965 if let Some(component) = new_pairs.remove(&pool) {
967 removed_pairs.insert(pool.clone(), component);
968 } else if let Some(component) = state_guard.components.get(&pool) {
969 removed_pairs.insert(pool.clone(), component.clone());
970 } else {
971 warn!(pool = pool, "Component not found in new_pairs or state, cannot add to removed_pairs");
974 }
975
976 msg_failed_components.insert(pool.clone());
978 } else {
979 return Err(e);
980 }
981 }
982 }
983 }
984 };
985 }
986
987 let mut state_guard = self.state.write().await;
989
990 state_guard
992 .failed_components
993 .extend(msg_failed_components);
994
995 updated_states.retain(|id, _| {
999 !state_guard
1000 .failed_components
1001 .contains(id)
1002 });
1003 new_pairs.retain(|id, _| {
1004 !state_guard
1005 .failed_components
1006 .contains(id)
1007 });
1008
1009 if let Some(header) = current_block.as_ref() {
1010 let execution_block = self.execution_block(header);
1011 let decoder_state = &mut *state_guard;
1012 Self::refresh_execution_block(
1013 &mut updated_states,
1014 &mut decoder_state.states,
1015 &decoder_state.failed_components,
1016 &removed_pairs,
1017 &execution_block,
1018 );
1019 }
1020
1021 state_guard
1022 .states
1023 .extend(updated_states.clone());
1024
1025 state_guard.current_block_number = block_number_or_timestamp;
1026
1027 for (id, component) in new_pairs.iter() {
1029 state_guard
1030 .components
1031 .insert(id.clone(), component.clone());
1032 }
1033
1034 for id in removed_pairs.keys() {
1036 state_guard.components.remove(id);
1037 }
1038
1039 for (key, values) in contracts_map {
1040 state_guard
1041 .contracts_map
1042 .entry(key)
1043 .or_insert_with(HashSet::new)
1044 .extend(values);
1045 }
1046
1047 Ok(Update::new(block_number_or_timestamp, updated_states, new_pairs)
1049 .set_is_partial(is_partial)
1050 .set_removed_pairs(removed_pairs)
1051 .set_sync_states(msg.sync_states.clone()))
1052 }
1053
1054 pub async fn apply_deltas_ephemeral(
1074 &self,
1075 pending_deltas: &HashMap<String, BlockAggregatedChanges>,
1076 header: H,
1077 ) -> Result<Update, StreamDecodeError> {
1078 let block_number_or_timestamp = header
1079 .clone()
1080 .block_number_or_timestamp();
1081 let current_block = header.block();
1082 let state_guard = self.state.read().await;
1083
1084 let mut updated_states: HashMap<String, Box<dyn ProtocolSim>> = HashMap::new();
1085
1086 for deltas in pending_deltas.values() {
1087 let all_balances = Balances {
1088 component_balances: deltas
1089 .component_balances
1090 .iter()
1091 .map(|(pool_id, bals)| {
1092 let balances = bals
1093 .iter()
1094 .map(|(t, b)| (t.clone(), b.balance.clone()))
1095 .collect();
1096 (pool_id.clone(), balances)
1097 })
1098 .collect(),
1099 account_balances: HashMap::new(),
1100 };
1101
1102 for (id, state_delta) in &deltas.state_deltas {
1103 let dto_delta = Self::add_block_info_to_delta(
1104 ProtocolStateDelta::from(state_delta.clone()),
1105 current_block.clone(),
1106 );
1107 if let Err(e) = Self::apply_update(
1108 id,
1109 dto_delta,
1110 &mut updated_states,
1111 &state_guard,
1112 &all_balances,
1113 ) {
1114 warn!(pool = id, error = %e, "EphemeralDeltaTransitionError");
1115 }
1116 }
1117 }
1118
1119 if let Some(header) = current_block.as_ref() {
1124 let execution_block = BlockContext::new(header.number, header.timestamp);
1125 for state in updated_states.values_mut() {
1126 state.apply_block(&execution_block);
1127 }
1128 }
1129
1130 Ok(Update::new(block_number_or_timestamp, updated_states, HashMap::new()))
1131 }
1132
1133 fn add_block_info_to_delta(
1135 mut delta: ProtocolStateDelta,
1136 block_header_opt: Option<BlockHeader>,
1137 ) -> ProtocolStateDelta {
1138 if let Some(header) = block_header_opt {
1139 delta.updated_attributes.insert(
1142 "block_number".to_string(),
1143 Bytes::from(header.number.to_be_bytes().to_vec()),
1144 );
1145 delta.updated_attributes.insert(
1146 "block_timestamp".to_string(),
1147 Bytes::from(header.timestamp.to_be_bytes().to_vec()),
1148 );
1149 }
1150 delta
1151 }
1152
1153 fn apply_update(
1154 id: &String,
1155 update: ProtocolStateDelta,
1156 updated_states: &mut HashMap<String, Box<dyn ProtocolSim>>,
1157 state_guard: &RwLockReadGuard<'_, DecoderState>,
1158 all_balances: &Balances,
1159 ) -> Result<(), StreamDecodeError> {
1160 match updated_states.entry(id.clone()) {
1161 Entry::Occupied(mut entry) => {
1162 let state: &mut Box<dyn ProtocolSim> = entry.get_mut();
1164 state
1165 .delta_transition(update, &state_guard.tokens, all_balances)
1166 .map_err(|e| {
1167 error!(pool = id, error = ?e, "DeltaTransitionError");
1168 StreamDecodeError::Fatal(format!("TransitionFailure: {e:?}"))
1169 })?;
1170 }
1171 Entry::Vacant(_) => {
1172 match state_guard.states.get(id) {
1173 Some(stored_state) => {
1176 let mut state = stored_state.clone();
1177 state
1178 .delta_transition(update, &state_guard.tokens, all_balances)
1179 .map_err(|e| {
1180 error!(pool = id, error = ?e, "DeltaTransitionError");
1181 StreamDecodeError::Fatal(format!("TransitionFailure: {e:?}"))
1182 })?;
1183 updated_states.insert(id.clone(), state);
1184 }
1185 None => debug!(pool = id, reason = "MissingState", "DeltaTransitionError"),
1186 }
1187 }
1188 }
1189 Ok(())
1190 }
1191}
1192
1193fn generate_proxy_token_address(idx: u32) -> Result<Address, StreamDecodeError> {
1195 let padded_idx = format!("{idx:x}");
1196 let padded_zeroes = "0".repeat(33 - padded_idx.len());
1197 let proxy_token_address = format!("{padded_zeroes}{padded_idx}BAdbaBe");
1198 let decoded = hex::decode(proxy_token_address).map_err(|e| {
1199 StreamDecodeError::Fatal(format!("Invalid proxy token address encoding: {e}"))
1200 })?;
1201
1202 const ADDRESS_LENGTH: usize = 20;
1203 if decoded.len() != ADDRESS_LENGTH {
1204 return Err(StreamDecodeError::Fatal(format!(
1205 "Invalid proxy token address length: expected {}, got {}",
1206 ADDRESS_LENGTH,
1207 decoded.len(),
1208 )));
1209 }
1210
1211 Ok(Address::from_slice(&decoded))
1212}
1213
1214fn create_proxy_token_account(
1219 addr: Address,
1220 new_address: Option<Address>,
1221 storage: &HashMap<U256, U256>,
1222 chain: Chain,
1223 balance: Option<U256>,
1224) -> AccountUpdate {
1225 let mut slots = storage.clone();
1226 if let Some(new_address) = new_address {
1227 slots.insert(*IMPLEMENTATION_SLOT, U256::from_be_slice(new_address.as_slice()));
1228 }
1229
1230 AccountUpdate {
1231 address: addr,
1232 chain,
1233 slots,
1234 balance,
1235 code: Some(ERC20_PROXY_BYTECODE.to_vec()),
1236 change: ChangeType::Creation,
1237 }
1238}
1239
1240#[cfg(test)]
1241mock! {
1242 #[derive(Debug)]
1243 pub ProtocolSim {
1244 pub fn fee(&self) -> f64;
1245 pub fn spot_price(&self, base: &Token, quote: &Token) -> Result<f64, SimulationError>;
1246 pub fn get_amount_out(
1247 &self,
1248 amount_in: BigUint,
1249 token_in: &Token,
1250 token_out: &Token,
1251 ) -> Result<GetAmountOutResult, SimulationError>;
1252 pub fn get_limits(
1253 &self,
1254 sell_token: Bytes,
1255 buy_token: Bytes,
1256 ) -> Result<(BigUint, BigUint), SimulationError>;
1257 pub fn delta_transition(
1258 &mut self,
1259 delta: ProtocolStateDelta,
1260 tokens: &HashMap<Bytes, Token>,
1261 balances: &Balances,
1262 ) -> Result<(), TransitionError>;
1263 pub fn clone_box(&self) -> Box<dyn ProtocolSim>;
1264 pub fn eq(&self, other: &dyn ProtocolSim) -> bool;
1265 }
1266}
1267
1268#[cfg(test)]
1269crate::impl_non_serializable_protocol!(MockProtocolSim, "test protocol");
1270
1271#[cfg(test)]
1272impl ProtocolSim for MockProtocolSim {
1273 fn fee(&self) -> f64 {
1274 self.fee()
1275 }
1276
1277 fn spot_price(&self, base: &Token, quote: &Token) -> Result<f64, SimulationError> {
1278 self.spot_price(base, quote)
1279 }
1280
1281 fn get_amount_out(
1282 &self,
1283 amount_in: BigUint,
1284 token_in: &Token,
1285 token_out: &Token,
1286 ) -> Result<GetAmountOutResult, SimulationError> {
1287 self.get_amount_out(amount_in, token_in, token_out)
1288 }
1289
1290 fn get_limits(
1291 &self,
1292 sell_token: Bytes,
1293 buy_token: Bytes,
1294 ) -> Result<(BigUint, BigUint), SimulationError> {
1295 self.get_limits(sell_token, buy_token)
1296 }
1297
1298 fn delta_transition(
1299 &mut self,
1300 delta: ProtocolStateDelta,
1301 tokens: &HashMap<Bytes, Token>,
1302 balances: &Balances,
1303 ) -> Result<(), TransitionError> {
1304 self.delta_transition(delta, tokens, balances)
1305 }
1306
1307 fn clone_box(&self) -> Box<dyn ProtocolSim> {
1308 self.clone_box()
1309 }
1310
1311 fn as_any(&self) -> &dyn Any {
1312 panic!("MockProtocolSim does not support as_any")
1313 }
1314
1315 fn as_any_mut(&mut self) -> &mut dyn Any {
1316 panic!("MockProtocolSim does not support as_any_mut")
1317 }
1318
1319 fn eq(&self, other: &dyn ProtocolSim) -> bool {
1320 self.eq(other)
1321 }
1322
1323 fn typetag_name(&self) -> &'static str {
1324 unreachable!()
1325 }
1326
1327 fn typetag_deserialize(&self) {
1328 unreachable!()
1329 }
1330}
1331
1332#[cfg(test)]
1333mod tests {
1334 use std::str::FromStr;
1335
1336 use alloy::primitives::address;
1337 use mockall::predicate::*;
1338 use rstest::*;
1339 use tycho_client::feed::BlockHeader;
1340 use tycho_common::{models::Chain, Bytes};
1341
1342 use super::*;
1343
1344 fn header_at(number: u64, timestamp: u64, partial: Option<u32>) -> BlockHeader {
1345 BlockHeader {
1346 hash: Bytes::from([0u8; 32]),
1347 number,
1348 parent_hash: Bytes::from([0u8; 32]),
1349 revert: false,
1350 timestamp,
1351 partial_block_index: partial,
1352 }
1353 }
1354
1355 fn block_sensitive_state() -> Box<dyn ProtocolSim> {
1357 use crate::evm::protocol::{
1358 aerodrome_slipstreams::state::AerodromeSlipstreamsState,
1359 utils::{
1360 slipstreams::{dynamic_fee_module::DynamicFeeConfig, observations::Observation},
1361 uniswap::{tick_list::TickInfo, tick_math::get_sqrt_ratio_at_tick},
1362 },
1363 };
1364
1365 Box::new(
1366 AerodromeSlipstreamsState::new(
1367 "block-sensitive".to_string(),
1368 0,
1369 1_000_000_000_000_000_000,
1370 get_sqrt_ratio_at_tick(0).unwrap(),
1371 0,
1372 1,
1373 3000,
1374 1,
1375 0,
1376 vec![TickInfo::new(-120, 0).unwrap(), TickInfo::new(120, 0).unwrap()],
1377 vec![Observation { block_timestamp: 500, initialized: true, ..Default::default() }],
1378 DynamicFeeConfig::new(2700, 30_000, 0, true, 750),
1379 )
1380 .expect("state should build")
1381 .with_position_assumption(crate::protocol::models::BlockPositionAssumption::First),
1384 )
1385 }
1386
1387 #[test]
1388 fn confirmed_header_targets_the_next_block() {
1389 let decoder = TychoStreamDecoder::<BlockHeader>::new(Chain::Base);
1390
1391 let execution_block = decoder.execution_block(&header_at(100, 1_000, None));
1392
1393 assert_eq!(execution_block.number(), 101);
1394 assert_eq!(execution_block.timestamp(), 1_000 + Chain::Base.block_time_secs());
1395 }
1396
1397 #[test]
1398 fn partial_header_targets_the_block_that_is_still_open() {
1399 let decoder = TychoStreamDecoder::<BlockHeader>::new(Chain::Ethereum);
1400
1401 let execution_block = decoder.execution_block(&header_at(100, 1_000, Some(3)));
1402
1403 assert_eq!(execution_block.number(), 100);
1404 assert_eq!(execution_block.timestamp(), 1_000);
1405 }
1406
1407 #[test]
1408 fn refresh_re_emits_a_state_whose_fee_flipped_without_a_delta() {
1409 let mut stored = HashMap::from([("block-sensitive".to_string(), {
1413 let mut state = block_sensitive_state();
1414 state.apply_block(&BlockContext::new(100, 500));
1415 state
1416 })]);
1417 let mut updated: HashMap<String, Box<dyn ProtocolSim>> = HashMap::new();
1418
1419 TychoStreamDecoder::<BlockHeader>::refresh_execution_block(
1420 &mut updated,
1421 &mut stored,
1422 &HashSet::new(),
1423 &HashMap::<String, ()>::new(),
1424 &BlockContext::new(101, 502),
1425 );
1426
1427 let emitted = updated
1428 .get("block-sensitive")
1429 .expect("a fee flip must be emitted even without a delta");
1430 assert_eq!(emitted.fee(), 750.0 / 1_000_000.0);
1431 assert_eq!(stored["block-sensitive"].fee(), 750.0 / 1_000_000.0);
1433 }
1434
1435 #[test]
1436 fn refresh_never_re_emits_failed_components() {
1437 let mut stored = HashMap::from([("zombie".to_string(), {
1440 let mut state = block_sensitive_state();
1441 state.apply_block(&BlockContext::new(100, 500));
1442 state
1443 })]);
1444 let failed = HashSet::from(["zombie".to_string()]);
1445 let mut updated: HashMap<String, Box<dyn ProtocolSim>> = HashMap::new();
1446
1447 TychoStreamDecoder::<BlockHeader>::refresh_execution_block(
1448 &mut updated,
1449 &mut stored,
1450 &failed,
1451 &HashMap::<String, ()>::new(),
1452 &BlockContext::new(101, 502),
1453 );
1454
1455 assert!(updated.is_empty());
1456 }
1457
1458 #[test]
1459 fn refresh_never_re_emits_removed_components() {
1460 let mut stored = HashMap::from([("gone".to_string(), {
1463 let mut state = block_sensitive_state();
1464 state.apply_block(&BlockContext::new(100, 500));
1465 state
1466 })]);
1467 let removed = HashMap::from([("gone".to_string(), ())]);
1468 let mut updated: HashMap<String, Box<dyn ProtocolSim>> = HashMap::new();
1469
1470 TychoStreamDecoder::<BlockHeader>::refresh_execution_block(
1471 &mut updated,
1472 &mut stored,
1473 &HashSet::new(),
1474 &removed,
1475 &BlockContext::new(101, 502),
1476 );
1477
1478 assert!(updated.is_empty());
1479 }
1480
1481 #[test]
1482 fn refresh_stays_quiet_when_no_fee_changed() {
1483 let mut stored: HashMap<String, Box<dyn ProtocolSim>> = HashMap::from([
1486 ("idle-sensitive".to_string(), {
1487 let mut state = block_sensitive_state();
1488 state.apply_block(&BlockContext::new(101, 502));
1489 state
1490 }),
1491 (
1492 "univ2".to_string(),
1493 Box::new(crate::evm::protocol::uniswap_v2::state::UniswapV2State::new(
1494 U256::from(1_000_000u64),
1495 U256::from(1_000_000u64),
1496 )) as Box<dyn ProtocolSim>,
1497 ),
1498 ]);
1499 let mut updated: HashMap<String, Box<dyn ProtocolSim>> = HashMap::new();
1500
1501 TychoStreamDecoder::<BlockHeader>::refresh_execution_block(
1502 &mut updated,
1503 &mut stored,
1504 &HashSet::new(),
1505 &HashMap::<String, ()>::new(),
1506 &BlockContext::new(102, 504),
1507 );
1508
1509 assert!(updated.is_empty());
1510 }
1511 use crate::evm::protocol::{curve::CurveState, uniswap_v2::state::UniswapV2State};
1512
1513 #[test]
1514 fn curve_vm_adapter_registration_flagged_deprecated() {
1515 assert!(!is_deprecated_curve_registration::<CurveState>("vm:curve"));
1517 assert!(is_deprecated_curve_registration::<UniswapV2State>("vm:curve"));
1519 assert!(!is_deprecated_curve_registration::<UniswapV2State>("uniswap_v2"));
1521 }
1522
1523 async fn setup_decoder(set_tokens: bool) -> TychoStreamDecoder<BlockHeader> {
1524 let mut decoder = TychoStreamDecoder::new(Chain::Ethereum);
1525 decoder.register_decoder::<UniswapV2State>("uniswap_v2");
1526 if set_tokens {
1527 let tokens = [
1528 Bytes::from("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").lpad(20, 0),
1529 Bytes::from("0xdac17f958d2ee523a2206206994597c13d831ec7").lpad(20, 0),
1530 ]
1531 .iter()
1532 .map(|addr| {
1533 let addr_str = format!("{addr:x}");
1534 (
1535 addr.clone(),
1536 Token::new(addr, &addr_str, 18, 100, &[Some(100_000)], Chain::Ethereum, 100),
1537 )
1538 })
1539 .collect();
1540 decoder.set_tokens(tokens).await;
1541 }
1542 decoder
1543 }
1544
1545 fn load_test_msg(name: &str) -> FeedMessage<BlockHeader> {
1546 use std::{fs, path::Path};
1547
1548 use tycho_client::feed::dto;
1549 let project_root = env!("CARGO_MANIFEST_DIR");
1550 let asset_path = Path::new(project_root).join(format!("tests/assets/decoder/{name}.json"));
1551 let json_data = fs::read_to_string(asset_path).expect("Failed to read test asset");
1552 let feed_msg: dto::FeedMessage<BlockHeader> =
1553 serde_json::from_str(&json_data).expect("Failed to deserialize FeedMsg json!");
1554 FeedMessage::from(feed_msg)
1555 }
1556
1557 #[tokio::test]
1558 async fn test_decode() {
1559 let decoder = setup_decoder(true).await;
1560
1561 let msg = load_test_msg("uniswap_v2_snapshot");
1562 let res1 = decoder
1563 .decode(&msg)
1564 .await
1565 .expect("decode failure");
1566 let msg = load_test_msg("uniswap_v2_delta");
1567 let res2 = decoder
1568 .decode(&msg)
1569 .await
1570 .expect("decode failure");
1571
1572 assert_eq!(res1.states.len(), 1);
1573 assert_eq!(res2.states.len(), 1);
1574 assert_eq!(res1.sync_states.len(), 1);
1575 assert_eq!(res2.sync_states.len(), 1);
1576 }
1577
1578 #[tokio::test]
1579 async fn test_decode_token_creation_delta_with_existing_proxy() {
1580 let decoder = setup_decoder(true).await;
1581 let msg = load_test_msg("uniswap_v2_delta_token_creation");
1582
1583 decoder
1586 .decode(&msg)
1587 .await
1588 .expect("first decode (proxy creation) failed");
1589
1590 decoder
1594 .decode(&msg)
1595 .await
1596 .expect("decode of a token Creation delta with an existing proxy failed");
1597 }
1598
1599 #[tokio::test]
1600 async fn test_decode_component_missing_token() {
1601 let decoder = setup_decoder(false).await;
1602 let tokens = [Bytes::from("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").lpad(20, 0)]
1603 .iter()
1604 .map(|addr| {
1605 let addr_str = format!("{addr:x}");
1606 (
1607 addr.clone(),
1608 Token::new(addr, &addr_str, 18, 100, &[Some(100_000)], Chain::Ethereum, 100),
1609 )
1610 })
1611 .collect();
1612 decoder.set_tokens(tokens).await;
1613
1614 let msg = load_test_msg("uniswap_v2_snapshot");
1615 let res1 = decoder
1616 .decode(&msg)
1617 .await
1618 .expect("decode failure");
1619
1620 assert_eq!(res1.states.len(), 0);
1621 }
1622
1623 #[tokio::test]
1624 async fn test_decode_component_bad_id() {
1625 let decoder = setup_decoder(true).await;
1626 let msg = load_test_msg("uniswap_v2_snapshot_broken_id");
1627
1628 match decoder.decode(&msg).await {
1629 Err(StreamDecodeError::Fatal(msg)) => {
1630 assert_eq!(msg, "Component id mismatch");
1631 }
1632 Ok(_) => {
1633 panic!("Expected failures to be raised")
1634 }
1635 }
1636 }
1637
1638 #[rstest]
1639 #[case(true)]
1640 #[case(false)]
1641 #[tokio::test]
1642 async fn test_decode_component_bad_state(#[case] skip_failures: bool) {
1643 let mut decoder = setup_decoder(true).await;
1644 decoder.skip_state_decode_failures = skip_failures;
1645
1646 let msg = load_test_msg("uniswap_v2_snapshot_broken_state");
1647 match decoder.decode(&msg).await {
1648 Err(StreamDecodeError::Fatal(msg)) => {
1649 if !skip_failures {
1650 assert_eq!(msg, "Missing attributes reserve0");
1651 } else {
1652 panic!("Expected failures to be ignored. Err: {msg}")
1653 }
1654 }
1655 Ok(res) => {
1656 if !skip_failures {
1657 panic!("Expected failures to be raised")
1658 } else {
1659 assert_eq!(res.states.len(), 0);
1660 }
1661 }
1662 }
1663 }
1664
1665 #[tokio::test]
1666 async fn test_decode_updates_state_on_contract_change() {
1667 let decoder = setup_decoder(true).await;
1668
1669 let mut mock_state = MockProtocolSim::new();
1671
1672 mock_state
1673 .expect_clone_box()
1674 .times(1)
1675 .returning(|| {
1676 let mut cloned_mock_state = MockProtocolSim::new();
1677 cloned_mock_state
1679 .expect_delta_transition()
1680 .times(1)
1681 .returning(|_, _, _| Ok(()));
1682 cloned_mock_state
1683 .expect_clone_box()
1684 .times(1)
1685 .returning(|| Box::new(MockProtocolSim::new()));
1686 Box::new(cloned_mock_state)
1687 });
1688
1689 let pool_id =
1691 "0x93d199263632a4ef4bb438f1feb99e57b4b5f0bd0000000000000000000005c2".to_string();
1692 decoder
1693 .state
1694 .write()
1695 .await
1696 .states
1697 .insert(pool_id.clone(), Box::new(mock_state) as Box<dyn ProtocolSim>);
1698 decoder
1699 .state
1700 .write()
1701 .await
1702 .contracts_map
1703 .insert(
1704 Bytes::from("0xba12222222228d8ba445958a75a0704d566bf2c8").lpad(20, 0),
1705 HashSet::from([pool_id.clone()]),
1706 );
1707
1708 let msg = load_test_msg("balancer_v2_delta");
1710
1711 let _ = decoder
1713 .decode(&msg)
1714 .await
1715 .expect("decode failure");
1716
1717 }
1719
1720 #[test]
1721 fn test_generate_proxy_token_address() {
1722 let idx = 1;
1723 let generated_address =
1724 generate_proxy_token_address(idx).expect("proxy token address should be valid");
1725 assert_eq!(generated_address, address!("000000000000000000000000000000001badbabe"));
1726
1727 let idx = 123456;
1728 let generated_address =
1729 generate_proxy_token_address(idx).expect("proxy token address should be valid");
1730 assert_eq!(generated_address, address!("00000000000000000000000000001e240badbabe"));
1731 }
1732
1733 #[tokio::test(flavor = "multi_thread")]
1734 async fn test_euler_hook_low_pool_manager_balance() {
1735 let mut decoder = TychoStreamDecoder::new(Chain::Ethereum);
1736
1737 decoder.register_decoder_with_context::<crate::evm::protocol::uniswap_v4::state::UniswapV4State>(
1738 "uniswap_v4_hooks", DecoderContext::new().vm_traces(true)
1739 );
1740
1741 let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap();
1742 let teth = Bytes::from_str("0xd11c452fc99cf405034ee446803b6f6c1f6d5ed8").unwrap();
1743 let tokens = HashMap::from([
1744 (
1745 weth.clone(),
1746 Token::new(&weth, "WETH", 18, 100, &[Some(100_000)], Chain::Ethereum, 100),
1747 ),
1748 (
1749 teth.clone(),
1750 Token::new(&teth, "tETH", 18, 100, &[Some(100_000)], Chain::Ethereum, 100),
1751 ),
1752 ]);
1753
1754 decoder.set_tokens(tokens.clone()).await;
1755
1756 let msg = load_test_msg("euler_hook_snapshot");
1757 let res = decoder
1758 .decode(&msg)
1759 .await
1760 .expect("decode failure");
1761
1762 let pool_state = res
1763 .states
1764 .get("0xc70d7fbd7fcccdf726e02fed78548b40dc52502b097c7a1ee7d995f4d4396134")
1765 .expect("Couldn't find target pool");
1766 let amount_out = pool_state
1767 .get_amount_out(
1768 BigUint::from_str("1000000000000000000").unwrap(),
1769 tokens.get(&teth).unwrap(),
1770 tokens.get(&weth).unwrap(),
1771 )
1772 .expect("Get amount out failed");
1773
1774 assert_eq!(amount_out.amount, BigUint::from_str("1216190190361759119").unwrap());
1775 }
1776
1777 fn component_with_id(id: &str) -> ComponentWithState {
1778 use tycho_common::models::protocol::{ProtocolComponent, ProtocolComponentState};
1779
1780 ComponentWithState {
1781 state: ProtocolComponentState::new(id, HashMap::new(), HashMap::new()),
1782 component: ProtocolComponent { id: id.to_string(), ..Default::default() },
1783 component_tvl: None,
1784 entrypoints: Vec::new(),
1785 }
1786 }
1787
1788 fn rejects_a(component: &ComponentWithState) -> bool {
1789 component.component.id != "a"
1790 }
1791
1792 fn rejects_b(component: &ComponentWithState) -> bool {
1793 component.component.id != "b"
1794 }
1795
1796 #[test]
1797 fn test_admits_requires_every_registered_filter() {
1798 let mut decoder = TychoStreamDecoder::<BlockHeader>::new(Chain::Ethereum);
1801 decoder.register_filter("x", rejects_a);
1802 decoder.register_filter("x", rejects_b);
1803
1804 assert!(!decoder.admits("x", &component_with_id("a")));
1805 assert!(!decoder.admits("x", &component_with_id("b")));
1806 assert!(decoder.admits("x", &component_with_id("c")));
1807 assert!(decoder.admits("y", &component_with_id("a")));
1808 }
1809}