tycho_simulation/evm/protocol/aerodrome_slipstreams/
decoder.rs

1use 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    /// Decodes a `ComponentWithState` into a `AerodromeSlipstreamsState`. Errors with a
23    /// `InvalidSnapshotError` if the snapshot is missing any required attributes or if the fee
24    /// amount is not supported.
25    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        // This is a hotfix because if the liquidity has never been updated after creation, it's
40        // currently encoded as H256::zero(), therefore, we can't decode this as u128.
41        // We can remove this once it has been fixed on the tycho side.
42        let liq_16_bytes = if liq.len() == 32 {
43            // Make sure it only happens for 0 values, otherwise error.
44            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            // Make sure it only happens for 0 values, otherwise error.
125            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        // This is a hotfix because if the tick has never been updated after creation, it's
155        // currently encoded as H256::zero(), therefore, we can't decode this as i32. We can
156        // remove this this will be fixed on the tycho side.
157        let ticks_4_bytes = if tick.len() == 32 {
158            // Make sure it only happens for 0 values, otherwise error.
159            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}