tycho_simulation/evm/protocol/erc4626/
state.rs1use std::{any::Any, collections::HashMap, fmt::Debug};
2
3use alloy::primitives::U256;
4use num_bigint::{BigUint, ToBigUint};
5use serde::{Deserialize, Serialize};
6use tracing::trace;
7use tycho_common::{
8 dto::ProtocolStateDelta,
9 models::token::Token,
10 simulation::{
11 errors::{SimulationError, TransitionError},
12 protocol_sim::{Balances, GetAmountOutResult, ProtocolSim},
13 },
14 Bytes,
15};
16
17use crate::evm::{
18 engine_db::{create_engine, SHARED_TYCHO_DB},
19 protocol::{
20 erc4626::vm,
21 u256_num::{biguint_to_u256, u256_to_biguint, u256_to_f64},
22 },
23};
24
25#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
26pub struct ERC4626State {
27 pool_address: Bytes,
28 asset_token: Token,
29 share_token: Token,
30 asset_price: U256,
31 share_price: U256,
32 max_deposit: U256,
33 max_redeem: U256,
34}
35
36impl ERC4626State {
37 pub fn new(
38 pool_address: &Bytes,
39 asset_token: &Token,
40 share_token: &Token,
41 asset_price: U256,
42 share_price: U256,
43 max_deposit: U256,
44 max_redeem: U256,
45 ) -> Self {
46 Self {
47 pool_address: pool_address.clone(),
48 asset_token: asset_token.clone(),
49 share_token: share_token.clone(),
50 asset_price,
51 share_price,
52 max_deposit,
53 max_redeem,
54 }
55 }
56}
57
58#[typetag::serde]
59impl ProtocolSim for ERC4626State {
60 fn fee(&self) -> f64 {
61 0f64
62 }
63
64 fn spot_price(&self, base: &Token, quote: &Token) -> Result<f64, SimulationError> {
65 let share_unit = U256::from(10).pow(U256::from(self.share_token.decimals));
66 let asset_unit = U256::from(10).pow(U256::from(self.asset_token.decimals));
67
68 let one_share_in_asset = u256_to_f64(self.share_price)? / u256_to_f64(asset_unit)?;
69 let one_asset_in_share = u256_to_f64(self.asset_price)? / u256_to_f64(share_unit)?;
70
71 if base.address == self.share_token.address && quote.address == self.asset_token.address {
72 return Ok(one_share_in_asset); }
74
75 if base.address == self.asset_token.address && quote.address == self.share_token.address {
76 return Ok(one_asset_in_share); }
78
79 Err(SimulationError::FatalError("invalid pair".into()))
80 }
81
82 fn get_amount_out(
83 &self,
84 amount_in: BigUint,
85 token_in: &Token,
86 token_out: &Token,
87 ) -> Result<GetAmountOutResult, SimulationError> {
88 let amount_in = biguint_to_u256(&amount_in);
89 if token_in.address == self.asset_token.address &&
90 token_out.address == self.share_token.address
91 {
92 Ok(GetAmountOutResult {
95 amount: u256_to_biguint(
96 amount_in * self.asset_price /
97 U256::from(10).pow(U256::from(self.asset_token.decimals)),
98 ),
99 gas: 86107.to_biguint().expect("infallible"),
100 new_state: self.clone_box(),
101 })
102 } else if token_in.address == self.share_token.address &&
103 token_out.address == self.asset_token.address
104 {
105 Ok(GetAmountOutResult {
108 amount: u256_to_biguint(
109 amount_in * self.share_price /
110 U256::from(10).pow(U256::from(self.share_token.decimals)),
111 ),
112 gas: 74977.to_biguint().expect("infallible"),
113 new_state: self.clone_box(),
114 })
115 } else {
116 Err(SimulationError::FatalError(format!(
117 "Invalid token pair: {}, {}",
118 token_in.address, token_out.address
119 )))
120 }
121 }
122
123 fn get_limits(
124 &self,
125 sell_token: Bytes,
126 buy_token: Bytes,
127 ) -> Result<(BigUint, BigUint), SimulationError> {
128 let share_scale = BigUint::from(10u32).pow(self.share_token.decimals);
129 let asset_scale = BigUint::from(10u32).pow(self.asset_token.decimals);
130
131 if sell_token == self.share_token.address && buy_token == self.asset_token.address {
132 let buy_raw = (&u256_to_biguint(self.max_redeem) * &u256_to_biguint(self.share_price)) /
134 &share_scale;
135 return Ok((u256_to_biguint(self.max_redeem), buy_raw));
136 }
137
138 if sell_token == self.asset_token.address && buy_token == self.share_token.address {
139 let buy_raw = (&u256_to_biguint(self.max_deposit) * &u256_to_biguint(self.asset_price)) /
141 &asset_scale;
142
143 return Ok((u256_to_biguint(self.max_deposit), buy_raw));
144 }
145
146 Err(SimulationError::FatalError(format!(
147 "Invalid token pair: {}, {}",
148 sell_token, buy_token
149 )))
150 }
151
152 fn delta_transition(
153 &mut self,
154 _delta: ProtocolStateDelta,
155 _tokens: &HashMap<Bytes, Token>,
156 _balances: &Balances,
157 ) -> Result<(), TransitionError> {
158 let engine =
159 create_engine(SHARED_TYCHO_DB.clone(), false).expect("Failed to create engine");
160
161 let state =
162 vm::decode_from_vm(&self.pool_address, &self.asset_token, &self.share_token, engine)?;
163 trace!(?state, "Calling delta transition for {}", &self.pool_address);
164
165 self.asset_price = state.asset_price;
166 self.share_price = state.share_price;
167 self.max_deposit = state.max_deposit;
168 self.max_redeem = state.max_redeem;
169 Ok(())
170 }
171
172 fn clone_box(&self) -> Box<dyn ProtocolSim> {
173 Box::new(self.clone())
174 }
175
176 fn as_any(&self) -> &dyn Any {
177 self
178 }
179
180 fn as_any_mut(&mut self) -> &mut dyn Any {
181 self
182 }
183
184 fn eq(&self, other: &dyn ProtocolSim) -> bool {
185 if let Some(other_state) = other
186 .as_any()
187 .downcast_ref::<ERC4626State>()
188 {
189 self.pool_address == other_state.pool_address &&
190 self.asset_token == other_state.asset_token &&
191 self.share_token == other_state.share_token &&
192 self.asset_price == other_state.asset_price &&
193 self.share_price == other_state.share_price &&
194 self.max_deposit == other_state.max_deposit &&
195 self.max_redeem == other_state.max_redeem
196 } else {
197 false
198 }
199 }
200
201 fn query_pool_swap(
202 &self,
203 params: &tycho_common::simulation::protocol_sim::QueryPoolSwapParams,
204 ) -> Result<tycho_common::simulation::protocol_sim::PoolSwap, SimulationError> {
205 crate::evm::query_pool_swap::query_pool_swap(self, params)
206 }
207}