Skip to main content

tycho_simulation/evm/protocol/etherfi/
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};
6
7use super::state::{
8    decode_attribute, decode_u16_attribute, decode_u64_attribute, BucketAttributes, BucketLimit,
9    EtherfiState, PoolState, RedemptionInfo, Venue, WrapperState, BURN_BUCKET, EXIT_FEE_BPS_ATTR,
10    EXIT_FEE_SPLIT_TO_TREASURY_BPS_ATTR, LOW_WATERMARK_BPS_ATTR, MINT_BUCKET, POOL_COMPONENT_ID,
11    REDEMPTION_BUCKET, TOTAL_SHARES_ATTR, TOTAL_VALUE_IN_LP_ATTR, TOTAL_VALUE_OUT_OF_LP_ATTR,
12    WEETH_SHARES_ATTR, WRAPPER_COMPONENT_ID,
13};
14use crate::protocol::{
15    errors::InvalidSnapshotError,
16    models::{DecoderContext, TryFromWithBlock},
17};
18
19impl TryFromWithBlock<ComponentWithState, BlockHeader> for EtherfiState {
20    type Error = InvalidSnapshotError;
21
22    async fn try_from_with_header(
23        snapshot: ComponentWithState,
24        block: BlockHeader,
25        _account_balances: &HashMap<Bytes, HashMap<Bytes, Bytes>>,
26        _all_tokens: &HashMap<Bytes, Token>,
27        _decoder_context: &DecoderContext,
28    ) -> Result<Self, Self::Error> {
29        let raw = |name: &str| -> Result<&Bytes, InvalidSnapshotError> {
30            snapshot
31                .state
32                .attributes
33                .get(name)
34                .ok_or_else(|| InvalidSnapshotError::MissingAttribute(name.to_string()))
35        };
36        let value = |name: &str| -> Result<U256, InvalidSnapshotError> {
37            decode_attribute(name, raw(name)?).map_err(InvalidSnapshotError::ValueError)
38        };
39        let value_u64 = |name: &str| -> Result<u64, InvalidSnapshotError> {
40            decode_u64_attribute(name, raw(name)?).map_err(InvalidSnapshotError::ValueError)
41        };
42        let value_u16 = |name: &str| -> Result<u16, InvalidSnapshotError> {
43            decode_u16_attribute(name, raw(name)?).map_err(InvalidSnapshotError::ValueError)
44        };
45        let bucket = |names: &BucketAttributes| -> Result<BucketLimit, InvalidSnapshotError> {
46            Ok(BucketLimit {
47                capacity: value_u64(names.capacity)?,
48                remaining: value_u64(names.remaining)?,
49                last_refill: value_u64(names.last_refill)?,
50                refill_rate: value_u64(names.refill_rate)?,
51            })
52        };
53
54        let id = &snapshot.component.id;
55        let venue = if id.eq_ignore_ascii_case(POOL_COMPONENT_ID) {
56            Venue::Pool(PoolState {
57                redemption: RedemptionInfo {
58                    limit: bucket(&REDEMPTION_BUCKET)?,
59                    exit_fee_split_to_treasury_bps: value_u16(EXIT_FEE_SPLIT_TO_TREASURY_BPS_ATTR)?,
60                    exit_fee_bps: value_u16(EXIT_FEE_BPS_ATTR)?,
61                    low_watermark_bps: value_u16(LOW_WATERMARK_BPS_ATTR)?,
62                },
63                mint_limit: bucket(&MINT_BUCKET)?,
64                burn_limit: bucket(&BURN_BUCKET)?,
65            })
66        } else if id.eq_ignore_ascii_case(WRAPPER_COMPONENT_ID) {
67            Venue::Wrapper(WrapperState { weeth_shares: value(WEETH_SHARES_ATTR)? })
68        } else {
69            return Err(InvalidSnapshotError::ValueError(format!(
70                "unknown EtherFi component id {id}"
71            )));
72        };
73
74        // Seeded from the observed header; `apply_block` moves it to the execution block before
75        // the state is quoted.
76        Ok(EtherfiState::new(
77            block.timestamp,
78            value(TOTAL_VALUE_OUT_OF_LP_ATTR)?,
79            value(TOTAL_VALUE_IN_LP_ATTR)?,
80            value(TOTAL_SHARES_ATTR)?,
81            venue,
82        ))
83    }
84}