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