Skip to main content

tycho_simulation/evm/protocol/cowamm/
tycho_decoder.rs

1use std::collections::HashMap;
2
3use alloy::primitives::U256;
4use tycho_client::feed::{synchronizer::ComponentWithState, BlockHeader};
5use tycho_common::{dto::ProtocolComponent, models::token::Token, Bytes};
6
7use crate::{
8    evm::protocol::cowamm::state::CowAMMState,
9    protocol::{
10        errors::InvalidSnapshotError,
11        models::{DecoderContext, TryFromWithBlock},
12    },
13};
14
15impl TryFromWithBlock<ComponentWithState, BlockHeader> for CowAMMState {
16    type Error = InvalidSnapshotError;
17
18    async fn try_from_with_header(
19        snapshot: ComponentWithState,
20        _block: BlockHeader,
21        _account_balances: &HashMap<Bytes, HashMap<Bytes, Bytes>>,
22        _all_tokens: &HashMap<Bytes, Token>,
23        _decoder_context: &DecoderContext,
24    ) -> Result<Self, Self::Error> {
25        let address = snapshot
26            .component
27            .static_attributes
28            .get("address")
29            .ok_or_else(|| InvalidSnapshotError::MissingAttribute("address".to_string()))?
30            .clone();
31
32        let token_a = snapshot
33            .component
34            .static_attributes
35            .get("token_a")
36            .ok_or_else(|| InvalidSnapshotError::MissingAttribute("token_a".to_string()))?
37            .clone();
38
39        let liquidity_a = U256::from_be_slice(
40            snapshot
41                .state
42                .attributes
43                .get("liquidity_a")
44                .ok_or_else(|| InvalidSnapshotError::MissingAttribute("liquidity_a".to_string()))?
45                .as_ref(),
46        );
47
48        let liquidity_b = U256::from_be_slice(
49            snapshot
50                .state
51                .attributes
52                .get("liquidity_b")
53                .ok_or_else(|| InvalidSnapshotError::MissingAttribute("liquidity_b".to_string()))?
54                .as_ref(),
55        );
56
57        let token_b = snapshot
58            .component
59            .static_attributes
60            .get("token_b")
61            .ok_or_else(|| InvalidSnapshotError::MissingAttribute("token_b".to_string()))?
62            .clone();
63
64        let lp_token = snapshot
65            .component
66            .static_attributes
67            .get("lp_token")
68            .ok_or_else(|| InvalidSnapshotError::MissingAttribute("lp_token".to_string()))?
69            .clone();
70
71        let lp_token_supply = U256::from_be_slice(
72            snapshot
73                .state
74                .attributes
75                .get("lp_token_supply")
76                .ok_or_else(|| {
77                    InvalidSnapshotError::MissingAttribute("lp_token_supply".to_string())
78                })?
79                .as_ref(),
80        );
81
82        let fee = 0u64;
83
84        //weight_a and weight_b are left padded big endian numbers of 32 bytes
85        //we want a U256 number from the hex representation
86        // weight_a and weight_b are left-padded big-endian numbers
87        let weight_a = U256::from_be_slice(
88            snapshot
89                .component
90                .static_attributes
91                .get("weight_a")
92                .ok_or_else(|| InvalidSnapshotError::MissingAttribute("weight_a".to_string()))?
93                .as_ref(),
94        );
95
96        let weight_b = U256::from_be_slice(
97            snapshot
98                .component
99                .static_attributes
100                .get("weight_b")
101                .ok_or_else(|| InvalidSnapshotError::MissingAttribute("weight_b".to_string()))?
102                .as_ref(),
103        );
104
105        Ok(Self::new(
106            address,
107            token_a,
108            token_b,
109            liquidity_a,
110            liquidity_b,
111            lp_token,
112            lp_token_supply,
113            weight_a,
114            weight_b,
115            fee,
116        ))
117    }
118}
119
120pub fn attributes() -> HashMap<String, Bytes> {
121    HashMap::from([
122        ("liquidity_a".to_string(), Bytes::from(vec![0; 32])),
123        ("liquidity_b".to_string(), Bytes::from(vec![0; 32])),
124        ("lp_token_supply".to_string(), Bytes::from(vec![0; 32])),
125    ])
126}
127pub fn static_attributes() -> HashMap<String, Bytes> {
128    HashMap::from([
129        ("address".to_string(), Bytes::from(vec![0; 32])),
130        ("weight_a".to_string(), Bytes::from(vec![0; 32])),
131        ("weight_b".to_string(), Bytes::from(vec![0; 32])),
132        ("token_a".to_string(), Bytes::from(vec![0; 32])),
133        ("token_b".to_string(), Bytes::from(vec![0; 32])),
134        ("lp_token".to_string(), Bytes::from(vec![0; 32])),
135        ("fee".to_string(), 0u64.into()),
136    ])
137}
138
139pub fn component() -> ProtocolComponent {
140    ProtocolComponent { static_attributes: static_attributes(), ..Default::default() }
141}
142
143pub fn state() -> CowAMMState {
144    CowAMMState::new(
145        Bytes::from(vec![0; 32]),
146        Bytes::from(vec![0; 32]),
147        Bytes::from(vec![0; 32]),
148        U256::from(0),
149        U256::from(0),
150        Bytes::from(vec![0; 32]),
151        U256::from(0),
152        U256::from(0),
153        U256::from(0),
154        0u64,
155    )
156}
157
158#[cfg(test)]
159mod tests {
160    use rstest::rstest;
161    use tycho_common::dto::ResponseProtocolState;
162
163    use super::*;
164
165    fn header() -> BlockHeader {
166        BlockHeader {
167            number: 1,
168            hash: Bytes::from(vec![0; 32]),
169            parent_hash: Bytes::from(vec![0; 32]),
170            revert: false,
171            timestamp: 0,
172        }
173    }
174
175    #[tokio::test]
176    async fn test_cowamm_try_from_with_block() {
177        let snapshot = ComponentWithState {
178            state: ResponseProtocolState { attributes: attributes(), ..Default::default() },
179            component: component(),
180            component_tvl: None,
181            entrypoints: Vec::new(),
182        };
183
184        let decoder_context = DecoderContext::new();
185
186        let result = CowAMMState::try_from_with_header(
187            snapshot,
188            header(),
189            &HashMap::default(),
190            &HashMap::default(),
191            &decoder_context,
192        )
193        .await
194        .unwrap();
195
196        assert_eq!(state(), result);
197    }
198
199    #[tokio::test]
200    #[rstest]
201    #[case::missing_weight_a("address")]
202    #[case::missing_weight_a("weight_a")]
203    #[case::missing_weight_b("weight_b")]
204    #[case::missing_token_a("token_a")]
205    #[case::missing_token_b("token_b")]
206    #[case::missing_liquidity_a("liquidity_a")]
207    #[case::missing_liquidity_b("liquidity_b")]
208    #[case::missing_lp_token("lp_token")]
209    #[case::missing_lp_token_supply("lp_token_supply")]
210
211    async fn test_cowamm_try_from_missing_attribute(#[case] missing_attribute: &str) {
212        let mut component = component();
213        let mut attributes = attributes();
214
215        let _ = match missing_attribute {
216            "liquidity_a" | "liquidity_b" | "lp_token_supply" => {
217                attributes.remove(missing_attribute)
218            }
219            "address" | "weight_a" | "weight_b" | "token_a" | "token_b" | "lp_token" => component
220                .static_attributes
221                .remove(missing_attribute),
222            &_ => None,
223        };
224
225        let snapshot = ComponentWithState {
226            state: ResponseProtocolState {
227                component_id: "State1".to_owned(),
228                attributes,
229                balances: HashMap::new(),
230            },
231            component,
232            component_tvl: None,
233            entrypoints: Vec::new(),
234        };
235
236        let decoder_context = DecoderContext::new();
237
238        let result = CowAMMState::try_from_with_header(
239            snapshot,
240            header(),
241            &HashMap::default(),
242            &HashMap::default(),
243            &decoder_context,
244        )
245        .await;
246
247        assert!(result.is_err());
248        assert!(matches!(
249            result.unwrap_err(),
250            InvalidSnapshotError::MissingAttribute(ref attr) if attr == missing_attribute
251        ));
252    }
253}