Skip to main content

evm_dex_pool/erc4626/
standard.rs

1use crate::contracts::IERC4626;
2use crate::pool::base::{EventApplicable, TopicList};
3use alloy::sol_types::SolEvent;
4use alloy::{
5    primitives::{Address, FixedBytes, U256},
6    rpc::types::Log,
7};
8use anyhow::{anyhow, Result};
9use serde::{Deserialize, Serialize};
10use std::any::Any;
11use std::fmt;
12
13const FEE_DENOMINATOR: u128 = 1000000;
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct ERC4626Standard {
17    /// Pool address
18    pub address: Address,
19    /// Token received from depositing, i.e. shares token
20    pub vault_token: Address,
21    /// Token received from withdrawing, i.e. underlying token
22    pub asset_token: Address,
23    /// Total supply of vault tokens
24    pub vault_reserve: U256,
25    /// Total balance of asset tokens held by vault
26    pub asset_reserve: U256,
27    /// Deposit fee in basis points
28    pub deposit_fee: u32,
29    /// Withdrawal fee in basis points
30    pub withdraw_fee: u32,
31}
32
33impl ERC4626Standard {
34    pub fn new(
35        address: Address,
36        vault_token: Address,
37        asset_token: Address,
38        vault_reserve: U256,
39        asset_reserve: U256,
40        deposit_fee: u32,
41        withdraw_fee: u32,
42    ) -> Self {
43        Self {
44            address,
45            vault_token,
46            asset_token,
47            vault_reserve,
48            asset_reserve,
49            deposit_fee,
50            withdraw_fee,
51        }
52    }
53
54    pub fn address(&self) -> Address {
55        self.address
56    }
57
58    /// Calculate output amount for a swap given an input amount and token
59    pub fn calculate_output(&self, token_in: &Address, amount_in: U256) -> Result<U256> {
60        if amount_in.is_zero() {
61            return Ok(U256::ZERO);
62        }
63
64        if self.vault_reserve.is_zero() {
65            return Ok(amount_in);
66        }
67
68        let (fee, reserve_in, reserve_out) = if token_in.eq(&self.vault_token) {
69            (self.withdraw_fee, self.vault_reserve, self.asset_reserve)
70        } else {
71            (self.deposit_fee, self.asset_reserve, self.vault_reserve)
72        };
73
74        Ok(
75            amount_in * reserve_out / reserve_in * U256::from(FEE_DENOMINATOR - fee as u128)
76                / U256::from(FEE_DENOMINATOR),
77        )
78    }
79
80    /// Calculate input amount for a swap given an output amount and token
81    pub fn calculate_input(&self, token_out: &Address, amount_out: U256) -> Result<U256> {
82        if amount_out.is_zero() {
83            return Ok(U256::ZERO);
84        }
85
86        if self.asset_reserve.is_zero() {
87            return Ok(amount_out);
88        }
89
90        let (fee, reserve_in, reserve_out) = if token_out.eq(&self.vault_token) {
91            (self.withdraw_fee, self.vault_reserve, self.asset_reserve)
92        } else {
93            (self.deposit_fee, self.asset_reserve, self.vault_reserve)
94        };
95
96        Ok(
97            amount_out * reserve_in / reserve_out * U256::from(FEE_DENOMINATOR - fee as u128)
98                / U256::from(FEE_DENOMINATOR),
99        )
100    }
101
102    /// Apply a swap to the pool state
103    pub fn apply_swap(
104        &mut self,
105        _token_in: &Address,
106        _amount_in: U256,
107        _amount_out: U256,
108    ) -> Result<()> {
109        Err(anyhow!("Not implemented"))
110    }
111
112    /// Get the tokens in the pool
113    pub fn tokens(&self) -> (Address, Address) {
114        (self.vault_token, self.asset_token)
115    }
116
117    /// Get the pool fee as a fraction
118    pub fn fee(&self) -> f64 {
119        self.deposit_fee as f64 / FEE_DENOMINATOR as f64
120    }
121
122    pub fn fee_raw(&self) -> u64 {
123        self.deposit_fee as u64
124    }
125
126    /// Get a unique identifier for the pool
127    pub fn id(&self) -> String {
128        self.address.to_string()
129    }
130
131    /// Check if the pool contains a token
132    pub fn contains_token(&self, token: &Address) -> bool {
133        *token == self.vault_token || *token == self.asset_token
134    }
135
136    /// Log summary of the pool
137    pub fn log_summary(&self) -> String {
138        format!(
139            "ERC4626 Standard Pool {} - {} (reserves: {}, {})",
140            self.vault_token, self.asset_token, self.vault_reserve, self.asset_reserve
141        )
142    }
143
144    pub fn as_any(&self) -> &dyn Any {
145        self
146    }
147
148    pub fn as_any_mut(&mut self) -> &mut dyn Any {
149        self
150    }
151}
152
153impl EventApplicable for ERC4626Standard {
154    fn apply_log(&mut self, log: &Log) -> Result<()> {
155        match log.topic0() {
156            Some(&IERC4626::Deposit::SIGNATURE_HASH) => {
157                let deposit_data: IERC4626::Deposit = log.log_decode()?.inner.data;
158                self.vault_reserve += deposit_data.shares;
159                self.asset_reserve += deposit_data.assets;
160            }
161            Some(&IERC4626::Withdraw::SIGNATURE_HASH) => {
162                let withdraw_data: IERC4626::Withdraw = log.log_decode()?.inner.data;
163                self.vault_reserve -= withdraw_data.shares;
164                self.asset_reserve -= withdraw_data.assets;
165            }
166            _ => return Ok(()),
167        }
168        Ok(())
169    }
170}
171
172impl TopicList for ERC4626Standard {
173    fn topics() -> Vec<FixedBytes<32>> {
174        vec![
175            IERC4626::Deposit::SIGNATURE_HASH,
176            IERC4626::Withdraw::SIGNATURE_HASH,
177        ]
178    }
179
180    fn profitable_topics() -> Vec<FixedBytes<32>> {
181        vec![
182            IERC4626::Deposit::SIGNATURE_HASH,
183            IERC4626::Withdraw::SIGNATURE_HASH,
184        ]
185    }
186}
187
188impl fmt::Display for ERC4626Standard {
189    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190        write!(
191            f,
192            "ERC4626 Standard Pool {} - {} (reserves: {}, {})",
193            self.vault_token, self.asset_token, self.vault_reserve, self.asset_reserve
194        )
195    }
196}