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