tycho_simulation/evm/protocol/erc4626/
vm.rs1use std::{collections::HashMap, fmt::Debug};
2
3use alloy::{
4 core::sol,
5 primitives::{Address as AlloyAddress, U256},
6 sol_types::SolCall,
7};
8use revm::{primitives::KECCAK_EMPTY, state::AccountInfo, DatabaseRef};
9use tycho_common::{
10 models::{token::Token, Address},
11 simulation::errors::SimulationError,
12};
13use tycho_ethereum::BytesCodec;
14
15use crate::evm::{
16 engine_db::engine_db_interface::EngineDatabaseInterface,
17 protocol::{
18 erc4626::state::ERC4626State,
19 vm::{
20 constants::{EXTERNAL_ACCOUNT, MAX_BALANCE},
21 erc20_token::{Overwrites, TokenProxyOverwriteFactory},
22 },
23 },
24 simulation::{SimulationEngine, SimulationParameters},
25};
26
27sol! {
28 function convertToShares(uint256 assets) public returns (uint256);
29 function convertToAssets(uint256 shares) public returns (uint256);
30 function maxDeposit(address caller) external returns (uint256);
31 function maxWithdraw(address caller) external returns (uint256);
32 function totalSupply() external view returns (uint256);
33}
34
35pub fn decode_from_vm<D: EngineDatabaseInterface + Clone + Debug>(
36 pool: &Address,
37 asset_token: &Token,
38 share_token: &Token,
39 vm_engine: SimulationEngine<D>,
40) -> Result<ERC4626State, SimulationError>
41where
42 <D as DatabaseRef>::Error: Debug,
43 <D as EngineDatabaseInterface>::Error: Debug,
44{
45 let total_supply = simulate_and_decode_call(
46 &vm_engine,
47 pool,
48 AlloyAddress::ZERO,
49 totalSupplyCall {},
50 None,
51 "totalSupply",
52 )?;
53
54 let share_price = simulate_and_decode_call(
55 &vm_engine,
56 pool,
57 AlloyAddress::ZERO,
58 convertToAssetsCall { shares: U256::from(10).pow(U256::from(share_token.decimals)) },
59 None,
60 "convertToAssets",
61 )?;
62
63 let asset_price = simulate_and_decode_call(
64 &vm_engine,
65 pool,
66 AlloyAddress::ZERO,
67 convertToSharesCall { assets: U256::from(10).pow(U256::from(asset_token.decimals)) },
68 None,
69 "convertToShares",
70 )?;
71
72 vm_engine
73 .state
74 .init_account(
75 *EXTERNAL_ACCOUNT,
76 AccountInfo { balance: *MAX_BALANCE, nonce: 0, code_hash: KECCAK_EMPTY, code: None },
77 None,
78 false,
79 )
80 .map_err(|err| {
81 SimulationError::FatalError(format!(
82 "Failed to decode from vm: Failed to init external account: {err:?}"
83 ))
84 })?;
85
86 let mut factory = TokenProxyOverwriteFactory::new(
87 AlloyAddress::from_slice(asset_token.address.as_ref()),
88 None,
89 );
90 factory.set_balance(*MAX_BALANCE, *EXTERNAL_ACCOUNT);
91 let token_overwrites = factory.get_overwrites();
92
93 let caller = AlloyAddress::from_slice(&*EXTERNAL_ACCOUNT.0);
94
95 let max_deposit = simulate_and_decode_call(
98 &vm_engine,
99 pool,
100 caller,
101 maxDepositCall { caller },
102 Some(token_overwrites.clone()),
103 "maxDeposit",
104 )?;
105
106 Ok(ERC4626State::new(
110 pool,
111 asset_token,
112 share_token,
113 asset_price,
114 share_price,
115 max_deposit,
116 total_supply,
117 ))
118}
119
120fn simulate_and_decode_call<D, Call, Ret>(
121 vm_engine: &SimulationEngine<D>,
122 pool: &Address,
123 caller: AlloyAddress,
124 call: Call,
125 overrides: Option<HashMap<AlloyAddress, Overwrites>>,
126 method: &str,
127) -> Result<Ret, SimulationError>
128where
129 D: EngineDatabaseInterface + Clone + Debug,
130 <D as DatabaseRef>::Error: Debug,
131 <D as EngineDatabaseInterface>::Error: Debug,
132 Call: SolCall<Return = Ret>,
133{
134 let data = call.abi_encode();
135 let to = AlloyAddress::from_bytes(pool);
136
137 let params = SimulationParameters {
138 caller,
139 to,
140 data,
141 value: U256::ZERO,
142 overrides,
143 gas_limit: None,
144 transient_storage: None,
145 };
146
147 let res = vm_engine
148 .simulate(¶ms)
149 .map_err(|e| SimulationError::FatalError(format!("{method} simulate failed: {e}")))?;
150
151 Call::abi_decode_returns(res.result.as_ref())
152 .map_err(|e| SimulationError::FatalError(format!("{method} decode failed: {e}")))
153}
154
155#[cfg(test)]
156mod test {
157 use std::str::FromStr;
158
159 use tycho_client::feed::BlockHeader;
160 use tycho_common::{
161 models::{token::Token, Chain},
162 Bytes,
163 };
164
165 use crate::evm::{
166 engine_db::{
167 simulation_db::SimulationDB,
168 utils::{get_client, get_runtime},
169 },
170 protocol::erc4626::vm::decode_from_vm,
171 simulation::SimulationEngine,
172 };
173
174 #[test]
175 #[ignore = "Requires RPC_URL to be set in environment variables or .env file"]
176 fn test_decode_simulation_db() {
177 let usdc = Token::new(
178 &Bytes::from_str("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48").unwrap(),
179 "usdc",
180 6,
181 0,
182 &[Some(20000)],
183 Chain::Ethereum,
184 100,
185 );
186 let sp_usdc = Token::new(
187 &Bytes::from_str("0x28B3a8fb53B741A8Fd78c0fb9A6B2393d896a43d").unwrap(),
188 "sp_usdc",
189 6,
190 0,
191 &[Some(2000)],
192 Chain::Ethereum,
193 100,
194 );
195
196 let block = BlockHeader {
197 number: 23881700,
198 hash: Bytes::from_str(
199 "0xb11cb57ba2620d0f31da3a3c531977707569b796003ba65c44eaca990e6f2957",
200 )
201 .unwrap(),
202 timestamp: 1764145355,
203 ..Default::default()
204 };
205 let mut db = SimulationDB::new(get_client(None).unwrap(), get_runtime().unwrap(), None);
206 db.set_block(Some(block));
207 let vm = SimulationEngine::new(db, false);
208
209 decode_from_vm(
210 &Bytes::from("0x28B3a8fb53B741A8Fd78c0fb9A6B2393d896a43d"),
211 &usdc,
212 &sp_usdc,
213 vm,
214 )
215 .expect("decoding failed");
216 }
217}