tycho_simulation/evm/protocol/aerodrome_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::AerodromeSlipstreamsState;
8use crate::{
9 evm::protocol::utils::{
10 slipstreams::{dynamic_fee_module::DynamicFeeConfig, observations::Observation},
11 uniswap::{i24_be_bytes_to_i32, tick_list::TickInfo},
12 },
13 protocol::{
14 errors::InvalidSnapshotError,
15 models::{DecoderContext, TryFromWithBlock},
16 },
17};
18
19impl TryFromWithBlock<ComponentWithState, BlockHeader> for AerodromeSlipstreamsState {
20 type Error = InvalidSnapshotError;
21
22 async fn try_from_with_header(
26 snapshot: ComponentWithState,
27 block: BlockHeader,
28 _account_balances: &HashMap<Bytes, HashMap<Bytes, Bytes>>,
29 _all_tokens: &HashMap<Bytes, Token>,
30 _decoder_context: &DecoderContext,
31 ) -> Result<Self, Self::Error> {
32 let liq = snapshot
33 .state
34 .attributes
35 .get("liquidity")
36 .ok_or_else(|| InvalidSnapshotError::MissingAttribute("liquidity".to_string()))?
37 .clone();
38
39 let liq_16_bytes = if liq.len() == 32 {
43 if liq == Bytes::zero(32) {
45 Bytes::from([0; 16])
46 } else {
47 return Err(InvalidSnapshotError::ValueError(format!(
48 "Liquidity bytes too long for {liq}, expected 16"
49 )));
50 }
51 } else {
52 liq
53 };
54
55 let liquidity = u128::from(liq_16_bytes);
56
57 let sqrt_price = U256::from_be_slice(
58 snapshot
59 .state
60 .attributes
61 .get("sqrt_price_x96")
62 .ok_or_else(|| InvalidSnapshotError::MissingAttribute("sqrt_price".to_string()))?,
63 );
64
65 let observation_index = u16::from(
66 snapshot
67 .state
68 .attributes
69 .get("observationIndex")
70 .ok_or_else(|| {
71 InvalidSnapshotError::MissingAttribute("observationIndex".to_string())
72 })?
73 .clone(),
74 );
75
76 let observation_cardinality = u16::from(
77 snapshot
78 .state
79 .attributes
80 .get("observationCardinality")
81 .ok_or_else(|| {
82 InvalidSnapshotError::MissingAttribute("observationCardinality".to_string())
83 })?
84 .clone(),
85 );
86
87 let dfc_base_fee = u32::from(
88 snapshot
89 .state
90 .attributes
91 .get("dfc_baseFee")
92 .ok_or_else(|| InvalidSnapshotError::MissingAttribute("dfc_baseFee".to_string()))?
93 .clone(),
94 );
95
96 let dfc_scaling_factor = u64::from(
97 snapshot
98 .state
99 .attributes
100 .get("dfc_scalingFactor")
101 .ok_or_else(|| {
102 InvalidSnapshotError::MissingAttribute("dfc_scalingFactor".to_string())
103 })?
104 .clone(),
105 );
106
107 let dfc_fee_cap = u32::from(
108 snapshot
109 .state
110 .attributes
111 .get("dfc_feeCap")
112 .ok_or_else(|| InvalidSnapshotError::MissingAttribute("dfc_feeCap".to_string()))?
113 .clone(),
114 );
115
116 let tick_spacing = snapshot
117 .component
118 .static_attributes
119 .get("tick_spacing")
120 .ok_or_else(|| InvalidSnapshotError::MissingAttribute("tick_spacing".to_string()))?
121 .clone();
122
123 let tick_spacing_4_bytes = if tick_spacing.len() == 32 {
124 if tick_spacing == Bytes::zero(32) {
126 Bytes::from([0; 4])
127 } else {
128 return Err(InvalidSnapshotError::ValueError(format!(
129 "Tick Spacing bytes too long for {tick_spacing}, expected 4"
130 )));
131 }
132 } else {
133 tick_spacing
134 };
135
136 let tick_spacing = i24_be_bytes_to_i32(&tick_spacing_4_bytes);
137
138 let default_fee = u32::from(
139 snapshot
140 .component
141 .static_attributes
142 .get("default_fee")
143 .ok_or_else(|| InvalidSnapshotError::MissingAttribute("default_fee".to_string()))?
144 .clone(),
145 );
146
147 let tick = snapshot
148 .state
149 .attributes
150 .get("tick")
151 .ok_or_else(|| InvalidSnapshotError::MissingAttribute("tick".to_string()))?
152 .clone();
153
154 let ticks_4_bytes = if tick.len() == 32 {
158 if tick == Bytes::zero(32) {
160 Bytes::from([0; 4])
161 } else {
162 return Err(InvalidSnapshotError::ValueError(format!(
163 "Tick bytes too long for {tick}, expected 4"
164 )));
165 }
166 } else {
167 tick
168 };
169 let tick = i24_be_bytes_to_i32(&ticks_4_bytes);
170
171 let ticks: Result<Vec<_>, _> = snapshot
172 .state
173 .attributes
174 .iter()
175 .filter_map(|(key, value)| {
176 if key.starts_with("ticks/") {
177 Some(
178 key.split('/')
179 .nth(1)?
180 .parse::<i32>()
181 .map_err(|err| InvalidSnapshotError::ValueError(err.to_string()))
182 .and_then(|tick_index| {
183 TickInfo::new(tick_index, i128::from(value.clone())).map_err(
184 |err| InvalidSnapshotError::ValueError(err.to_string()),
185 )
186 }),
187 )
188 } else {
189 None
190 }
191 })
192 .collect();
193
194 let mut ticks = match ticks {
195 Ok(ticks) if !ticks.is_empty() => ticks
196 .into_iter()
197 .filter(|t| t.net_liquidity != 0)
198 .collect::<Vec<_>>(),
199 _ => return Err(InvalidSnapshotError::MissingAttribute("tick_liquidities".to_string())),
200 };
201
202 ticks.sort_by_key(|tick| tick.index);
203
204 let observations: Vec<Observation> = snapshot
205 .state
206 .attributes
207 .iter()
208 .filter_map(|(key, value)| {
209 key.strip_prefix("observations/")?
210 .parse::<i32>()
211 .ok()
212 .and_then(|idx| Observation::from_attribute(idx, value).ok())
213 })
214 .collect();
215
216 let mut observations: Vec<_> = observations
217 .into_iter()
218 .filter(|t| t.initialized)
219 .collect();
220
221 if observations.is_empty() {
222 return Err(InvalidSnapshotError::MissingAttribute("observations".to_string()));
223 }
224
225 observations.sort_by_key(|observation| observation.index);
226
227 AerodromeSlipstreamsState::new(
228 block.timestamp,
229 liquidity,
230 sqrt_price,
231 observation_index,
232 observation_cardinality,
233 default_fee,
234 tick_spacing,
235 tick,
236 ticks,
237 observations,
238 DynamicFeeConfig::new(dfc_base_fee, dfc_fee_cap, dfc_scaling_factor),
239 )
240 .map_err(|err| InvalidSnapshotError::ValueError(err.to_string()))
241 }
242}