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