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