Skip to main content

tycho_simulation/evm/protocol/rocketpool/
decoder.rs

1use std::collections::HashMap;
2
3use alloy::primitives::U256;
4use tycho_client::feed::{synchronizer::ComponentWithState, BlockHeader};
5use tycho_common::{models::token::Token, Bytes};
6use tycho_ethereum::BytesCodec;
7
8use super::state::RocketpoolState;
9use crate::protocol::{
10    errors::InvalidSnapshotError,
11    models::{DecoderContext, TryFromWithBlock},
12};
13
14impl TryFromWithBlock<ComponentWithState, BlockHeader> for RocketpoolState {
15    type Error = InvalidSnapshotError;
16
17    /// Decodes a `ComponentWithState` into a `RocketpoolState`. Errors with a
18    /// `InvalidSnapshotError` if any required attribute is 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 get_u256 = |name: &str| -> Result<U256, InvalidSnapshotError> {
27            snapshot
28                .state
29                .attributes
30                .get(name)
31                .map(U256::from_bytes)
32                .ok_or_else(|| InvalidSnapshotError::MissingAttribute(name.to_string()))
33        };
34
35        let get_bool = |name: &str| -> Result<bool, InvalidSnapshotError> {
36            snapshot
37                .state
38                .attributes
39                .get(name)
40                .map(|val| !U256::from_bytes(val).is_zero())
41                .ok_or_else(|| InvalidSnapshotError::MissingAttribute(name.to_string()))
42        };
43
44        Ok(RocketpoolState::new(
45            get_u256("reth_supply")?,
46            get_u256("total_eth")?,
47            get_u256("deposit_contract_balance")?,
48            get_u256("reth_contract_liquidity")?,
49            get_u256("deposit_fee")?,
50            get_bool("deposits_enabled")?,
51            get_u256("min_deposit_amount")?,
52            get_u256("max_deposit_pool_size")?,
53            get_bool("deposit_assigning_enabled")?,
54            get_u256("deposit_assign_maximum")?,
55            get_u256("deposit_assign_socialised_maximum")?,
56            get_u256("megapool_queue_requested_total")?,
57            get_u256("target_reth_collateral_rate")?,
58        ))
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use std::collections::HashMap;
65
66    use alloy::primitives::U256;
67    use rstest::rstest;
68    use tycho_client::feed::synchronizer::ComponentWithState;
69    use tycho_common::{dto::ResponseProtocolState, Bytes};
70
71    use super::super::state::RocketpoolState;
72    use crate::{
73        evm::protocol::test_utils::try_decode_snapshot_with_defaults,
74        protocol::errors::InvalidSnapshotError,
75    };
76
77    fn create_test_snapshot() -> ComponentWithState {
78        ComponentWithState {
79            state: ResponseProtocolState {
80                component_id: "Rocketpool".to_owned(),
81                attributes: HashMap::from([
82                    (
83                        "total_eth".to_string(),
84                        Bytes::from(U256::from(100_000_000_000_000_000_000u128).to_be_bytes_vec()),
85                    ),
86                    (
87                        "reth_supply".to_string(),
88                        Bytes::from(U256::from(95_000_000_000_000_000_000u128).to_be_bytes_vec()),
89                    ),
90                    (
91                        "deposit_contract_balance".to_string(),
92                        Bytes::from(U256::from(50_000_000_000_000_000_000u128).to_be_bytes_vec()),
93                    ), // 50 ETH in deposit contract
94                    (
95                        "reth_contract_liquidity".to_string(),
96                        Bytes::from(U256::from(10_000_000_000_000_000_000u128).to_be_bytes_vec()),
97                    ), // 10 ETH in rETH contract
98                    ("deposits_enabled".to_string(), Bytes::from(vec![0x01])),
99                    ("deposit_assigning_enabled".to_string(), Bytes::from(vec![0x01])),
100                    (
101                        "deposit_fee".to_string(),
102                        Bytes::from(U256::from(5_000_000_000_000_000u128).to_be_bytes_vec()),
103                    ), // 0.5%
104                    (
105                        "min_deposit_amount".to_string(),
106                        Bytes::from(U256::from(10_000_000_000_000_000u128).to_be_bytes_vec()),
107                    ), // 0.01 ETH
108                    (
109                        "max_deposit_pool_size".to_string(),
110                        Bytes::from(
111                            U256::from(5_000_000_000_000_000_000_000u128).to_be_bytes_vec(),
112                        ),
113                    ), // 5000 ETH
114                    (
115                        "deposit_assign_maximum".to_string(),
116                        Bytes::from(U256::from(90u64).to_be_bytes_vec()),
117                    ),
118                    (
119                        "deposit_assign_socialised_maximum".to_string(),
120                        Bytes::from(U256::from(0u64).to_be_bytes_vec()),
121                    ),
122                    (
123                        "megapool_queue_requested_total".to_string(),
124                        Bytes::from(
125                            U256::from(1_000_000_000_000_000_000_000_u128).to_be_bytes_vec(),
126                        ),
127                    ),
128                    (
129                        "target_reth_collateral_rate".to_string(),
130                        Bytes::from(U256::from(10_000_000_000_000_000_u128).to_be_bytes_vec()),
131                    ),
132                ]),
133                balances: HashMap::new(),
134            },
135            component: Default::default(),
136            component_tvl: None,
137            entrypoints: Vec::new(),
138        }
139    }
140
141    #[tokio::test]
142    async fn test_rocketpool_try_from() {
143        let snapshot = create_test_snapshot();
144        let result = try_decode_snapshot_with_defaults::<RocketpoolState>(snapshot).await;
145
146        assert!(result.is_ok());
147        let state = result.unwrap();
148        assert_eq!(state.total_eth, U256::from(100_000_000_000_000_000_000u128));
149        assert_eq!(state.reth_supply, U256::from(95_000_000_000_000_000_000u128));
150        assert_eq!(state.deposit_contract_balance, U256::from(50_000_000_000_000_000_000u128));
151        assert_eq!(state.reth_contract_liquidity, U256::from(10_000_000_000_000_000_000u128));
152        assert!(state.deposits_enabled);
153        assert!(state.deposit_assigning_enabled);
154        assert_eq!(state.deposit_assign_maximum, U256::from(90u64));
155        assert_eq!(
156            state.megapool_queue_requested_total,
157            U256::from(1_000_000_000_000_000_000_000u128)
158        );
159    }
160
161    #[tokio::test]
162    #[rstest]
163    #[case::missing_total_eth("total_eth")]
164    #[case::missing_reth_supply("reth_supply")]
165    #[case::missing_deposit_contract_balance("deposit_contract_balance")]
166    #[case::missing_reth_contract_liquidity("reth_contract_liquidity")]
167    #[case::missing_deposits_enabled("deposits_enabled")]
168    #[case::missing_deposit_assigning_enabled("deposit_assigning_enabled")]
169    #[case::missing_deposit_fee("deposit_fee")]
170    #[case::missing_min_deposit_amount("min_deposit_amount")]
171    #[case::missing_max_deposit_pool_size("max_deposit_pool_size")]
172    #[case::missing_deposit_assign_maximum("deposit_assign_maximum")]
173    #[case::missing_deposit_assign_socialised_maximum("deposit_assign_socialised_maximum")]
174    #[case::missing_megapool_queue_requested_total("megapool_queue_requested_total")]
175    #[case::missing_target_reth_collateral_rate("target_reth_collateral_rate")]
176    async fn test_rocketpool_try_from_missing_attribute(#[case] missing_attribute: &str) {
177        let mut snapshot = create_test_snapshot();
178        snapshot
179            .state
180            .attributes
181            .remove(missing_attribute);
182
183        let result = try_decode_snapshot_with_defaults::<RocketpoolState>(snapshot).await;
184
185        assert!(result.is_err());
186        assert!(matches!(
187            result.unwrap_err(),
188            InvalidSnapshotError::MissingAttribute(ref x) if x == missing_attribute
189        ));
190    }
191}