tycho_simulation/evm/protocol/uniswap_v2/
decoder.rs

1use std::collections::HashMap;
2
3use tycho_client::feed::{synchronizer::ComponentWithState, BlockHeader};
4use tycho_common::{models::token::Token, Bytes};
5
6use crate::{
7    evm::protocol::{cpmm::protocol::cpmm_try_from_with_header, uniswap_v2::state::UniswapV2State},
8    protocol::{
9        errors::InvalidSnapshotError,
10        models::{DecoderContext, TryFromWithBlock},
11    },
12};
13
14impl TryFromWithBlock<ComponentWithState, BlockHeader> for UniswapV2State {
15    type Error = InvalidSnapshotError;
16
17    /// Decodes a `ComponentWithState` into a `UniswapV2State`. Errors with a `InvalidSnapshotError`
18    /// if either reserve0 or reserve1 attributes are missing.
19    async fn try_from_with_header(
20        snapshot: ComponentWithState,
21        _block: BlockHeader,
22        _account_balances: &HashMap<Bytes, HashMap<Bytes, Bytes>>,
23        _all_tokens: &HashMap<Bytes, Token>,
24        _decoder_context: &DecoderContext,
25    ) -> Result<Self, Self::Error> {
26        let (reserve0, reserve1) = cpmm_try_from_with_header(snapshot)?;
27        Ok(Self::new(reserve0, reserve1))
28    }
29}
30
31#[cfg(test)]
32mod tests {
33    use std::collections::HashMap;
34
35    use alloy::primitives::U256;
36    use rstest::rstest;
37    use tycho_client::feed::{synchronizer::ComponentWithState, BlockHeader};
38    use tycho_common::{dto::ResponseProtocolState, Bytes};
39
40    use super::super::state::UniswapV2State;
41    use crate::protocol::{
42        errors::InvalidSnapshotError,
43        models::{DecoderContext, TryFromWithBlock},
44    };
45
46    fn header() -> BlockHeader {
47        BlockHeader {
48            number: 1,
49            hash: Bytes::from(vec![0; 32]),
50            parent_hash: Bytes::from(vec![0; 32]),
51            revert: false,
52            timestamp: 1,
53        }
54    }
55
56    #[tokio::test]
57    async fn test_usv2_try_from() {
58        let snapshot = ComponentWithState {
59            state: ResponseProtocolState {
60                component_id: "State1".to_owned(),
61                attributes: HashMap::from([
62                    ("reserve0".to_string(), Bytes::from(vec![0; 32])),
63                    ("reserve1".to_string(), Bytes::from(vec![0; 32])),
64                ]),
65                balances: HashMap::new(),
66            },
67            component: Default::default(),
68            component_tvl: None,
69            entrypoints: Vec::new(),
70        };
71
72        let decoder_context = DecoderContext::new();
73        let result = UniswapV2State::try_from_with_header(
74            snapshot,
75            header(),
76            &HashMap::new(),
77            &HashMap::new(),
78            &decoder_context,
79        )
80        .await;
81
82        assert!(result.is_ok());
83        assert_eq!(result.unwrap(), UniswapV2State::new(U256::from(0u64), U256::from(0u64)));
84    }
85
86    #[tokio::test]
87    #[rstest]
88    #[case::missing_reserve0("reserve0")]
89    #[case::missing_reserve1("reserve1")]
90    async fn test_usv2_try_from_missing_attribute(#[case] missing_attribute: &str) {
91        let mut attributes = HashMap::from([
92            ("reserve0".to_string(), Bytes::from(vec![0; 32])),
93            ("reserve1".to_string(), Bytes::from(vec![0; 32])),
94        ]);
95        attributes.remove(missing_attribute);
96
97        let snapshot = ComponentWithState {
98            state: ResponseProtocolState {
99                component_id: "State1".to_owned(),
100                attributes,
101                balances: HashMap::new(),
102            },
103            component: Default::default(),
104            component_tvl: None,
105            entrypoints: Vec::new(),
106        };
107
108        let decoder_context = DecoderContext::new();
109        let result = UniswapV2State::try_from_with_header(
110            snapshot,
111            header(),
112            &HashMap::new(),
113            &HashMap::new(),
114            &decoder_context,
115        )
116        .await;
117
118        assert!(result.is_err());
119        assert!(matches!(
120            result.unwrap_err(),
121            InvalidSnapshotError::MissingAttribute(ref x) if x == missing_attribute
122        ));
123    }
124}