tycho_simulation/evm/protocol/velodrome_slipstreams/
decoder.rs1use std::collections::HashMap;
2
3use alloy::primitives::U256;
4use tycho_client::feed::{synchronizer::ComponentWithState, BlockHeader};
5use tycho_common::{models::token::Token, Bytes};
6
7use super::state::VelodromeSlipstreamsState;
8use crate::{
9 evm::protocol::utils::uniswap::{i24_be_bytes_to_i32, tick_list::TickInfo},
10 protocol::{
11 errors::InvalidSnapshotError,
12 models::{DecoderContext, TryFromWithBlock},
13 },
14};
15
16impl TryFromWithBlock<ComponentWithState, BlockHeader> for VelodromeSlipstreamsState {
17 type Error = InvalidSnapshotError;
18
19 async fn try_from_with_header(
23 snapshot: ComponentWithState,
24 _block: BlockHeader,
25 _account_balances: &HashMap<Bytes, HashMap<Bytes, Bytes>>,
26 _all_tokens: &HashMap<Bytes, Token>,
27 _decoder_context: &DecoderContext,
28 ) -> Result<Self, Self::Error> {
29 let liquidity = u128::from(
30 snapshot
31 .state
32 .attributes
33 .get("liquidity")
34 .ok_or_else(|| InvalidSnapshotError::MissingAttribute("liquidity".to_string()))?
35 .clone(),
36 );
37
38 let sqrt_price = U256::from_be_slice(
39 snapshot
40 .state
41 .attributes
42 .get("sqrt_price_x96")
43 .ok_or_else(|| InvalidSnapshotError::MissingAttribute("sqrt_price".to_string()))?,
44 );
45
46 let custom_fee = u32::from(
47 snapshot
48 .state
49 .attributes
50 .get("custom_fee")
51 .ok_or_else(|| InvalidSnapshotError::MissingAttribute("custom_fee".to_string()))?
52 .clone(),
53 );
54
55 let tick_spacing = snapshot
56 .component
57 .static_attributes
58 .get("tick_spacing")
59 .ok_or_else(|| InvalidSnapshotError::MissingAttribute("tick_spacing".to_string()))?
60 .clone();
61
62 let tick_spacing_4_bytes = if tick_spacing.len() == 32 {
63 if tick_spacing == Bytes::zero(32) {
65 Bytes::from([0; 4])
66 } else {
67 return Err(InvalidSnapshotError::ValueError(format!(
68 "Tick Spacing bytes too long for {tick_spacing}, expected 4"
69 )));
70 }
71 } else {
72 tick_spacing
73 };
74
75 let tick_spacing = i24_be_bytes_to_i32(&tick_spacing_4_bytes);
76
77 let default_fee = u32::from(
78 snapshot
79 .component
80 .static_attributes
81 .get("default_fee")
82 .ok_or_else(|| InvalidSnapshotError::MissingAttribute("default_fee".to_string()))?
83 .clone(),
84 );
85
86 let tick = i32::from(
87 snapshot
88 .state
89 .attributes
90 .get("tick")
91 .ok_or_else(|| InvalidSnapshotError::MissingAttribute("tick".to_string()))?
92 .clone(),
93 );
94
95 let ticks: Result<Vec<_>, _> = snapshot
96 .state
97 .attributes
98 .iter()
99 .filter_map(|(key, value)| {
100 if key.starts_with("ticks/") {
101 Some(
102 key.split('/')
103 .nth(1)?
104 .parse::<i32>()
105 .map_err(|err| InvalidSnapshotError::ValueError(err.to_string()))
106 .and_then(|tick_index| {
107 TickInfo::new(tick_index, i128::from(value.clone())).map_err(
108 |err| InvalidSnapshotError::ValueError(err.to_string()),
109 )
110 }),
111 )
112 } else {
113 None
114 }
115 })
116 .collect();
117
118 let mut ticks = match ticks {
119 Ok(ticks) if !ticks.is_empty() => ticks
120 .into_iter()
121 .filter(|t| t.net_liquidity != 0)
122 .collect::<Vec<_>>(),
123 _ => return Err(InvalidSnapshotError::MissingAttribute("tick_liquidities".to_string())),
124 };
125
126 ticks.sort_by_key(|tick| tick.index);
127
128 VelodromeSlipstreamsState::new(
129 liquidity,
130 sqrt_price,
131 default_fee,
132 custom_fee,
133 tick_spacing,
134 tick,
135 ticks,
136 )
137 .map_err(|err| InvalidSnapshotError::ValueError(err.to_string()))
138 }
139}