Skip to main content

tycho_simulation/evm/protocol/cowamm/
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    use crate::evm::protocol::test_utils::try_decode_snapshot_with_defaults;
165
166    #[tokio::test]
167    async fn test_cowamm_try_from_with_block() {
168        let snapshot = ComponentWithState {
169            state: ResponseProtocolState { attributes: attributes(), ..Default::default() },
170            component: component(),
171            component_tvl: None,
172            entrypoints: Vec::new(),
173        };
174
175        let result = try_decode_snapshot_with_defaults::<CowAMMState>(snapshot)
176            .await
177            .expect("reconstructing state");
178
179        assert_eq!(state(), result);
180    }
181
182    #[tokio::test]
183    #[rstest]
184    #[case::missing_weight_a("address")]
185    #[case::missing_weight_a("weight_a")]
186    #[case::missing_weight_b("weight_b")]
187    #[case::missing_token_a("token_a")]
188    #[case::missing_token_b("token_b")]
189    #[case::missing_liquidity_a("liquidity_a")]
190    #[case::missing_liquidity_b("liquidity_b")]
191    #[case::missing_lp_token("lp_token")]
192    #[case::missing_lp_token_supply("lp_token_supply")]
193
194    async fn test_cowamm_try_from_missing_attribute(#[case] missing_attribute: &str) {
195        let mut component = component();
196        let mut attributes = attributes();
197
198        let _ = match missing_attribute {
199            "liquidity_a" | "liquidity_b" | "lp_token_supply" => {
200                attributes.remove(missing_attribute)
201            }
202            "address" | "weight_a" | "weight_b" | "token_a" | "token_b" | "lp_token" => component
203                .static_attributes
204                .remove(missing_attribute),
205            &_ => None,
206        };
207
208        let snapshot = ComponentWithState {
209            state: ResponseProtocolState {
210                component_id: "State1".to_owned(),
211                attributes,
212                balances: HashMap::new(),
213            },
214            component,
215            component_tvl: None,
216            entrypoints: Vec::new(),
217        };
218
219        let result = try_decode_snapshot_with_defaults::<CowAMMState>(snapshot).await;
220
221        assert!(result.is_err());
222        assert!(matches!(
223            result.unwrap_err(),
224            InvalidSnapshotError::MissingAttribute(ref attr) if attr == missing_attribute
225        ));
226    }
227}