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(
1073 &self,
1074 pending_deltas: &HashMap<String, BlockAggregatedChanges>,
1075 header: H,
1076 ) -> Result<Update, StreamDecodeError> {
1077 let block_number_or_timestamp = header
1078 .clone()
1079 .block_number_or_timestamp();
1080 let current_block = header.block();
1081 let state_guard = self.state.read().await;
1082
1083 let mut updated_states: HashMap<String, Box<dyn ProtocolSim>> = HashMap::new();
1084
1085 for deltas in pending_deltas.values() {
1086 let all_balances = Balances {
1087 component_balances: deltas
1088 .component_balances
1089 .iter()
1090 .map(|(pool_id, bals)| {
1091 let balances = bals
1092 .iter()
1093 .map(|(t, b)| (t.clone(), b.balance.clone()))
1094 .collect();
1095 (pool_id.clone(), balances)
1096 })
1097 .collect(),
1098 account_balances: HashMap::new(),
1099 };
1100
1101 for (id, state_delta) in &deltas.state_deltas {
1102 let dto_delta = Self::add_block_info_to_delta(
1103 ProtocolStateDelta::from(state_delta.clone()),
1104 current_block.clone(),
1105 );
1106 if let Err(e) = Self::apply_update(
1107 id,
1108 dto_delta,
1109 &mut updated_states,
1110 &state_guard,
1111 &all_balances,
1112 ) {
1113 warn!(pool = id, error = %e, "EphemeralDeltaTransitionError");
1114 }
1115 }
1116 }
1117
1118 if let Some(header) = current_block.as_ref() {
1123 let execution_block = BlockContext::new(header.number, header.timestamp);
1124 for state in updated_states.values_mut() {
1125 state.apply_block(&execution_block);
1126 }
1127 }
1128
1129 Ok(Update::new(block_number_or_timestamp, updated_states, HashMap::new()))
1130 }
1131
1132 fn add_block_info_to_delta(
1134 mut delta: ProtocolStateDelta,
1135 block_header_opt: Option<BlockHeader>,
1136 ) -> ProtocolStateDelta {
1137 if let Some(header) = block_header_opt {
1138 delta.updated_attributes.insert(
1141 "block_number".to_string(),
1142 Bytes::from(header.number.to_be_bytes().to_vec()),
1143 );
1144 delta.updated_attributes.insert(
1145 "block_timestamp".to_string(),
1146 Bytes::from(header.timestamp.to_be_bytes().to_vec()),
1147 );
1148 }
1149 delta
1150 }
1151
1152 fn apply_update(
1153 id: &String,
1154 update: ProtocolStateDelta,
1155 updated_states: &mut HashMap<String, Box<dyn ProtocolSim>>,
1156 state_guard: &RwLockReadGuard<'_, DecoderState>,
1157 all_balances: &Balances,
1158 ) -> Result<(), StreamDecodeError> {
1159 match updated_states.entry(id.clone()) {
1160 Entry::Occupied(mut entry) => {
1161 let state: &mut Box<dyn ProtocolSim> = entry.get_mut();
1163 state
1164 .delta_transition(update, &state_guard.tokens, all_balances)
1165 .map_err(|e| {
1166 error!(pool = id, error = ?e, "DeltaTransitionError");
1167 StreamDecodeError::Fatal(format!("TransitionFailure: {e:?}"))
1168 })?;
1169 }
1170 Entry::Vacant(_) => {
1171 match state_guard.states.get(id) {
1172 Some(stored_state) => {
1175 let mut state = stored_state.clone();
1176 state
1177 .delta_transition(update, &state_guard.tokens, all_balances)
1178 .map_err(|e| {
1179 error!(pool = id, error = ?e, "DeltaTransitionError");
1180 StreamDecodeError::Fatal(format!("TransitionFailure: {e:?}"))
1181 })?;
1182 updated_states.insert(id.clone(), state);
1183 }
1184 None => debug!(pool = id, reason = "MissingState", "DeltaTransitionError"),
1185 }
1186 }
1187 }
1188 Ok(())
1189 }
1190}
1191
1192fn generate_proxy_token_address(idx: u32) -> Result<Address, StreamDecodeError> {
1194 let padded_idx = format!("{idx:x}");
1195 let padded_zeroes = "0".repeat(33 - padded_idx.len());
1196 let proxy_token_address = format!("{padded_zeroes}{padded_idx}BAdbaBe");
1197 let decoded = hex::decode(proxy_token_address).map_err(|e| {
1198 StreamDecodeError::Fatal(format!("Invalid proxy token address encoding: {e}"))
1199 })?;
1200
1201 const ADDRESS_LENGTH: usize = 20;
1202 if decoded.len() != ADDRESS_LENGTH {
1203 return Err(StreamDecodeError::Fatal(format!(
1204 "Invalid proxy token address length: expected {}, got {}",
1205 ADDRESS_LENGTH,
1206 decoded.len(),
1207 )));
1208 }
1209
1210 Ok(Address::from_slice(&decoded))
1211}
1212
1213fn create_proxy_token_account(
1218 addr: Address,
1219 new_address: Option<Address>,
1220 storage: &HashMap<U256, U256>,
1221 chain: Chain,
1222 balance: Option<U256>,
1223) -> AccountUpdate {
1224 let mut slots = storage.clone();
1225 if let Some(new_address) = new_address {
1226 slots.insert(*IMPLEMENTATION_SLOT, U256::from_be_slice(new_address.as_slice()));
1227 }
1228
1229 AccountUpdate {
1230 address: addr,
1231 chain,
1232 slots,
1233 balance,
1234 code: Some(ERC20_PROXY_BYTECODE.to_vec()),
1235 change: ChangeType::Creation,
1236 }
1237}
1238
1239#[cfg(test)]
1240mock! {
1241 #[derive(Debug)]
1242 pub ProtocolSim {
1243 pub fn fee(&self) -> f64;
1244 pub fn spot_price(&self, base: &Token, quote: &Token) -> Result<f64, SimulationError>;
1245 pub fn get_amount_out(
1246 &self,
1247 amount_in: BigUint,
1248 token_in: &Token,
1249 token_out: &Token,
1250 ) -> Result<GetAmountOutResult, SimulationError>;
1251 pub fn get_limits(
1252 &self,
1253 sell_token: Bytes,
1254 buy_token: Bytes,
1255 ) -> Result<(BigUint, BigUint), SimulationError>;
1256 pub fn delta_transition(
1257 &mut self,
1258 delta: ProtocolStateDelta,
1259 tokens: &HashMap<Bytes, Token>,
1260 balances: &Balances,
1261 ) -> Result<(), TransitionError>;
1262 pub fn clone_box(&self) -> Box<dyn ProtocolSim>;
1263 pub fn eq(&self, other: &dyn ProtocolSim) -> bool;
1264 }
1265}
1266
1267#[cfg(test)]
1268crate::impl_non_serializable_protocol!(MockProtocolSim, "test protocol");
1269
1270#[cfg(test)]
1271impl ProtocolSim for MockProtocolSim {
1272 fn fee(&self) -> f64 {
1273 self.fee()
1274 }
1275
1276 fn spot_price(&self, base: &Token, quote: &Token) -> Result<f64, SimulationError> {
1277 self.spot_price(base, quote)
1278 }
1279
1280 fn get_amount_out(
1281 &self,
1282 amount_in: BigUint,
1283 token_in: &Token,
1284 token_out: &Token,
1285 ) -> Result<GetAmountOutResult, SimulationError> {
1286 self.get_amount_out(amount_in, token_in, token_out)
1287 }
1288
1289 fn get_limits(
1290 &self,
1291 sell_token: Bytes,
1292 buy_token: Bytes,
1293 ) -> Result<(BigUint, BigUint), SimulationError> {
1294 self.get_limits(sell_token, buy_token)
1295 }
1296
1297 fn delta_transition(
1298 &mut self,
1299 delta: ProtocolStateDelta,
1300 tokens: &HashMap<Bytes, Token>,
1301 balances: &Balances,
1302 ) -> Result<(), TransitionError> {
1303 self.delta_transition(delta, tokens, balances)
1304 }
1305
1306 fn clone_box(&self) -> Box<dyn ProtocolSim> {
1307 self.clone_box()
1308 }
1309
1310 fn as_any(&self) -> &dyn Any {
1311 panic!("MockProtocolSim does not support as_any")
1312 }
1313
1314 fn as_any_mut(&mut self) -> &mut dyn Any {
1315 panic!("MockProtocolSim does not support as_any_mut")
1316 }
1317
1318 fn eq(&self, other: &dyn ProtocolSim) -> bool {
1319 self.eq(other)
1320 }
1321
1322 fn typetag_name(&self) -> &'static str {
1323 unreachable!()
1324 }
1325
1326 fn typetag_deserialize(&self) {
1327 unreachable!()
1328 }
1329}
1330
1331#[cfg(test)]
1332mod tests {
1333 use std::str::FromStr;
1334
1335 use alloy::primitives::address;
1336 use mockall::predicate::*;
1337 use rstest::*;
1338 use tycho_client::feed::BlockHeader;
1339 use tycho_common::{models::Chain, Bytes};
1340
1341 use super::*;
1342
1343 fn header_at(number: u64, timestamp: u64, partial: Option<u32>) -> BlockHeader {
1344 BlockHeader {
1345 hash: Bytes::from([0u8; 32]),
1346 number,
1347 parent_hash: Bytes::from([0u8; 32]),
1348 revert: false,
1349 timestamp,
1350 partial_block_index: partial,
1351 }
1352 }
1353
1354 fn block_sensitive_state() -> Box<dyn ProtocolSim> {
1356 use crate::evm::protocol::{
1357 aerodrome_slipstreams::state::AerodromeSlipstreamsState,
1358 utils::{
1359 slipstreams::{dynamic_fee_module::DynamicFeeConfig, observations::Observation},
1360 uniswap::{tick_list::TickInfo, tick_math::get_sqrt_ratio_at_tick},
1361 },
1362 };
1363
1364 Box::new(
1365 AerodromeSlipstreamsState::new(
1366 "block-sensitive".to_string(),
1367 0,
1368 1_000_000_000_000_000_000,
1369 get_sqrt_ratio_at_tick(0).unwrap(),
1370 0,
1371 1,
1372 3000,
1373 1,
1374 0,
1375 vec![TickInfo::new(-120, 0).unwrap(), TickInfo::new(120, 0).unwrap()],
1376 vec![Observation { block_timestamp: 500, initialized: true, ..Default::default() }],
1377 DynamicFeeConfig::new(2700, 30_000, 0, true, 750),
1378 )
1379 .expect("state should build")
1380 .with_position_assumption(crate::protocol::models::BlockPositionAssumption::First),
1383 )
1384 }
1385
1386 #[test]
1387 fn confirmed_header_targets_the_next_block() {
1388 let decoder = TychoStreamDecoder::<BlockHeader>::new(Chain::Base);
1389
1390 let execution_block = decoder.execution_block(&header_at(100, 1_000, None));
1391
1392 assert_eq!(execution_block.number(), 101);
1393 assert_eq!(execution_block.timestamp(), 1_000 + Chain::Base.block_time_secs());
1394 }
1395
1396 #[test]
1397 fn partial_header_targets_the_block_that_is_still_open() {
1398 let decoder = TychoStreamDecoder::<BlockHeader>::new(Chain::Ethereum);
1399
1400 let execution_block = decoder.execution_block(&header_at(100, 1_000, Some(3)));
1401
1402 assert_eq!(execution_block.number(), 100);
1403 assert_eq!(execution_block.timestamp(), 1_000);
1404 }
1405
1406 #[test]
1407 fn refresh_re_emits_a_state_whose_fee_flipped_without_a_delta() {
1408 let mut stored = HashMap::from([("block-sensitive".to_string(), {
1412 let mut state = block_sensitive_state();
1413 state.apply_block(&BlockContext::new(100, 500));
1414 state
1415 })]);
1416 let mut updated: HashMap<String, Box<dyn ProtocolSim>> = HashMap::new();
1417
1418 TychoStreamDecoder::<BlockHeader>::refresh_execution_block(
1419 &mut updated,
1420 &mut stored,
1421 &HashSet::new(),
1422 &HashMap::<String, ()>::new(),
1423 &BlockContext::new(101, 502),
1424 );
1425
1426 let emitted = updated
1427 .get("block-sensitive")
1428 .expect("a fee flip must be emitted even without a delta");
1429 assert_eq!(emitted.fee(), 750.0 / 1_000_000.0);
1430 assert_eq!(stored["block-sensitive"].fee(), 750.0 / 1_000_000.0);
1432 }
1433
1434 #[test]
1435 fn refresh_never_re_emits_failed_components() {
1436 let mut stored = HashMap::from([("zombie".to_string(), {
1439 let mut state = block_sensitive_state();
1440 state.apply_block(&BlockContext::new(100, 500));
1441 state
1442 })]);
1443 let failed = HashSet::from(["zombie".to_string()]);
1444 let mut updated: HashMap<String, Box<dyn ProtocolSim>> = HashMap::new();
1445
1446 TychoStreamDecoder::<BlockHeader>::refresh_execution_block(
1447 &mut updated,
1448 &mut stored,
1449 &failed,
1450 &HashMap::<String, ()>::new(),
1451 &BlockContext::new(101, 502),
1452 );
1453
1454 assert!(updated.is_empty());
1455 }
1456
1457 #[test]
1458 fn refresh_never_re_emits_removed_components() {
1459 let mut stored = HashMap::from([("gone".to_string(), {
1462 let mut state = block_sensitive_state();
1463 state.apply_block(&BlockContext::new(100, 500));
1464 state
1465 })]);
1466 let removed = HashMap::from([("gone".to_string(), ())]);
1467 let mut updated: HashMap<String, Box<dyn ProtocolSim>> = HashMap::new();
1468
1469 TychoStreamDecoder::<BlockHeader>::refresh_execution_block(
1470 &mut updated,
1471 &mut stored,
1472 &HashSet::new(),
1473 &removed,
1474 &BlockContext::new(101, 502),
1475 );
1476
1477 assert!(updated.is_empty());
1478 }
1479
1480 #[test]
1481 fn refresh_stays_quiet_when_no_fee_changed() {
1482 let mut stored: HashMap<String, Box<dyn ProtocolSim>> = HashMap::from([
1485 ("idle-sensitive".to_string(), {
1486 let mut state = block_sensitive_state();
1487 state.apply_block(&BlockContext::new(101, 502));
1488 state
1489 }),
1490 (
1491 "univ2".to_string(),
1492 Box::new(crate::evm::protocol::uniswap_v2::state::UniswapV2State::new(
1493 U256::from(1_000_000u64),
1494 U256::from(1_000_000u64),
1495 )) as Box<dyn ProtocolSim>,
1496 ),
1497 ]);
1498 let mut updated: HashMap<String, Box<dyn ProtocolSim>> = HashMap::new();
1499
1500 TychoStreamDecoder::<BlockHeader>::refresh_execution_block(
1501 &mut updated,
1502 &mut stored,
1503 &HashSet::new(),
1504 &HashMap::<String, ()>::new(),
1505 &BlockContext::new(102, 504),
1506 );
1507
1508 assert!(updated.is_empty());
1509 }
1510 use crate::evm::protocol::{curve::CurveState, uniswap_v2::state::UniswapV2State};
1511
1512 #[test]
1513 fn curve_vm_adapter_registration_flagged_deprecated() {
1514 assert!(!is_deprecated_curve_registration::<CurveState>("vm:curve"));
1516 assert!(is_deprecated_curve_registration::<UniswapV2State>("vm:curve"));
1518 assert!(!is_deprecated_curve_registration::<UniswapV2State>("uniswap_v2"));
1520 }
1521
1522 async fn setup_decoder(set_tokens: bool) -> TychoStreamDecoder<BlockHeader> {
1523 let mut decoder = TychoStreamDecoder::new(Chain::Ethereum);
1524 decoder.register_decoder::<UniswapV2State>("uniswap_v2");
1525 if set_tokens {
1526 let tokens = [
1527 Bytes::from("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").lpad(20, 0),
1528 Bytes::from("0xdac17f958d2ee523a2206206994597c13d831ec7").lpad(20, 0),
1529 ]
1530 .iter()
1531 .map(|addr| {
1532 let addr_str = format!("{addr:x}");
1533 (
1534 addr.clone(),
1535 Token::new(addr, &addr_str, 18, 100, &[Some(100_000)], Chain::Ethereum, 100),
1536 )
1537 })
1538 .collect();
1539 decoder.set_tokens(tokens).await;
1540 }
1541 decoder
1542 }
1543
1544 fn load_test_msg(name: &str) -> FeedMessage<BlockHeader> {
1545 use std::{fs, path::Path};
1546
1547 use tycho_client::feed::dto;
1548 let project_root = env!("CARGO_MANIFEST_DIR");
1549 let asset_path = Path::new(project_root).join(format!("tests/assets/decoder/{name}.json"));
1550 let json_data = fs::read_to_string(asset_path).expect("Failed to read test asset");
1551 let feed_msg: dto::FeedMessage<BlockHeader> =
1552 serde_json::from_str(&json_data).expect("Failed to deserialize FeedMsg json!");
1553 FeedMessage::from(feed_msg)
1554 }
1555
1556 #[tokio::test]
1557 async fn test_decode() {
1558 let decoder = setup_decoder(true).await;
1559
1560 let msg = load_test_msg("uniswap_v2_snapshot");
1561 let res1 = decoder
1562 .decode(&msg)
1563 .await
1564 .expect("decode failure");
1565 let msg = load_test_msg("uniswap_v2_delta");
1566 let res2 = decoder
1567 .decode(&msg)
1568 .await
1569 .expect("decode failure");
1570
1571 assert_eq!(res1.states.len(), 1);
1572 assert_eq!(res2.states.len(), 1);
1573 assert_eq!(res1.sync_states.len(), 1);
1574 assert_eq!(res2.sync_states.len(), 1);
1575 }
1576
1577 #[tokio::test]
1578 async fn test_decode_token_creation_delta_with_existing_proxy() {
1579 let decoder = setup_decoder(true).await;
1580 let msg = load_test_msg("uniswap_v2_delta_token_creation");
1581
1582 decoder
1585 .decode(&msg)
1586 .await
1587 .expect("first decode (proxy creation) failed");
1588
1589 decoder
1593 .decode(&msg)
1594 .await
1595 .expect("decode of a token Creation delta with an existing proxy failed");
1596 }
1597
1598 #[tokio::test]
1599 async fn test_decode_component_missing_token() {
1600 let decoder = setup_decoder(false).await;
1601 let tokens = [Bytes::from("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").lpad(20, 0)]
1602 .iter()
1603 .map(|addr| {
1604 let addr_str = format!("{addr:x}");
1605 (
1606 addr.clone(),
1607 Token::new(addr, &addr_str, 18, 100, &[Some(100_000)], Chain::Ethereum, 100),
1608 )
1609 })
1610 .collect();
1611 decoder.set_tokens(tokens).await;
1612
1613 let msg = load_test_msg("uniswap_v2_snapshot");
1614 let res1 = decoder
1615 .decode(&msg)
1616 .await
1617 .expect("decode failure");
1618
1619 assert_eq!(res1.states.len(), 0);
1620 }
1621
1622 #[tokio::test]
1623 async fn test_decode_component_bad_id() {
1624 let decoder = setup_decoder(true).await;
1625 let msg = load_test_msg("uniswap_v2_snapshot_broken_id");
1626
1627 match decoder.decode(&msg).await {
1628 Err(StreamDecodeError::Fatal(msg)) => {
1629 assert_eq!(msg, "Component id mismatch");
1630 }
1631 Ok(_) => {
1632 panic!("Expected failures to be raised")
1633 }
1634 }
1635 }
1636
1637 #[rstest]
1638 #[case(true)]
1639 #[case(false)]
1640 #[tokio::test]
1641 async fn test_decode_component_bad_state(#[case] skip_failures: bool) {
1642 let mut decoder = setup_decoder(true).await;
1643 decoder.skip_state_decode_failures = skip_failures;
1644
1645 let msg = load_test_msg("uniswap_v2_snapshot_broken_state");
1646 match decoder.decode(&msg).await {
1647 Err(StreamDecodeError::Fatal(msg)) => {
1648 if !skip_failures {
1649 assert_eq!(msg, "Missing attributes reserve0");
1650 } else {
1651 panic!("Expected failures to be ignored. Err: {msg}")
1652 }
1653 }
1654 Ok(res) => {
1655 if !skip_failures {
1656 panic!("Expected failures to be raised")
1657 } else {
1658 assert_eq!(res.states.len(), 0);
1659 }
1660 }
1661 }
1662 }
1663
1664 #[tokio::test]
1665 async fn test_decode_updates_state_on_contract_change() {
1666 let decoder = setup_decoder(true).await;
1667
1668 let mut mock_state = MockProtocolSim::new();
1670
1671 mock_state
1672 .expect_clone_box()
1673 .times(1)
1674 .returning(|| {
1675 let mut cloned_mock_state = MockProtocolSim::new();
1676 cloned_mock_state
1678 .expect_delta_transition()
1679 .times(1)
1680 .returning(|_, _, _| Ok(()));
1681 cloned_mock_state
1682 .expect_clone_box()
1683 .times(1)
1684 .returning(|| Box::new(MockProtocolSim::new()));
1685 Box::new(cloned_mock_state)
1686 });
1687
1688 let pool_id =
1690 "0x93d199263632a4ef4bb438f1feb99e57b4b5f0bd0000000000000000000005c2".to_string();
1691 decoder
1692 .state
1693 .write()
1694 .await
1695 .states
1696 .insert(pool_id.clone(), Box::new(mock_state) as Box<dyn ProtocolSim>);
1697 decoder
1698 .state
1699 .write()
1700 .await
1701 .contracts_map
1702 .insert(
1703 Bytes::from("0xba12222222228d8ba445958a75a0704d566bf2c8").lpad(20, 0),
1704 HashSet::from([pool_id.clone()]),
1705 );
1706
1707 let msg = load_test_msg("balancer_v2_delta");
1709
1710 let _ = decoder
1712 .decode(&msg)
1713 .await
1714 .expect("decode failure");
1715
1716 }
1718
1719 #[test]
1720 fn test_generate_proxy_token_address() {
1721 let idx = 1;
1722 let generated_address =
1723 generate_proxy_token_address(idx).expect("proxy token address should be valid");
1724 assert_eq!(generated_address, address!("000000000000000000000000000000001badbabe"));
1725
1726 let idx = 123456;
1727 let generated_address =
1728 generate_proxy_token_address(idx).expect("proxy token address should be valid");
1729 assert_eq!(generated_address, address!("00000000000000000000000000001e240badbabe"));
1730 }
1731
1732 #[tokio::test(flavor = "multi_thread")]
1733 async fn test_euler_hook_low_pool_manager_balance() {
1734 let mut decoder = TychoStreamDecoder::new(Chain::Ethereum);
1735
1736 decoder.register_decoder_with_context::<crate::evm::protocol::uniswap_v4::state::UniswapV4State>(
1737 "uniswap_v4_hooks", DecoderContext::new().vm_traces(true)
1738 );
1739
1740 let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap();
1741 let teth = Bytes::from_str("0xd11c452fc99cf405034ee446803b6f6c1f6d5ed8").unwrap();
1742 let tokens = HashMap::from([
1743 (
1744 weth.clone(),
1745 Token::new(&weth, "WETH", 18, 100, &[Some(100_000)], Chain::Ethereum, 100),
1746 ),
1747 (
1748 teth.clone(),
1749 Token::new(&teth, "tETH", 18, 100, &[Some(100_000)], Chain::Ethereum, 100),
1750 ),
1751 ]);
1752
1753 decoder.set_tokens(tokens.clone()).await;
1754
1755 let msg = load_test_msg("euler_hook_snapshot");
1756 let res = decoder
1757 .decode(&msg)
1758 .await
1759 .expect("decode failure");
1760
1761 let pool_state = res
1762 .states
1763 .get("0xc70d7fbd7fcccdf726e02fed78548b40dc52502b097c7a1ee7d995f4d4396134")
1764 .expect("Couldn't find target pool");
1765 let amount_out = pool_state
1766 .get_amount_out(
1767 BigUint::from_str("1000000000000000000").unwrap(),
1768 tokens.get(&teth).unwrap(),
1769 tokens.get(&weth).unwrap(),
1770 )
1771 .expect("Get amount out failed");
1772
1773 assert_eq!(amount_out.amount, BigUint::from_str("1216190190361759119").unwrap());
1774 }
1775
1776 fn component_with_id(id: &str) -> ComponentWithState {
1777 use tycho_common::models::protocol::{ProtocolComponent, ProtocolComponentState};
1778
1779 ComponentWithState {
1780 state: ProtocolComponentState::new(id, HashMap::new(), HashMap::new()),
1781 component: ProtocolComponent { id: id.to_string(), ..Default::default() },
1782 component_tvl: None,
1783 entrypoints: Vec::new(),
1784 }
1785 }
1786
1787 fn rejects_a(component: &ComponentWithState) -> bool {
1788 component.component.id != "a"
1789 }
1790
1791 fn rejects_b(component: &ComponentWithState) -> bool {
1792 component.component.id != "b"
1793 }
1794
1795 #[test]
1796 fn test_admits_requires_every_registered_filter() {
1797 let mut decoder = TychoStreamDecoder::<BlockHeader>::new(Chain::Ethereum);
1800 decoder.register_filter("x", rejects_a);
1801 decoder.register_filter("x", rejects_b);
1802
1803 assert!(!decoder.admits("x", &component_with_id("a")));
1804 assert!(!decoder.admits("x", &component_with_id("b")));
1805 assert!(decoder.admits("x", &component_with_id("c")));
1806 assert!(decoder.admits("y", &component_with_id("a")));
1807 }
1808}