1use std::{collections::HashMap, fmt::Debug};
2
3use alloy::{
4 primitives::{keccak256, Address, Keccak256, B256, U256},
5 sol_types::SolValue,
6};
7use revm::{
8 state::{AccountInfo, Bytecode},
9 DatabaseRef,
10};
11use tycho_common::simulation::errors::SimulationError;
12
13use super::{
14 constants::{EXTERNAL_ACCOUNT, MAX_BALANCE},
15 utils::coerce_error,
16};
17use crate::evm::{
18 engine_db::engine_db_interface::EngineDatabaseInterface,
19 simulation::{BlockEnvOverrides, SimulationEngine, SimulationParameters, SimulationResult},
20};
21
22#[derive(Debug, Clone)]
23pub struct TychoSimulationResponse {
24 pub return_value: Vec<u8>,
25 pub simulation_result: SimulationResult,
26}
27
28#[derive(Clone, Debug)]
52pub struct TychoSimulationContract<D: EngineDatabaseInterface + Clone + Debug>
53where
54 <D as DatabaseRef>::Error: Debug,
55 <D as EngineDatabaseInterface>::Error: Debug,
56{
57 pub(crate) address: Address,
58 pub(crate) engine: SimulationEngine<D>,
59}
60
61impl<D: EngineDatabaseInterface + Clone + Debug> TychoSimulationContract<D>
62where
63 <D as DatabaseRef>::Error: Debug,
64 <D as EngineDatabaseInterface>::Error: Debug,
65{
66 pub fn new(address: Address, engine: SimulationEngine<D>) -> Result<Self, SimulationError> {
67 Ok(Self { address, engine })
68 }
69
70 pub fn new_contract(
72 address: Address,
73 adapter_contract_bytecode: Bytecode,
74 engine: SimulationEngine<D>,
75 ) -> Result<Self, SimulationError> {
76 engine
77 .state
78 .init_account(
79 address,
80 AccountInfo {
81 balance: *MAX_BALANCE,
82 nonce: 0,
83 code_hash: B256::from(keccak256(
84 adapter_contract_bytecode
85 .clone()
86 .bytes(),
87 )),
88 code: Some(adapter_contract_bytecode),
89 },
90 None,
91 false,
92 )
93 .map_err(|err| {
94 SimulationError::FatalError(format!(
95 "Failed to init contract account in simulation engine: {err:?}"
96 ))
97 })?;
98
99 Ok(Self { address, engine })
100 }
101
102 fn encode_input(&self, selector: &str, args: impl SolValue) -> Vec<u8> {
103 let mut hasher = Keccak256::new();
104 hasher.update(selector.as_bytes());
105 let selector_bytes = &hasher.finalize()[..4];
106 let mut call_data = selector_bytes.to_vec();
107 let mut encoded_args = args.abi_encode();
108 if encoded_args.len() > 32 &&
112 encoded_args[..32] ==
113 [0u8; 31]
114 .into_iter()
115 .chain([32].to_vec())
116 .collect::<Vec<u8>>()
117 {
118 encoded_args = encoded_args[32..].to_vec();
119 }
120 call_data.extend(encoded_args);
121 call_data
122 }
123
124 #[allow(clippy::too_many_arguments)]
125 pub fn call(
126 &self,
127 selector: &str,
128 args: impl SolValue,
129 overrides: Option<HashMap<Address, HashMap<U256, U256>>>,
130 caller: Option<Address>,
131 value: U256,
132 transient_storage: Option<HashMap<Address, HashMap<U256, U256>>>,
133 block_overrides: Option<BlockEnvOverrides>,
134 ) -> Result<TychoSimulationResponse, SimulationError> {
135 let call_data = self.encode_input(selector, args);
136 let params = SimulationParameters {
137 data: call_data,
138 to: self.address,
139 overrides,
140 caller: caller.unwrap_or(*EXTERNAL_ACCOUNT),
141 value,
142 gas_limit: None,
143 transient_storage,
144 block_overrides,
145 };
146
147 let sim_result = self.simulate(params)?;
148
149 Ok(TychoSimulationResponse {
150 return_value: sim_result.result.to_vec(),
151 simulation_result: sim_result,
152 })
153 }
154
155 fn simulate(&self, params: SimulationParameters) -> Result<SimulationResult, SimulationError> {
156 self.engine
157 .simulate(¶ms)
158 .map_err(|e| coerce_error(&e, "pool_state", params.gas_limit))
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use std::str::FromStr;
165
166 use alloy::primitives::{hex, Bytes};
167 use tycho_client::feed::BlockHeader;
168
169 use super::*;
170 use crate::evm::{
171 engine_db::{
172 create_engine,
173 engine_db_interface::EngineDatabaseInterface,
174 simulation_db::SimulationDB,
175 tycho_db::PreCachedDBError,
176 utils::{get_client, get_runtime},
177 },
178 protocol::vm::{constants::BALANCER_V2, utils::string_to_bytes32},
179 };
180
181 #[derive(Debug, Clone)]
182 struct MockDatabase;
183
184 impl DatabaseRef for MockDatabase {
185 type Error = PreCachedDBError;
186
187 fn basic_ref(&self, _address: Address) -> Result<Option<AccountInfo>, Self::Error> {
188 Ok(Some(AccountInfo::default()))
189 }
190
191 fn code_by_hash_ref(&self, _code_hash: B256) -> Result<Bytecode, Self::Error> {
192 Ok(Bytecode::new())
193 }
194
195 fn storage_ref(&self, _address: Address, _index: U256) -> Result<U256, Self::Error> {
196 Ok(U256::from(0))
197 }
198
199 fn block_hash_ref(&self, _number: u64) -> Result<B256, Self::Error> {
200 Ok(B256::default())
201 }
202 }
203
204 impl EngineDatabaseInterface for MockDatabase {
205 type Error = String;
206
207 fn init_account(
208 &self,
209 _address: Address,
210 _account: AccountInfo,
211 _permanent_storage: Option<HashMap<U256, U256>>,
212 _mocked: bool,
213 ) -> Result<(), <Self as EngineDatabaseInterface>::Error> {
214 Ok(())
216 }
217
218 fn clear_temp_storage(&mut self) -> Result<(), <Self as EngineDatabaseInterface>::Error> {
219 Ok(())
221 }
222
223 fn get_current_block(&self) -> Option<tycho_client::feed::BlockHeader> {
224 None }
226 }
227
228 fn create_mock_engine() -> SimulationEngine<MockDatabase> {
229 SimulationEngine::new(MockDatabase, false)
230 }
231
232 fn create_contract() -> TychoSimulationContract<MockDatabase> {
233 let address = Address::ZERO;
234 let engine = create_mock_engine();
235 TychoSimulationContract::new_contract(
236 address,
237 Bytecode::new_raw(BALANCER_V2.into()),
238 engine,
239 )
240 .unwrap()
241 }
242
243 #[test]
244 fn test_encode_input_get_capabilities() {
245 let contract = create_contract();
246
247 let pool_id =
249 "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef".to_string();
250 let sell_token = Address::from_str("0000000000000000000000000000000000000002").unwrap();
251 let buy_token = Address::from_str("0000000000000000000000000000000000000003").unwrap();
252
253 let encoded = contract.encode_input(
254 "getCapabilities(bytes32,address,address)",
255 (string_to_bytes32(&pool_id).unwrap(), sell_token, buy_token),
256 );
257
258 let expected_selector = hex!("48bd7dfd");
260 assert_eq!(&encoded[..4], &expected_selector[..]);
261
262 let expected_pool_id =
263 hex!("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef");
264 let expected_sell_token =
265 hex!("0000000000000000000000000000000000000000000000000000000000000002"); let expected_buy_token =
267 hex!("0000000000000000000000000000000000000000000000000000000000000003"); assert_eq!(&encoded[4..36], &expected_pool_id); assert_eq!(&encoded[36..68], &expected_sell_token); assert_eq!(&encoded[68..100], &expected_buy_token); }
273
274 #[test]
275 fn test_transient_storage() {
276 let db = SimulationDB::new(
277 get_client(None).expect("Failed to create Tycho RPC client"),
278 get_runtime().expect("Failed to create Tokio runtime"),
279 None,
280 );
281 let mut engine = create_engine(db, true).expect("Failed to create simulation engine");
282
283 let block = BlockHeader {
285 number: 1,
286 hash: tycho_common::Bytes::from_str(
287 "0x0000000000000000000000000000000000000000000000000000000000000000",
288 )
289 .unwrap(),
290 timestamp: 1748397011,
291 ..Default::default()
292 };
293 engine.state.set_block(Some(block));
294
295 let contract_address = Address::from_str("0x0010d0d5db05933fa0d9f7038d365e1541a41888") .expect("Invalid address");
297 let storage_slot: U256 =
298 U256::from_str("0xc090fc4683624cfc3884e9d8de5eca132f2d0ec062aff75d43c0465d5ceeab23")
299 .expect("Invalid storage slot");
300 let storage_value: U256 = U256::from(42); let bytecode = Bytecode::new_raw(Bytes::from_str("0x6004361015600b575f80fd5b5f3560e01c63f8a8fd6d14601d575f80fd5b346054575f3660031901126054577fc090fc4683624cfc3884e9d8de5eca132f2d0ec062aff75d43c0465d5ceeab235c5f5260205ff35b5f80fdfea2646970667358221220f176684ab08659ff85817601a5398286c6029cf53bde9b1cce1a0c9bace67dad64736f6c634300081c0033").unwrap());
322 let contract = TychoSimulationContract::new_contract(contract_address, bytecode, engine)
323 .expect("Failed to create GenericVMHookHandler");
324
325 let transient_storage_params =
326 HashMap::from([(contract_address, HashMap::from([(storage_slot, storage_value)]))]);
327 let args = ();
328 let selector = "test()";
329
330 let res = contract
331 .call(
332 selector,
333 args,
334 None,
335 None,
336 U256::from(0u64),
337 Some(transient_storage_params),
338 None,
339 )
340 .unwrap();
341
342 let decoded: U256 = U256::abi_decode(&res.return_value)
343 .map_err(|e| {
344 SimulationError::FatalError(format!("Failed to decode test return value: {e:?}"))
345 })
346 .unwrap();
347
348 assert_eq!(decoded, storage_value);
349 }
350
351 #[test]
352 fn test_call_applies_block_overrides() {
353 let db = SimulationDB::new(
354 get_client(None).expect("Failed to create Tycho RPC client"),
355 get_runtime().expect("Failed to create Tokio runtime"),
356 None,
357 );
358 let mut engine = create_engine(db, false).expect("Failed to create simulation engine");
359 engine
360 .state
361 .set_block(Some(BlockHeader {
362 number: 1,
363 hash: tycho_common::Bytes::from_str(
364 "0x0000000000000000000000000000000000000000000000000000000000000000",
365 )
366 .unwrap(),
367 timestamp: 2,
368 ..Default::default()
369 }));
370
371 let contract_address = Address::from_str("0x0000000000000000000000000000000000001234")
372 .expect("Invalid contract address");
373 let bytecode = Bytecode::new_raw(Bytes::from_static(&[
384 0x43, 0x60, 0x00, 0x52, 0x60, 0x20, 0x60, 0x00, 0xf3, ]));
391 let contract = TychoSimulationContract::new_contract(contract_address, bytecode, engine)
392 .expect("Failed to create test contract");
393
394 let res = contract
395 .call(
396 "test()",
397 (),
398 None,
399 None,
400 U256::ZERO,
401 None,
402 Some(BlockEnvOverrides { number: Some(123), timestamp: Some(456) }),
403 )
404 .expect("contract call should apply block overrides");
405
406 assert_eq!(U256::from_be_slice(&res.return_value), U256::from(123));
407 }
408}