tycho_simulation/evm/protocol/cowamm/
tycho_decoder.rs1use 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 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}