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 {
724 code: None,
725 change: ChangeType::Update,
726 ..update.clone()
727 };
728 account_update_by_address.insert(original_address, proxy_update);
729
730 *impl_addr
731 }
732 None => {
733 let impl_addr = generate_proxy_token_address(
738 state_guard.proxy_token_addresses.len() as u32,
739 )?;
740 state_guard
741 .proxy_token_addresses
742 .insert(original_address, impl_addr);
743
744 let proxy_state = create_proxy_token_account(
749 original_address,
750 Some(impl_addr),
751 &update.slots,
752 update.chain,
753 update.balance,
754 );
755 new_proxy_accounts.push(proxy_state);
756
757 impl_addr
758 }
759 };
760
761 if update.code.is_some() {
763 let impl_update = AccountUpdate {
764 address: impl_addr,
765 slots: HashMap::new(),
766 ..update.clone()
767 };
768 account_update_by_address.insert(impl_addr, impl_update);
769 }
770 } else {
771 account_update_by_address.insert(update.address, update);
773 }
774 }
775 drop(state_guard);
776
777 let state_guard = self.state.read().await;
778 info!("Updating engine with {} contract deltas", deltas.account_deltas.len());
779 update_engine(
780 SHARED_TYCHO_DB.clone(),
781 header.clone().block(),
782 None,
783 account_update_by_address,
784 )
785 .map_err(|e| StreamDecodeError::Fatal(e.to_string()))?;
786
787 if !new_proxy_accounts.is_empty() {
790 SHARED_TYCHO_DB
791 .force_update_accounts(new_proxy_accounts)
792 .map_err(|e| StreamDecodeError::Fatal(e.to_string()))?;
793 }
794 info!("Engine updated");
795
796 let mut pools_to_update = HashSet::new();
798 for (account, _update) in deltas.account_deltas {
799 pools_to_update.extend(
801 contracts_map
802 .get(&account)
803 .cloned()
804 .unwrap_or_default(),
805 );
806 pools_to_update.extend(
808 state_guard
809 .contracts_map
810 .get(&account)
811 .cloned()
812 .unwrap_or_default(),
813 );
814 }
815
816 let all_balances = Balances {
818 component_balances: deltas
819 .component_balances
820 .iter()
821 .map(|(pool_id, bals)| {
822 let mut balances = HashMap::new();
823 for (t, b) in bals {
824 balances.insert(t.clone(), b.balance.clone());
825 }
826 pools_to_update.insert(pool_id.clone());
827 (pool_id.clone(), balances)
828 })
829 .collect(),
830 account_balances: deltas
831 .account_balances
832 .iter()
833 .map(|(account, bals)| {
834 let mut balances = HashMap::new();
835 for (t, b) in bals {
836 balances.insert(t.clone(), b.balance.clone());
837 }
838 pools_to_update.extend(
839 contracts_map
840 .get(account)
841 .cloned()
842 .unwrap_or_default(),
843 );
844 (account.clone(), balances)
845 })
846 .collect(),
847 };
848
849 for (id, update) in deltas.state_deltas {
851 let update_with_block = Self::add_block_info_to_delta(
853 ProtocolStateDelta::from(update),
854 current_block.clone(),
855 );
856 match Self::apply_update(
857 &id,
858 update_with_block,
859 &mut updated_states,
860 &state_guard,
861 &all_balances,
862 ) {
863 Ok(_) => {
864 pools_to_update.remove(&id);
865 }
866 Err(e) => {
867 if self.skip_state_decode_failures {
868 warn!(pool = id, error = %e, "Failed to apply state update, marking component as removed");
869 updated_states.remove(&id);
871 if let Some(component) = new_pairs.remove(&id) {
873 removed_pairs.insert(id.clone(), component);
874 } else if let Some(component) = state_guard.components.get(&id) {
875 removed_pairs.insert(id.clone(), component.clone());
876 } else {
877 warn!(pool = id, "Component not found in new_pairs or state, cannot add to removed_pairs");
880 }
881 pools_to_update.remove(&id);
882
883 msg_failed_components.insert(id.clone());
885 } else {
886 return Err(e);
887 }
888 }
889 }
890 }
891
892 for pool in pools_to_update {
894 let default_delta_with_block = Self::add_block_info_to_delta(
896 ProtocolStateDelta::default(),
897 current_block.clone(),
898 );
899 match Self::apply_update(
900 &pool,
901 default_delta_with_block,
902 &mut updated_states,
903 &state_guard,
904 &all_balances,
905 ) {
906 Ok(_) => {}
907 Err(e) => {
908 if self.skip_state_decode_failures {
909 warn!(pool = pool, error = %e, "Failed to apply contract/balance update, marking component as removed");
910 updated_states.remove(&pool);
912 if let Some(component) = new_pairs.remove(&pool) {
914 removed_pairs.insert(pool.clone(), component);
915 } else if let Some(component) = state_guard.components.get(&pool) {
916 removed_pairs.insert(pool.clone(), component.clone());
917 } else {
918 warn!(pool = pool, "Component not found in new_pairs or state, cannot add to removed_pairs");
921 }
922
923 msg_failed_components.insert(pool.clone());
925 } else {
926 return Err(e);
927 }
928 }
929 }
930 }
931 };
932 }
933
934 let mut state_guard = self.state.write().await;
936
937 state_guard
939 .failed_components
940 .extend(msg_failed_components);
941
942 updated_states.retain(|id, _| {
946 !state_guard
947 .failed_components
948 .contains(id)
949 });
950 new_pairs.retain(|id, _| {
951 !state_guard
952 .failed_components
953 .contains(id)
954 });
955
956 state_guard
957 .states
958 .extend(updated_states.clone());
959
960 state_guard.current_block_number = block_number_or_timestamp;
961
962 for (id, component) in new_pairs.iter() {
964 state_guard
965 .components
966 .insert(id.clone(), component.clone());
967 }
968
969 for id in removed_pairs.keys() {
971 state_guard.components.remove(id);
972 }
973
974 for (key, values) in contracts_map {
975 state_guard
976 .contracts_map
977 .entry(key)
978 .or_insert_with(HashSet::new)
979 .extend(values);
980 }
981
982 Ok(Update::new(block_number_or_timestamp, updated_states, new_pairs)
984 .set_is_partial(is_partial)
985 .set_removed_pairs(removed_pairs)
986 .set_sync_states(msg.sync_states.clone()))
987 }
988
989 pub async fn apply_deltas_ephemeral(
1008 &self,
1009 pending_deltas: &HashMap<String, BlockAggregatedChanges>,
1010 header: H,
1011 ) -> Result<Update, StreamDecodeError> {
1012 let block_number_or_timestamp = header
1013 .clone()
1014 .block_number_or_timestamp();
1015 let current_block = header.block();
1016 let state_guard = self.state.read().await;
1017
1018 let mut updated_states: HashMap<String, Box<dyn ProtocolSim>> = HashMap::new();
1019
1020 for deltas in pending_deltas.values() {
1021 let all_balances = Balances {
1022 component_balances: deltas
1023 .component_balances
1024 .iter()
1025 .map(|(pool_id, bals)| {
1026 let balances = bals
1027 .iter()
1028 .map(|(t, b)| (t.clone(), b.balance.clone()))
1029 .collect();
1030 (pool_id.clone(), balances)
1031 })
1032 .collect(),
1033 account_balances: HashMap::new(),
1034 };
1035
1036 for (id, state_delta) in &deltas.state_deltas {
1037 let dto_delta = Self::add_block_info_to_delta(
1038 ProtocolStateDelta::from(state_delta.clone()),
1039 current_block.clone(),
1040 );
1041 if let Err(e) = Self::apply_update(
1042 id,
1043 dto_delta,
1044 &mut updated_states,
1045 &state_guard,
1046 &all_balances,
1047 ) {
1048 warn!(pool = id, error = %e, "EphemeralDeltaTransitionError");
1049 }
1050 }
1051 }
1052
1053 Ok(Update::new(block_number_or_timestamp, updated_states, HashMap::new()))
1054 }
1055
1056 fn add_block_info_to_delta(
1058 mut delta: ProtocolStateDelta,
1059 block_header_opt: Option<BlockHeader>,
1060 ) -> ProtocolStateDelta {
1061 if let Some(header) = block_header_opt {
1062 delta.updated_attributes.insert(
1065 "block_number".to_string(),
1066 Bytes::from(header.number.to_be_bytes().to_vec()),
1067 );
1068 delta.updated_attributes.insert(
1069 "block_timestamp".to_string(),
1070 Bytes::from(header.timestamp.to_be_bytes().to_vec()),
1071 );
1072 }
1073 delta
1074 }
1075
1076 fn apply_update(
1077 id: &String,
1078 update: ProtocolStateDelta,
1079 updated_states: &mut HashMap<String, Box<dyn ProtocolSim>>,
1080 state_guard: &RwLockReadGuard<'_, DecoderState>,
1081 all_balances: &Balances,
1082 ) -> Result<(), StreamDecodeError> {
1083 match updated_states.entry(id.clone()) {
1084 Entry::Occupied(mut entry) => {
1085 let state: &mut Box<dyn ProtocolSim> = entry.get_mut();
1087 state
1088 .delta_transition(update, &state_guard.tokens, all_balances)
1089 .map_err(|e| {
1090 error!(pool = id, error = ?e, "DeltaTransitionError");
1091 StreamDecodeError::Fatal(format!("TransitionFailure: {e:?}"))
1092 })?;
1093 }
1094 Entry::Vacant(_) => {
1095 match state_guard.states.get(id) {
1096 Some(stored_state) => {
1099 let mut state = stored_state.clone();
1100 state
1101 .delta_transition(update, &state_guard.tokens, all_balances)
1102 .map_err(|e| {
1103 error!(pool = id, error = ?e, "DeltaTransitionError");
1104 StreamDecodeError::Fatal(format!("TransitionFailure: {e:?}"))
1105 })?;
1106 updated_states.insert(id.clone(), state);
1107 }
1108 None => debug!(pool = id, reason = "MissingState", "DeltaTransitionError"),
1109 }
1110 }
1111 }
1112 Ok(())
1113 }
1114}
1115
1116fn generate_proxy_token_address(idx: u32) -> Result<Address, StreamDecodeError> {
1118 let padded_idx = format!("{idx:x}");
1119 let padded_zeroes = "0".repeat(33 - padded_idx.len());
1120 let proxy_token_address = format!("{padded_zeroes}{padded_idx}BAdbaBe");
1121 let decoded = hex::decode(proxy_token_address).map_err(|e| {
1122 StreamDecodeError::Fatal(format!("Invalid proxy token address encoding: {e}"))
1123 })?;
1124
1125 const ADDRESS_LENGTH: usize = 20;
1126 if decoded.len() != ADDRESS_LENGTH {
1127 return Err(StreamDecodeError::Fatal(format!(
1128 "Invalid proxy token address length: expected {}, got {}",
1129 ADDRESS_LENGTH,
1130 decoded.len(),
1131 )));
1132 }
1133
1134 Ok(Address::from_slice(&decoded))
1135}
1136
1137fn create_proxy_token_account(
1142 addr: Address,
1143 new_address: Option<Address>,
1144 storage: &HashMap<U256, U256>,
1145 chain: Chain,
1146 balance: Option<U256>,
1147) -> AccountUpdate {
1148 let mut slots = storage.clone();
1149 if let Some(new_address) = new_address {
1150 slots.insert(*IMPLEMENTATION_SLOT, U256::from_be_slice(new_address.as_slice()));
1151 }
1152
1153 AccountUpdate {
1154 address: addr,
1155 chain,
1156 slots,
1157 balance,
1158 code: Some(ERC20_PROXY_BYTECODE.to_vec()),
1159 change: ChangeType::Creation,
1160 }
1161}
1162
1163#[cfg(test)]
1164mock! {
1165 #[derive(Debug)]
1166 pub ProtocolSim {
1167 pub fn fee(&self) -> f64;
1168 pub fn spot_price(&self, base: &Token, quote: &Token) -> Result<f64, SimulationError>;
1169 pub fn get_amount_out(
1170 &self,
1171 amount_in: BigUint,
1172 token_in: &Token,
1173 token_out: &Token,
1174 ) -> Result<GetAmountOutResult, SimulationError>;
1175 pub fn get_limits(
1176 &self,
1177 sell_token: Bytes,
1178 buy_token: Bytes,
1179 ) -> Result<(BigUint, BigUint), SimulationError>;
1180 pub fn delta_transition(
1181 &mut self,
1182 delta: ProtocolStateDelta,
1183 tokens: &HashMap<Bytes, Token>,
1184 balances: &Balances,
1185 ) -> Result<(), TransitionError>;
1186 pub fn clone_box(&self) -> Box<dyn ProtocolSim>;
1187 pub fn eq(&self, other: &dyn ProtocolSim) -> bool;
1188 }
1189}
1190
1191#[cfg(test)]
1192crate::impl_non_serializable_protocol!(MockProtocolSim, "test protocol");
1193
1194#[cfg(test)]
1195impl ProtocolSim for MockProtocolSim {
1196 fn fee(&self) -> f64 {
1197 self.fee()
1198 }
1199
1200 fn spot_price(&self, base: &Token, quote: &Token) -> Result<f64, SimulationError> {
1201 self.spot_price(base, quote)
1202 }
1203
1204 fn get_amount_out(
1205 &self,
1206 amount_in: BigUint,
1207 token_in: &Token,
1208 token_out: &Token,
1209 ) -> Result<GetAmountOutResult, SimulationError> {
1210 self.get_amount_out(amount_in, token_in, token_out)
1211 }
1212
1213 fn get_limits(
1214 &self,
1215 sell_token: Bytes,
1216 buy_token: Bytes,
1217 ) -> Result<(BigUint, BigUint), SimulationError> {
1218 self.get_limits(sell_token, buy_token)
1219 }
1220
1221 fn delta_transition(
1222 &mut self,
1223 delta: ProtocolStateDelta,
1224 tokens: &HashMap<Bytes, Token>,
1225 balances: &Balances,
1226 ) -> Result<(), TransitionError> {
1227 self.delta_transition(delta, tokens, balances)
1228 }
1229
1230 fn clone_box(&self) -> Box<dyn ProtocolSim> {
1231 self.clone_box()
1232 }
1233
1234 fn as_any(&self) -> &dyn Any {
1235 panic!("MockProtocolSim does not support as_any")
1236 }
1237
1238 fn as_any_mut(&mut self) -> &mut dyn Any {
1239 panic!("MockProtocolSim does not support as_any_mut")
1240 }
1241
1242 fn eq(&self, other: &dyn ProtocolSim) -> bool {
1243 self.eq(other)
1244 }
1245
1246 fn typetag_name(&self) -> &'static str {
1247 unreachable!()
1248 }
1249
1250 fn typetag_deserialize(&self) {
1251 unreachable!()
1252 }
1253}
1254
1255#[cfg(test)]
1256mod tests {
1257 use std::str::FromStr;
1258
1259 use alloy::primitives::address;
1260 use mockall::predicate::*;
1261 use rstest::*;
1262 use tycho_client::feed::BlockHeader;
1263 use tycho_common::{models::Chain, Bytes};
1264
1265 use super::*;
1266 use crate::evm::protocol::{curve::CurveState, uniswap_v2::state::UniswapV2State};
1267
1268 #[test]
1269 fn curve_vm_adapter_registration_flagged_deprecated() {
1270 assert!(!is_deprecated_curve_registration::<CurveState>("vm:curve"));
1272 assert!(is_deprecated_curve_registration::<UniswapV2State>("vm:curve"));
1274 assert!(!is_deprecated_curve_registration::<UniswapV2State>("uniswap_v2"));
1276 }
1277
1278 async fn setup_decoder(set_tokens: bool) -> TychoStreamDecoder<BlockHeader> {
1279 let mut decoder = TychoStreamDecoder::new();
1280 decoder.register_decoder::<UniswapV2State>("uniswap_v2");
1281 if set_tokens {
1282 let tokens = [
1283 Bytes::from("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").lpad(20, 0),
1284 Bytes::from("0xdac17f958d2ee523a2206206994597c13d831ec7").lpad(20, 0),
1285 ]
1286 .iter()
1287 .map(|addr| {
1288 let addr_str = format!("{addr:x}");
1289 (
1290 addr.clone(),
1291 Token::new(addr, &addr_str, 18, 100, &[Some(100_000)], Chain::Ethereum, 100),
1292 )
1293 })
1294 .collect();
1295 decoder.set_tokens(tokens).await;
1296 }
1297 decoder
1298 }
1299
1300 fn load_test_msg(name: &str) -> FeedMessage<BlockHeader> {
1301 use std::{fs, path::Path};
1302
1303 use tycho_client::feed::dto;
1304 let project_root = env!("CARGO_MANIFEST_DIR");
1305 let asset_path = Path::new(project_root).join(format!("tests/assets/decoder/{name}.json"));
1306 let json_data = fs::read_to_string(asset_path).expect("Failed to read test asset");
1307 let feed_msg: dto::FeedMessage<BlockHeader> =
1308 serde_json::from_str(&json_data).expect("Failed to deserialize FeedMsg json!");
1309 FeedMessage::from(feed_msg)
1310 }
1311
1312 #[tokio::test]
1313 async fn test_decode() {
1314 let decoder = setup_decoder(true).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 let msg = load_test_msg("uniswap_v2_delta");
1322 let res2 = decoder
1323 .decode(&msg)
1324 .await
1325 .expect("decode failure");
1326
1327 assert_eq!(res1.states.len(), 1);
1328 assert_eq!(res2.states.len(), 1);
1329 assert_eq!(res1.sync_states.len(), 1);
1330 assert_eq!(res2.sync_states.len(), 1);
1331 }
1332
1333 #[tokio::test]
1334 async fn test_decode_token_creation_delta_with_existing_proxy() {
1335 let decoder = setup_decoder(true).await;
1336 let msg = load_test_msg("uniswap_v2_delta_token_creation");
1337
1338 decoder
1341 .decode(&msg)
1342 .await
1343 .expect("first decode (proxy creation) failed");
1344
1345 decoder
1349 .decode(&msg)
1350 .await
1351 .expect("decode of a token Creation delta with an existing proxy failed");
1352 }
1353
1354 #[tokio::test]
1355 async fn test_decode_component_missing_token() {
1356 let decoder = setup_decoder(false).await;
1357 let tokens = [Bytes::from("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").lpad(20, 0)]
1358 .iter()
1359 .map(|addr| {
1360 let addr_str = format!("{addr:x}");
1361 (
1362 addr.clone(),
1363 Token::new(addr, &addr_str, 18, 100, &[Some(100_000)], Chain::Ethereum, 100),
1364 )
1365 })
1366 .collect();
1367 decoder.set_tokens(tokens).await;
1368
1369 let msg = load_test_msg("uniswap_v2_snapshot");
1370 let res1 = decoder
1371 .decode(&msg)
1372 .await
1373 .expect("decode failure");
1374
1375 assert_eq!(res1.states.len(), 0);
1376 }
1377
1378 #[tokio::test]
1379 async fn test_decode_component_bad_id() {
1380 let decoder = setup_decoder(true).await;
1381 let msg = load_test_msg("uniswap_v2_snapshot_broken_id");
1382
1383 match decoder.decode(&msg).await {
1384 Err(StreamDecodeError::Fatal(msg)) => {
1385 assert_eq!(msg, "Component id mismatch");
1386 }
1387 Ok(_) => {
1388 panic!("Expected failures to be raised")
1389 }
1390 }
1391 }
1392
1393 #[rstest]
1394 #[case(true)]
1395 #[case(false)]
1396 #[tokio::test]
1397 async fn test_decode_component_bad_state(#[case] skip_failures: bool) {
1398 let mut decoder = setup_decoder(true).await;
1399 decoder.skip_state_decode_failures = skip_failures;
1400
1401 let msg = load_test_msg("uniswap_v2_snapshot_broken_state");
1402 match decoder.decode(&msg).await {
1403 Err(StreamDecodeError::Fatal(msg)) => {
1404 if !skip_failures {
1405 assert_eq!(msg, "Missing attributes reserve0");
1406 } else {
1407 panic!("Expected failures to be ignored. Err: {msg}")
1408 }
1409 }
1410 Ok(res) => {
1411 if !skip_failures {
1412 panic!("Expected failures to be raised")
1413 } else {
1414 assert_eq!(res.states.len(), 0);
1415 }
1416 }
1417 }
1418 }
1419
1420 #[tokio::test]
1421 async fn test_decode_updates_state_on_contract_change() {
1422 let decoder = setup_decoder(true).await;
1423
1424 let mut mock_state = MockProtocolSim::new();
1426
1427 mock_state
1428 .expect_clone_box()
1429 .times(1)
1430 .returning(|| {
1431 let mut cloned_mock_state = MockProtocolSim::new();
1432 cloned_mock_state
1434 .expect_delta_transition()
1435 .times(1)
1436 .returning(|_, _, _| Ok(()));
1437 cloned_mock_state
1438 .expect_clone_box()
1439 .times(1)
1440 .returning(|| Box::new(MockProtocolSim::new()));
1441 Box::new(cloned_mock_state)
1442 });
1443
1444 let pool_id =
1446 "0x93d199263632a4ef4bb438f1feb99e57b4b5f0bd0000000000000000000005c2".to_string();
1447 decoder
1448 .state
1449 .write()
1450 .await
1451 .states
1452 .insert(pool_id.clone(), Box::new(mock_state) as Box<dyn ProtocolSim>);
1453 decoder
1454 .state
1455 .write()
1456 .await
1457 .contracts_map
1458 .insert(
1459 Bytes::from("0xba12222222228d8ba445958a75a0704d566bf2c8").lpad(20, 0),
1460 HashSet::from([pool_id.clone()]),
1461 );
1462
1463 let msg = load_test_msg("balancer_v2_delta");
1465
1466 let _ = decoder
1468 .decode(&msg)
1469 .await
1470 .expect("decode failure");
1471
1472 }
1474
1475 #[test]
1476 fn test_generate_proxy_token_address() {
1477 let idx = 1;
1478 let generated_address =
1479 generate_proxy_token_address(idx).expect("proxy token address should be valid");
1480 assert_eq!(generated_address, address!("000000000000000000000000000000001badbabe"));
1481
1482 let idx = 123456;
1483 let generated_address =
1484 generate_proxy_token_address(idx).expect("proxy token address should be valid");
1485 assert_eq!(generated_address, address!("00000000000000000000000000001e240badbabe"));
1486 }
1487
1488 #[tokio::test(flavor = "multi_thread")]
1489 async fn test_euler_hook_low_pool_manager_balance() {
1490 let mut decoder = TychoStreamDecoder::new();
1491
1492 decoder.register_decoder_with_context::<crate::evm::protocol::uniswap_v4::state::UniswapV4State>(
1493 "uniswap_v4_hooks", DecoderContext::new().vm_traces(true)
1494 );
1495
1496 let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap();
1497 let teth = Bytes::from_str("0xd11c452fc99cf405034ee446803b6f6c1f6d5ed8").unwrap();
1498 let tokens = HashMap::from([
1499 (
1500 weth.clone(),
1501 Token::new(&weth, "WETH", 18, 100, &[Some(100_000)], Chain::Ethereum, 100),
1502 ),
1503 (
1504 teth.clone(),
1505 Token::new(&teth, "tETH", 18, 100, &[Some(100_000)], Chain::Ethereum, 100),
1506 ),
1507 ]);
1508
1509 decoder.set_tokens(tokens.clone()).await;
1510
1511 let msg = load_test_msg("euler_hook_snapshot");
1512 let res = decoder
1513 .decode(&msg)
1514 .await
1515 .expect("decode failure");
1516
1517 let pool_state = res
1518 .states
1519 .get("0xc70d7fbd7fcccdf726e02fed78548b40dc52502b097c7a1ee7d995f4d4396134")
1520 .expect("Couldn't find target pool");
1521 let amount_out = pool_state
1522 .get_amount_out(
1523 BigUint::from_str("1000000000000000000").unwrap(),
1524 tokens.get(&teth).unwrap(),
1525 tokens.get(&weth).unwrap(),
1526 )
1527 .expect("Get amount out failed");
1528
1529 assert_eq!(amount_out.amount, BigUint::from_str("1216190190361759119").unwrap());
1530 }
1531}