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, 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, FilterFn>,
104 override_providers: HashMap<String, Arc<dyn StateOverrideProvider>>,
107}
108
109impl<H> Default for TychoStreamDecoder<H>
110where
111 H: HeaderLike + Clone + Sync + Send + 'static + std::fmt::Debug,
112{
113 fn default() -> Self {
114 Self::new()
115 }
116}
117
118fn is_deprecated_curve_registration<T: 'static>(exchange: &str) -> bool {
122 exchange == "vm:curve" &&
123 std::any::type_name::<T>() !=
124 std::any::type_name::<crate::evm::protocol::curve::CurveState>()
125}
126
127impl<H> TychoStreamDecoder<H>
128where
129 H: HeaderLike + Clone + Sync + Send + 'static + std::fmt::Debug,
130{
131 pub fn new() -> Self {
132 Self {
133 state: Arc::new(RwLock::new(DecoderState::default())),
134 skip_state_decode_failures: false,
135 min_token_quality: 100,
136 registry: HashMap::new(),
137 inclusion_filters: HashMap::new(),
138 override_providers: HashMap::new(),
139 }
140 }
141
142 pub fn set_override_provider(
148 &mut self,
149 protocol_system: String,
150 provider: Arc<dyn StateOverrideProvider>,
151 ) {
152 self.override_providers
153 .insert(protocol_system, provider);
154 }
155
156 pub async fn set_tokens(&self, tokens: HashMap<Bytes, Token>) {
161 let mut guard = self.state.write().await;
162 guard.tokens = tokens;
163 }
164
165 pub fn skip_state_decode_failures(&mut self, skip: bool) {
166 self.skip_state_decode_failures = skip;
167 }
168
169 pub fn min_token_quality(&mut self, quality: u32) {
175 self.min_token_quality = quality;
176 }
177
178 pub fn register_decoder_with_context<T>(&mut self, exchange: &str, context: DecoderContext)
191 where
192 T: ProtocolSim
193 + TryFromWithBlock<ComponentWithState, H, Error = InvalidSnapshotError>
194 + Send
195 + 'static,
196 {
197 if is_deprecated_curve_registration::<T>(exchange) {
198 warn!(
199 registered_type = std::any::type_name::<T>(),
200 "Registering \"vm:curve\" with the generic VM adapter is deprecated; register the \
201 native `CurveState` decoder instead (`exchange::<CurveState>(\"vm:curve\", ...)`). \
202 The VM-adapter path still works but will be removed in a future release."
203 );
204 }
205 let decoder = Box::new(
206 move |component: ComponentWithState,
207 header: H,
208 account_balances: AccountBalances,
209 state: Arc<RwLock<DecoderState>>,
210 live_override: Option<watch::Receiver<OverrideSnapshot>>| {
211 let mut context = context.clone();
212 context.live_override = live_override;
213 Box::pin(async move {
214 let guard = state.read().await;
215 T::try_from_with_header(
216 component,
217 header,
218 &account_balances,
219 &guard.tokens,
220 &context,
221 )
222 .await
223 .map(|c| Box::new(c) as Box<dyn ProtocolSim>)
224 }) as DecodeFut
225 },
226 );
227 self.registry
228 .insert(exchange.to_string(), decoder);
229 }
230
231 pub fn register_decoder<T>(&mut self, exchange: &str)
243 where
244 T: ProtocolSim
245 + TryFromWithBlock<ComponentWithState, H, Error = InvalidSnapshotError>
246 + Send
247 + 'static,
248 {
249 let context = DecoderContext::new();
250 self.register_decoder_with_context::<T>(exchange, context);
251 }
252
253 pub fn register_filter(&mut self, exchange: &str, predicate: FilterFn) {
269 self.inclusion_filters
270 .insert(exchange.to_string(), predicate);
271 }
272
273 pub async fn decode(&self, msg: &FeedMessage<H>) -> Result<Update, StreamDecodeError> {
276 let mut updated_states = HashMap::new();
278 let mut new_pairs = HashMap::new();
279 let mut removed_pairs = HashMap::new();
280 let mut contracts_map = HashMap::new();
281 let mut msg_failed_components = HashSet::new();
282
283 let header = msg
284 .state_msgs
285 .values()
286 .next()
287 .ok_or_else(|| StreamDecodeError::Fatal("Missing block!".into()))?
288 .header
289 .clone();
290
291 let block_number_or_timestamp = header
292 .clone()
293 .block_number_or_timestamp();
294 let current_block = header.clone().block();
295 let is_partial = current_block
296 .as_ref()
297 .map(|h| h.partial_block_index.is_some())
298 .unwrap_or(false);
299
300 for (protocol, protocol_msg) in msg.state_msgs.iter() {
301 if let Some(deltas) = protocol_msg.deltas.as_ref() {
303 let mut state_guard = self.state.write().await;
304
305 let new_tokens = deltas
306 .new_tokens
307 .iter()
308 .filter(|(addr, t)| {
309 t.quality >= self.min_token_quality &&
310 !state_guard.tokens.contains_key(*addr)
311 })
312 .map(|(addr, t)| (addr.clone(), t.clone()))
313 .collect::<HashMap<Bytes, Token>>();
314
315 if !new_tokens.is_empty() {
316 debug!(n = new_tokens.len(), "NewTokens");
317 state_guard.tokens.extend(new_tokens);
318 }
319 }
320
321 {
323 let mut state_guard = self.state.write().await;
324 let removed_components: Vec<(String, ProtocolComponent)> = protocol_msg
325 .removed_components
326 .iter()
327 .map(|(id, comp)| {
328 if *id != comp.id {
329 error!(
330 "Component id mismatch in removed components {id} != {}",
331 comp.id
332 );
333 return Err(StreamDecodeError::Fatal("Component id mismatch".into()));
334 }
335
336 let tokens = comp
337 .tokens
338 .iter()
339 .flat_map(|addr| state_guard.tokens.get(addr).cloned())
340 .collect::<Vec<_>>();
341
342 if tokens.len() == comp.tokens.len() {
343 Ok(Some((
344 id.clone(),
345 ProtocolComponent::from_with_tokens(comp.clone(), tokens),
346 )))
347 } else {
348 Ok(None)
349 }
350 })
351 .collect::<Result<Vec<Option<(String, ProtocolComponent)>>, StreamDecodeError>>(
352 )?
353 .into_iter()
354 .flatten()
355 .collect();
356
357 for (id, component) in removed_components {
359 state_guard.components.remove(&id);
360 state_guard.states.remove(&id);
361 removed_pairs.insert(id, component);
362 }
363
364 info!(
366 "Processing {} contracts from snapshots",
367 protocol_msg
368 .snapshots
369 .get_vm_storage()
370 .len()
371 );
372
373 let mut proxy_token_accounts: HashMap<Address, AccountUpdate> = HashMap::new();
374 let mut storage_by_address: HashMap<Address, ResponseAccount> = HashMap::new();
375 for (key, value) in protocol_msg
376 .snapshots
377 .get_vm_storage()
378 .iter()
379 {
380 let account: ResponseAccount = value.clone().into();
381
382 if state_guard.tokens.contains_key(key) {
383 let original_address = account.address;
384 let (impl_addr, proxy_state) = match state_guard
393 .proxy_token_addresses
394 .get(&original_address)
395 {
396 Some(impl_addr) => {
397 let proxy_state = AccountUpdate::new(
404 original_address,
405 value.chain,
406 account.slots.clone(),
407 Some(account.native_balance),
408 None,
409 ChangeType::Update,
410 );
411 (*impl_addr, proxy_state)
412 }
413 None => {
414 let impl_addr = generate_proxy_token_address(
418 state_guard.proxy_token_addresses.len() as u32,
419 )?;
420 state_guard
421 .proxy_token_addresses
422 .insert(original_address, impl_addr);
423
424 let proxy_state = create_proxy_token_account(
426 original_address,
427 Some(impl_addr),
428 &account.slots,
429 value.chain,
430 Some(account.native_balance),
431 );
432
433 (impl_addr, proxy_state)
434 }
435 };
436
437 proxy_token_accounts.insert(original_address, proxy_state);
438
439 let impl_update = ResponseAccount {
441 address: impl_addr,
442 slots: HashMap::new(),
443 ..account.clone()
444 };
445 storage_by_address.insert(impl_addr, impl_update);
446 } else {
447 storage_by_address.insert(account.address, account);
449 }
450 }
451
452 let mut proxy_creates: Vec<AccountUpdate> = Vec::new();
456 let mut proxy_updates: HashMap<Address, AccountUpdate> = HashMap::new();
457 for (addr, update) in proxy_token_accounts {
458 if matches!(update.change, ChangeType::Creation) {
459 proxy_creates.push(update);
460 } else {
461 proxy_updates.insert(addr, update);
462 }
463 }
464
465 info!("Updating engine with {} contracts from snapshots", storage_by_address.len());
466 update_engine(
467 SHARED_TYCHO_DB.clone(),
468 header.clone().block(),
469 Some(storage_by_address),
470 proxy_updates,
471 )
472 .map_err(|e| StreamDecodeError::Fatal(e.to_string()))?;
473
474 if !proxy_creates.is_empty() {
478 SHARED_TYCHO_DB
479 .force_update_accounts(proxy_creates)
480 .map_err(|e| StreamDecodeError::Fatal(e.to_string()))?;
481 }
482 info!("Engine updated");
483 drop(state_guard);
484 }
485
486 let account_balances = protocol_msg
489 .clone()
490 .snapshots
491 .get_vm_storage()
492 .iter()
493 .filter_map(|(addr, acc)| {
494 if acc.token_balances.is_empty() {
495 return None;
496 }
497 let balances = acc
498 .token_balances
499 .iter()
500 .map(|(token_addr, ab)| (token_addr.clone(), ab.balance.clone()))
501 .collect::<HashMap<Bytes, Bytes>>();
502 Some((addr.clone(), balances))
503 })
504 .collect::<AccountBalances>();
505
506 let mut new_components = HashMap::new();
507 let mut count_token_skips = 0;
508 let mut components_to_store = HashMap::new();
509 {
510 let state_guard = self.state.read().await;
511
512 'snapshot_loop: for (id, snapshot) in protocol_msg
514 .snapshots
515 .get_states()
516 .clone()
517 {
518 if self
520 .inclusion_filters
521 .get(protocol.as_str())
522 .is_some_and(|predicate| !predicate(&snapshot))
523 {
524 continue;
525 }
526
527 let mut component_tokens = Vec::new();
529 let mut new_tokens_accounts = HashMap::new();
530 for token in snapshot.component.tokens.clone() {
531 match state_guard.tokens.get(&token) {
532 Some(token) => {
533 component_tokens.push(token.clone());
534
535 let token_address = match bytes_to_address(&token.address) {
538 Ok(addr) => addr,
539 Err(_) => {
540 count_token_skips += 1;
541 msg_failed_components.insert(id.clone());
542 warn!(
543 "Token address could not be decoded {}, ignoring pool {:x?}",
544 token.address, id
545 );
546 continue 'snapshot_loop;
547 }
548 };
549 if !state_guard
551 .proxy_token_addresses
552 .contains_key(&token_address)
553 {
554 new_tokens_accounts.insert(
555 token_address,
556 create_proxy_token_account(
557 token_address,
558 None,
559 &HashMap::new(),
560 snapshot.component.chain,
561 None,
562 ),
563 );
564 }
565 }
566 None => {
567 count_token_skips += 1;
568 msg_failed_components.insert(id.clone());
569 debug!("Token not found {}, ignoring pool {:x?}", token, id);
570 continue 'snapshot_loop;
571 }
572 }
573 }
574 let component = ProtocolComponent::from_with_tokens(
575 snapshot.component.clone(),
576 component_tokens,
577 );
578
579 if !new_tokens_accounts.is_empty() {
581 update_engine(
582 SHARED_TYCHO_DB.clone(),
583 header.clone().block(),
584 None,
585 new_tokens_accounts,
586 )
587 .map_err(|e| StreamDecodeError::Fatal(e.to_string()))?;
588 }
589
590 if !component
593 .static_attributes
594 .contains_key("manual_updates")
595 {
596 for contract in &component.contract_ids {
597 contracts_map
598 .entry(contract.clone())
599 .or_insert_with(HashSet::new)
600 .insert(id.clone());
601 }
602 for (_, tracing) in snapshot.entrypoints.iter() {
605 for contract in tracing.accessed_slots.keys().cloned() {
606 contracts_map
607 .entry(contract)
608 .or_insert_with(HashSet::new)
609 .insert(id.clone());
610 }
611 }
612 }
613
614 new_pairs.insert(id.clone(), component.clone());
616
617 components_to_store.insert(id.clone(), component);
619
620 if let Some(state_decode_f) = self.registry.get(protocol.as_str()) {
622 let live_override = self
623 .override_providers
624 .get(protocol.as_str())
625 .and_then(|provider| provider.subscribe(protocol.as_str()));
626 match state_decode_f(
627 snapshot,
628 header.clone(),
629 account_balances.clone(),
630 self.state.clone(),
631 live_override,
632 )
633 .await
634 {
635 Ok(state) => {
636 new_components.insert(id.clone(), state);
637 }
638 Err(e) => {
639 if self.skip_state_decode_failures {
640 warn!(pool = id, error = %e, "StateDecodingFailure");
641 msg_failed_components.insert(id.clone());
642 continue 'snapshot_loop;
643 } else {
644 error!(pool = id, error = %e, "StateDecodingFailure");
645 return Err(StreamDecodeError::Fatal(format!("{e}")));
646 }
647 }
648 }
649 } else if self.skip_state_decode_failures {
650 warn!(pool = id, "MissingDecoderRegistration");
651 msg_failed_components.insert(id.clone());
652 continue 'snapshot_loop;
653 } else {
654 error!(pool = id, "MissingDecoderRegistration");
655 return Err(StreamDecodeError::Fatal(format!(
656 "Missing decoder registration for: {id}"
657 )));
658 }
659 }
660 }
661
662 if !components_to_store.is_empty() {
664 let mut state_guard = self.state.write().await;
665 for (id, component) in components_to_store {
666 state_guard
667 .components
668 .insert(id, component);
669 }
670 }
671
672 if !protocol_msg.snapshots.states.is_empty() {
673 info!("Decoded {} snapshots for protocol {protocol}", new_components.len());
674 }
675 if count_token_skips > 0 {
676 info!("Skipped {count_token_skips} pools due to missing tokens");
677 }
678
679 updated_states.extend(new_components);
681
682 if let Some(deltas) = protocol_msg.deltas.clone() {
684 let mut state_guard = self.state.write().await;
686
687 let mut account_update_by_address: HashMap<Address, AccountUpdate> = HashMap::new();
688 let mut new_proxy_accounts: Vec<AccountUpdate> = Vec::new();
690 for (key, value) in deltas.account_deltas.iter() {
691 let mut update: AccountUpdate = value.clone().into();
692
693 if update.code.is_none() && matches!(update.change, ChangeType::Creation) {
699 error!(
700 update = ?update,
701 "FaultyCreationDelta"
702 );
703 update.code = Some(vec![]);
704 }
705
706 if state_guard.tokens.contains_key(key) {
707 let original_address = update.address;
708 let impl_addr = match state_guard
715 .proxy_token_addresses
716 .get(&original_address)
717 {
718 Some(impl_addr) => {
719 let proxy_update = AccountUpdate { code: None, ..update.clone() };
723 account_update_by_address.insert(original_address, proxy_update);
724
725 *impl_addr
726 }
727 None => {
728 let impl_addr = generate_proxy_token_address(
733 state_guard.proxy_token_addresses.len() as u32,
734 )?;
735 state_guard
736 .proxy_token_addresses
737 .insert(original_address, impl_addr);
738
739 let proxy_state = create_proxy_token_account(
744 original_address,
745 Some(impl_addr),
746 &update.slots,
747 update.chain,
748 update.balance,
749 );
750 new_proxy_accounts.push(proxy_state);
751
752 impl_addr
753 }
754 };
755
756 if update.code.is_some() {
758 let impl_update = AccountUpdate {
759 address: impl_addr,
760 slots: HashMap::new(),
761 ..update.clone()
762 };
763 account_update_by_address.insert(impl_addr, impl_update);
764 }
765 } else {
766 account_update_by_address.insert(update.address, update);
768 }
769 }
770 drop(state_guard);
771
772 let state_guard = self.state.read().await;
773 info!("Updating engine with {} contract deltas", deltas.account_deltas.len());
774 update_engine(
775 SHARED_TYCHO_DB.clone(),
776 header.clone().block(),
777 None,
778 account_update_by_address,
779 )
780 .map_err(|e| StreamDecodeError::Fatal(e.to_string()))?;
781
782 if !new_proxy_accounts.is_empty() {
785 SHARED_TYCHO_DB
786 .force_update_accounts(new_proxy_accounts)
787 .map_err(|e| StreamDecodeError::Fatal(e.to_string()))?;
788 }
789 info!("Engine updated");
790
791 let mut pools_to_update = HashSet::new();
793 for (account, _update) in deltas.account_deltas {
794 pools_to_update.extend(
796 contracts_map
797 .get(&account)
798 .cloned()
799 .unwrap_or_default(),
800 );
801 pools_to_update.extend(
803 state_guard
804 .contracts_map
805 .get(&account)
806 .cloned()
807 .unwrap_or_default(),
808 );
809 }
810
811 let all_balances = Balances {
813 component_balances: deltas
814 .component_balances
815 .iter()
816 .map(|(pool_id, bals)| {
817 let mut balances = HashMap::new();
818 for (t, b) in bals {
819 balances.insert(t.clone(), b.balance.clone());
820 }
821 pools_to_update.insert(pool_id.clone());
822 (pool_id.clone(), balances)
823 })
824 .collect(),
825 account_balances: deltas
826 .account_balances
827 .iter()
828 .map(|(account, bals)| {
829 let mut balances = HashMap::new();
830 for (t, b) in bals {
831 balances.insert(t.clone(), b.balance.clone());
832 }
833 pools_to_update.extend(
834 contracts_map
835 .get(account)
836 .cloned()
837 .unwrap_or_default(),
838 );
839 (account.clone(), balances)
840 })
841 .collect(),
842 };
843
844 for (id, update) in deltas.state_deltas {
846 let update_with_block = Self::add_block_info_to_delta(
848 ProtocolStateDelta::from(update),
849 current_block.clone(),
850 );
851 match Self::apply_update(
852 &id,
853 update_with_block,
854 &mut updated_states,
855 &state_guard,
856 &all_balances,
857 ) {
858 Ok(_) => {
859 pools_to_update.remove(&id);
860 }
861 Err(e) => {
862 if self.skip_state_decode_failures {
863 warn!(pool = id, error = %e, "Failed to apply state update, marking component as removed");
864 updated_states.remove(&id);
866 if let Some(component) = new_pairs.remove(&id) {
868 removed_pairs.insert(id.clone(), component);
869 } else if let Some(component) = state_guard.components.get(&id) {
870 removed_pairs.insert(id.clone(), component.clone());
871 } else {
872 warn!(pool = id, "Component not found in new_pairs or state, cannot add to removed_pairs");
875 }
876 pools_to_update.remove(&id);
877
878 msg_failed_components.insert(id.clone());
880 } else {
881 return Err(e);
882 }
883 }
884 }
885 }
886
887 for pool in pools_to_update {
889 let default_delta_with_block = Self::add_block_info_to_delta(
891 ProtocolStateDelta::default(),
892 current_block.clone(),
893 );
894 match Self::apply_update(
895 &pool,
896 default_delta_with_block,
897 &mut updated_states,
898 &state_guard,
899 &all_balances,
900 ) {
901 Ok(_) => {}
902 Err(e) => {
903 if self.skip_state_decode_failures {
904 warn!(pool = pool, error = %e, "Failed to apply contract/balance update, marking component as removed");
905 updated_states.remove(&pool);
907 if let Some(component) = new_pairs.remove(&pool) {
909 removed_pairs.insert(pool.clone(), component);
910 } else if let Some(component) = state_guard.components.get(&pool) {
911 removed_pairs.insert(pool.clone(), component.clone());
912 } else {
913 warn!(pool = pool, "Component not found in new_pairs or state, cannot add to removed_pairs");
916 }
917
918 msg_failed_components.insert(pool.clone());
920 } else {
921 return Err(e);
922 }
923 }
924 }
925 }
926 };
927 }
928
929 let mut state_guard = self.state.write().await;
931
932 state_guard
934 .failed_components
935 .extend(msg_failed_components);
936
937 updated_states.retain(|id, _| {
941 !state_guard
942 .failed_components
943 .contains(id)
944 });
945 new_pairs.retain(|id, _| {
946 !state_guard
947 .failed_components
948 .contains(id)
949 });
950
951 state_guard
952 .states
953 .extend(updated_states.clone());
954
955 state_guard.current_block_number = block_number_or_timestamp;
956
957 for (id, component) in new_pairs.iter() {
959 state_guard
960 .components
961 .insert(id.clone(), component.clone());
962 }
963
964 for id in removed_pairs.keys() {
966 state_guard.components.remove(id);
967 }
968
969 for (key, values) in contracts_map {
970 state_guard
971 .contracts_map
972 .entry(key)
973 .or_insert_with(HashSet::new)
974 .extend(values);
975 }
976
977 Ok(Update::new(block_number_or_timestamp, updated_states, new_pairs)
979 .set_is_partial(is_partial)
980 .set_removed_pairs(removed_pairs)
981 .set_sync_states(msg.sync_states.clone()))
982 }
983
984 pub async fn apply_deltas_ephemeral(
1003 &self,
1004 pending_deltas: &HashMap<String, BlockAggregatedChanges>,
1005 header: H,
1006 ) -> Result<Update, StreamDecodeError> {
1007 let block_number_or_timestamp = header
1008 .clone()
1009 .block_number_or_timestamp();
1010 let current_block = header.block();
1011 let state_guard = self.state.read().await;
1012
1013 let mut updated_states: HashMap<String, Box<dyn ProtocolSim>> = HashMap::new();
1014
1015 for deltas in pending_deltas.values() {
1016 let all_balances = Balances {
1017 component_balances: deltas
1018 .component_balances
1019 .iter()
1020 .map(|(pool_id, bals)| {
1021 let balances = bals
1022 .iter()
1023 .map(|(t, b)| (t.clone(), b.balance.clone()))
1024 .collect();
1025 (pool_id.clone(), balances)
1026 })
1027 .collect(),
1028 account_balances: HashMap::new(),
1029 };
1030
1031 for (id, state_delta) in &deltas.state_deltas {
1032 let dto_delta = Self::add_block_info_to_delta(
1033 ProtocolStateDelta::from(state_delta.clone()),
1034 current_block.clone(),
1035 );
1036 if let Err(e) = Self::apply_update(
1037 id,
1038 dto_delta,
1039 &mut updated_states,
1040 &state_guard,
1041 &all_balances,
1042 ) {
1043 warn!(pool = id, error = %e, "EphemeralDeltaTransitionError");
1044 }
1045 }
1046 }
1047
1048 Ok(Update::new(block_number_or_timestamp, updated_states, HashMap::new()))
1049 }
1050
1051 fn add_block_info_to_delta(
1053 mut delta: ProtocolStateDelta,
1054 block_header_opt: Option<BlockHeader>,
1055 ) -> ProtocolStateDelta {
1056 if let Some(header) = block_header_opt {
1057 delta.updated_attributes.insert(
1060 "block_number".to_string(),
1061 Bytes::from(header.number.to_be_bytes().to_vec()),
1062 );
1063 delta.updated_attributes.insert(
1064 "block_timestamp".to_string(),
1065 Bytes::from(header.timestamp.to_be_bytes().to_vec()),
1066 );
1067 }
1068 delta
1069 }
1070
1071 fn apply_update(
1072 id: &String,
1073 update: ProtocolStateDelta,
1074 updated_states: &mut HashMap<String, Box<dyn ProtocolSim>>,
1075 state_guard: &RwLockReadGuard<'_, DecoderState>,
1076 all_balances: &Balances,
1077 ) -> Result<(), StreamDecodeError> {
1078 match updated_states.entry(id.clone()) {
1079 Entry::Occupied(mut entry) => {
1080 let state: &mut Box<dyn ProtocolSim> = entry.get_mut();
1082 state
1083 .delta_transition(update, &state_guard.tokens, all_balances)
1084 .map_err(|e| {
1085 error!(pool = id, error = ?e, "DeltaTransitionError");
1086 StreamDecodeError::Fatal(format!("TransitionFailure: {e:?}"))
1087 })?;
1088 }
1089 Entry::Vacant(_) => {
1090 match state_guard.states.get(id) {
1091 Some(stored_state) => {
1094 let mut state = stored_state.clone();
1095 state
1096 .delta_transition(update, &state_guard.tokens, all_balances)
1097 .map_err(|e| {
1098 error!(pool = id, error = ?e, "DeltaTransitionError");
1099 StreamDecodeError::Fatal(format!("TransitionFailure: {e:?}"))
1100 })?;
1101 updated_states.insert(id.clone(), state);
1102 }
1103 None => debug!(pool = id, reason = "MissingState", "DeltaTransitionError"),
1104 }
1105 }
1106 }
1107 Ok(())
1108 }
1109}
1110
1111fn generate_proxy_token_address(idx: u32) -> Result<Address, StreamDecodeError> {
1113 let padded_idx = format!("{idx:x}");
1114 let padded_zeroes = "0".repeat(33 - padded_idx.len());
1115 let proxy_token_address = format!("{padded_zeroes}{padded_idx}BAdbaBe");
1116 let decoded = hex::decode(proxy_token_address).map_err(|e| {
1117 StreamDecodeError::Fatal(format!("Invalid proxy token address encoding: {e}"))
1118 })?;
1119
1120 const ADDRESS_LENGTH: usize = 20;
1121 if decoded.len() != ADDRESS_LENGTH {
1122 return Err(StreamDecodeError::Fatal(format!(
1123 "Invalid proxy token address length: expected {}, got {}",
1124 ADDRESS_LENGTH,
1125 decoded.len(),
1126 )));
1127 }
1128
1129 Ok(Address::from_slice(&decoded))
1130}
1131
1132fn create_proxy_token_account(
1137 addr: Address,
1138 new_address: Option<Address>,
1139 storage: &HashMap<U256, U256>,
1140 chain: Chain,
1141 balance: Option<U256>,
1142) -> AccountUpdate {
1143 let mut slots = storage.clone();
1144 if let Some(new_address) = new_address {
1145 slots.insert(*IMPLEMENTATION_SLOT, U256::from_be_slice(new_address.as_slice()));
1146 }
1147
1148 AccountUpdate {
1149 address: addr,
1150 chain,
1151 slots,
1152 balance,
1153 code: Some(ERC20_PROXY_BYTECODE.to_vec()),
1154 change: ChangeType::Creation,
1155 }
1156}
1157
1158#[cfg(test)]
1159mock! {
1160 #[derive(Debug)]
1161 pub ProtocolSim {
1162 pub fn fee(&self) -> f64;
1163 pub fn spot_price(&self, base: &Token, quote: &Token) -> Result<f64, SimulationError>;
1164 pub fn get_amount_out(
1165 &self,
1166 amount_in: BigUint,
1167 token_in: &Token,
1168 token_out: &Token,
1169 ) -> Result<GetAmountOutResult, SimulationError>;
1170 pub fn get_limits(
1171 &self,
1172 sell_token: Bytes,
1173 buy_token: Bytes,
1174 ) -> Result<(BigUint, BigUint), SimulationError>;
1175 pub fn delta_transition(
1176 &mut self,
1177 delta: ProtocolStateDelta,
1178 tokens: &HashMap<Bytes, Token>,
1179 balances: &Balances,
1180 ) -> Result<(), TransitionError>;
1181 pub fn clone_box(&self) -> Box<dyn ProtocolSim>;
1182 pub fn eq(&self, other: &dyn ProtocolSim) -> bool;
1183 }
1184}
1185
1186#[cfg(test)]
1187crate::impl_non_serializable_protocol!(MockProtocolSim, "test protocol");
1188
1189#[cfg(test)]
1190impl ProtocolSim for MockProtocolSim {
1191 fn fee(&self) -> f64 {
1192 self.fee()
1193 }
1194
1195 fn spot_price(&self, base: &Token, quote: &Token) -> Result<f64, SimulationError> {
1196 self.spot_price(base, quote)
1197 }
1198
1199 fn get_amount_out(
1200 &self,
1201 amount_in: BigUint,
1202 token_in: &Token,
1203 token_out: &Token,
1204 ) -> Result<GetAmountOutResult, SimulationError> {
1205 self.get_amount_out(amount_in, token_in, token_out)
1206 }
1207
1208 fn get_limits(
1209 &self,
1210 sell_token: Bytes,
1211 buy_token: Bytes,
1212 ) -> Result<(BigUint, BigUint), SimulationError> {
1213 self.get_limits(sell_token, buy_token)
1214 }
1215
1216 fn delta_transition(
1217 &mut self,
1218 delta: ProtocolStateDelta,
1219 tokens: &HashMap<Bytes, Token>,
1220 balances: &Balances,
1221 ) -> Result<(), TransitionError> {
1222 self.delta_transition(delta, tokens, balances)
1223 }
1224
1225 fn clone_box(&self) -> Box<dyn ProtocolSim> {
1226 self.clone_box()
1227 }
1228
1229 fn as_any(&self) -> &dyn Any {
1230 panic!("MockProtocolSim does not support as_any")
1231 }
1232
1233 fn as_any_mut(&mut self) -> &mut dyn Any {
1234 panic!("MockProtocolSim does not support as_any_mut")
1235 }
1236
1237 fn eq(&self, other: &dyn ProtocolSim) -> bool {
1238 self.eq(other)
1239 }
1240
1241 fn typetag_name(&self) -> &'static str {
1242 unreachable!()
1243 }
1244
1245 fn typetag_deserialize(&self) {
1246 unreachable!()
1247 }
1248}
1249
1250#[cfg(test)]
1251mod tests {
1252 use std::str::FromStr;
1253
1254 use alloy::primitives::address;
1255 use mockall::predicate::*;
1256 use rstest::*;
1257 use tycho_client::feed::BlockHeader;
1258 use tycho_common::{models::Chain, Bytes};
1259
1260 use super::*;
1261 use crate::evm::protocol::{curve::CurveState, uniswap_v2::state::UniswapV2State};
1262
1263 #[test]
1264 fn curve_vm_adapter_registration_flagged_deprecated() {
1265 assert!(!is_deprecated_curve_registration::<CurveState>("vm:curve"));
1267 assert!(is_deprecated_curve_registration::<UniswapV2State>("vm:curve"));
1269 assert!(!is_deprecated_curve_registration::<UniswapV2State>("uniswap_v2"));
1271 }
1272
1273 async fn setup_decoder(set_tokens: bool) -> TychoStreamDecoder<BlockHeader> {
1274 let mut decoder = TychoStreamDecoder::new();
1275 decoder.register_decoder::<UniswapV2State>("uniswap_v2");
1276 if set_tokens {
1277 let tokens = [
1278 Bytes::from("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").lpad(20, 0),
1279 Bytes::from("0xdac17f958d2ee523a2206206994597c13d831ec7").lpad(20, 0),
1280 ]
1281 .iter()
1282 .map(|addr| {
1283 let addr_str = format!("{addr:x}");
1284 (
1285 addr.clone(),
1286 Token::new(addr, &addr_str, 18, 100, &[Some(100_000)], Chain::Ethereum, 100),
1287 )
1288 })
1289 .collect();
1290 decoder.set_tokens(tokens).await;
1291 }
1292 decoder
1293 }
1294
1295 fn load_test_msg(name: &str) -> FeedMessage<BlockHeader> {
1296 use std::{fs, path::Path};
1297
1298 use tycho_client::feed::dto;
1299 let project_root = env!("CARGO_MANIFEST_DIR");
1300 let asset_path = Path::new(project_root).join(format!("tests/assets/decoder/{name}.json"));
1301 let json_data = fs::read_to_string(asset_path).expect("Failed to read test asset");
1302 let feed_msg: dto::FeedMessage<BlockHeader> =
1303 serde_json::from_str(&json_data).expect("Failed to deserialize FeedMsg json!");
1304 FeedMessage::from(feed_msg)
1305 }
1306
1307 #[tokio::test]
1308 async fn test_decode() {
1309 let decoder = setup_decoder(true).await;
1310
1311 let msg = load_test_msg("uniswap_v2_snapshot");
1312 let res1 = decoder
1313 .decode(&msg)
1314 .await
1315 .expect("decode failure");
1316 let msg = load_test_msg("uniswap_v2_delta");
1317 let res2 = decoder
1318 .decode(&msg)
1319 .await
1320 .expect("decode failure");
1321
1322 assert_eq!(res1.states.len(), 1);
1323 assert_eq!(res2.states.len(), 1);
1324 assert_eq!(res1.sync_states.len(), 1);
1325 assert_eq!(res2.sync_states.len(), 1);
1326 }
1327
1328 #[tokio::test]
1329 async fn test_decode_component_missing_token() {
1330 let decoder = setup_decoder(false).await;
1331 let tokens = [Bytes::from("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").lpad(20, 0)]
1332 .iter()
1333 .map(|addr| {
1334 let addr_str = format!("{addr:x}");
1335 (
1336 addr.clone(),
1337 Token::new(addr, &addr_str, 18, 100, &[Some(100_000)], Chain::Ethereum, 100),
1338 )
1339 })
1340 .collect();
1341 decoder.set_tokens(tokens).await;
1342
1343 let msg = load_test_msg("uniswap_v2_snapshot");
1344 let res1 = decoder
1345 .decode(&msg)
1346 .await
1347 .expect("decode failure");
1348
1349 assert_eq!(res1.states.len(), 0);
1350 }
1351
1352 #[tokio::test]
1353 async fn test_decode_component_bad_id() {
1354 let decoder = setup_decoder(true).await;
1355 let msg = load_test_msg("uniswap_v2_snapshot_broken_id");
1356
1357 match decoder.decode(&msg).await {
1358 Err(StreamDecodeError::Fatal(msg)) => {
1359 assert_eq!(msg, "Component id mismatch");
1360 }
1361 Ok(_) => {
1362 panic!("Expected failures to be raised")
1363 }
1364 }
1365 }
1366
1367 #[rstest]
1368 #[case(true)]
1369 #[case(false)]
1370 #[tokio::test]
1371 async fn test_decode_component_bad_state(#[case] skip_failures: bool) {
1372 let mut decoder = setup_decoder(true).await;
1373 decoder.skip_state_decode_failures = skip_failures;
1374
1375 let msg = load_test_msg("uniswap_v2_snapshot_broken_state");
1376 match decoder.decode(&msg).await {
1377 Err(StreamDecodeError::Fatal(msg)) => {
1378 if !skip_failures {
1379 assert_eq!(msg, "Missing attributes reserve0");
1380 } else {
1381 panic!("Expected failures to be ignored. Err: {msg}")
1382 }
1383 }
1384 Ok(res) => {
1385 if !skip_failures {
1386 panic!("Expected failures to be raised")
1387 } else {
1388 assert_eq!(res.states.len(), 0);
1389 }
1390 }
1391 }
1392 }
1393
1394 #[tokio::test]
1395 async fn test_decode_updates_state_on_contract_change() {
1396 let decoder = setup_decoder(true).await;
1397
1398 let mut mock_state = MockProtocolSim::new();
1400
1401 mock_state
1402 .expect_clone_box()
1403 .times(1)
1404 .returning(|| {
1405 let mut cloned_mock_state = MockProtocolSim::new();
1406 cloned_mock_state
1408 .expect_delta_transition()
1409 .times(1)
1410 .returning(|_, _, _| Ok(()));
1411 cloned_mock_state
1412 .expect_clone_box()
1413 .times(1)
1414 .returning(|| Box::new(MockProtocolSim::new()));
1415 Box::new(cloned_mock_state)
1416 });
1417
1418 let pool_id =
1420 "0x93d199263632a4ef4bb438f1feb99e57b4b5f0bd0000000000000000000005c2".to_string();
1421 decoder
1422 .state
1423 .write()
1424 .await
1425 .states
1426 .insert(pool_id.clone(), Box::new(mock_state) as Box<dyn ProtocolSim>);
1427 decoder
1428 .state
1429 .write()
1430 .await
1431 .contracts_map
1432 .insert(
1433 Bytes::from("0xba12222222228d8ba445958a75a0704d566bf2c8").lpad(20, 0),
1434 HashSet::from([pool_id.clone()]),
1435 );
1436
1437 let msg = load_test_msg("balancer_v2_delta");
1439
1440 let _ = decoder
1442 .decode(&msg)
1443 .await
1444 .expect("decode failure");
1445
1446 }
1448
1449 #[test]
1450 fn test_generate_proxy_token_address() {
1451 let idx = 1;
1452 let generated_address =
1453 generate_proxy_token_address(idx).expect("proxy token address should be valid");
1454 assert_eq!(generated_address, address!("000000000000000000000000000000001badbabe"));
1455
1456 let idx = 123456;
1457 let generated_address =
1458 generate_proxy_token_address(idx).expect("proxy token address should be valid");
1459 assert_eq!(generated_address, address!("00000000000000000000000000001e240badbabe"));
1460 }
1461
1462 #[tokio::test(flavor = "multi_thread")]
1463 async fn test_euler_hook_low_pool_manager_balance() {
1464 let mut decoder = TychoStreamDecoder::new();
1465
1466 decoder.register_decoder_with_context::<crate::evm::protocol::uniswap_v4::state::UniswapV4State>(
1467 "uniswap_v4_hooks", DecoderContext::new().vm_traces(true)
1468 );
1469
1470 let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap();
1471 let teth = Bytes::from_str("0xd11c452fc99cf405034ee446803b6f6c1f6d5ed8").unwrap();
1472 let tokens = HashMap::from([
1473 (
1474 weth.clone(),
1475 Token::new(&weth, "WETH", 18, 100, &[Some(100_000)], Chain::Ethereum, 100),
1476 ),
1477 (
1478 teth.clone(),
1479 Token::new(&teth, "tETH", 18, 100, &[Some(100_000)], Chain::Ethereum, 100),
1480 ),
1481 ]);
1482
1483 decoder.set_tokens(tokens.clone()).await;
1484
1485 let msg = load_test_msg("euler_hook_snapshot");
1486 let res = decoder
1487 .decode(&msg)
1488 .await
1489 .expect("decode failure");
1490
1491 let pool_state = res
1492 .states
1493 .get("0xc70d7fbd7fcccdf726e02fed78548b40dc52502b097c7a1ee7d995f4d4396134")
1494 .expect("Couldn't find target pool");
1495 let amount_out = pool_state
1496 .get_amount_out(
1497 BigUint::from_str("1000000000000000000").unwrap(),
1498 tokens.get(&teth).unwrap(),
1499 tokens.get(&weth).unwrap(),
1500 )
1501 .expect("Get amount out failed");
1502
1503 assert_eq!(amount_out.amount, BigUint::from_str("1216190190361759119").unwrap());
1504 }
1505}