tycho_simulation/evm/protocol/velodrome_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::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    /// Decodes a `ComponentWithState` into a `AerodromeSlipstreamsState`. Errors with a
20    /// `InvalidSnapshotError` if the snapshot is missing any required attributes or if the fee
21    /// amount is not supported.
22    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            // Make sure it only happens for 0 values, otherwise error.
64            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}