1use std::{
2 collections::{HashMap, HashSet},
3 fmt::Debug,
4};
5
6use alloy::{
7 primitives::{Address, Bytes, Keccak256, U256},
8 sol_types::SolValue,
9};
10use itertools::Itertools;
11use revm::{
12 primitives::KECCAK_EMPTY,
13 state::{AccountInfo, Bytecode},
14 DatabaseRef,
15};
16use tracing::warn;
17use tycho_common::{simulation::errors::SimulationError, Bytes as TychoBytes};
18
19use super::{
20 constants::{EXTERNAL_ACCOUNT, MAX_BALANCE},
21 models::Capability,
22 state::EVMPoolState,
23 tycho_simulation_contract::TychoSimulationContract,
24 utils::get_code_for_contract,
25};
26use crate::evm::{
27 engine_db::{create_engine, engine_db_interface::EngineDatabaseInterface},
28 protocol::utils::bytes_to_address,
29 simulation::{BlockEnvOverrides, SimulationEngine, SimulationParameters},
30};
31
32#[derive(Debug)]
33pub struct EVMPoolStateBuilder<D: EngineDatabaseInterface + Clone + Debug>
80where
81 <D as DatabaseRef>::Error: Debug,
82 <D as EngineDatabaseInterface>::Error: Debug,
83{
84 id: String,
85 tokens: Vec<TychoBytes>,
86 balances: HashMap<Address, U256>,
87 adapter_address: Address,
88 balance_owner: Option<Address>,
89 capabilities: Option<HashSet<Capability>>,
90 involved_contracts: Option<HashSet<Address>>,
91 contract_balances: HashMap<Address, HashMap<Address, U256>>,
92 stateless_contracts: Option<HashMap<String, Option<Vec<u8>>>>,
93 manual_updates: Option<bool>,
94 trace: Option<bool>,
95 engine: Option<SimulationEngine<D>>,
96 adapter_contract: Option<TychoSimulationContract<D>>,
97 adapter_contract_bytecode: Option<Bytecode>,
98 disable_overwrite_tokens: HashSet<Address>,
99 self_contained_tokens: HashSet<Address>,
100 block_overrides: Option<BlockEnvOverrides>,
101}
102
103impl<D> EVMPoolStateBuilder<D>
104where
105 D: EngineDatabaseInterface + Clone + Debug + 'static,
106 <D as DatabaseRef>::Error: Debug,
107 <D as EngineDatabaseInterface>::Error: Debug,
108{
109 pub fn new(id: String, tokens: Vec<TychoBytes>, adapter_address: Address) -> Self {
110 Self {
111 id,
112 tokens,
113 balances: HashMap::new(),
114 adapter_address,
115 balance_owner: None,
116 capabilities: None,
117 involved_contracts: None,
118 contract_balances: HashMap::new(),
119 stateless_contracts: None,
120 manual_updates: None,
121 trace: None,
122 engine: None,
123 adapter_contract: None,
124 adapter_contract_bytecode: None,
125 disable_overwrite_tokens: HashSet::new(),
126 self_contained_tokens: HashSet::new(),
127 block_overrides: None,
128 }
129 }
130
131 #[deprecated(note = "Use account balances instead")]
132 pub fn balance_owner(mut self, balance_owner: Address) -> Self {
133 self.balance_owner = Some(balance_owner);
134 self
135 }
136
137 pub fn balances(mut self, balances: HashMap<Address, U256>) -> Self {
140 self.balances = balances;
141 self
142 }
143
144 pub fn account_balances(
146 mut self,
147 account_balances: HashMap<Address, HashMap<Address, U256>>,
148 ) -> Self {
149 self.contract_balances = account_balances;
150 self
151 }
152
153 pub fn capabilities(mut self, capabilities: HashSet<Capability>) -> Self {
154 self.capabilities = Some(capabilities);
155 self
156 }
157
158 pub fn involved_contracts(mut self, involved_contracts: HashSet<Address>) -> Self {
159 self.involved_contracts = Some(involved_contracts);
160 self
161 }
162
163 pub fn stateless_contracts(
164 mut self,
165 stateless_contracts: HashMap<String, Option<Vec<u8>>>,
166 ) -> Self {
167 self.stateless_contracts = Some(stateless_contracts);
168 self
169 }
170 pub fn manual_updates(mut self, manual_updates: bool) -> Self {
171 self.manual_updates = Some(manual_updates);
172 self
173 }
174
175 pub fn trace(mut self, trace: bool) -> Self {
176 self.trace = Some(trace);
177 self
178 }
179
180 pub fn engine(mut self, engine: SimulationEngine<D>) -> Self {
181 self.engine = Some(engine);
182 self
183 }
184
185 pub fn adapter_contract(mut self, adapter_contract: TychoSimulationContract<D>) -> Self {
186 self.adapter_contract = Some(adapter_contract);
187 self
188 }
189
190 pub fn adapter_contract_bytecode(mut self, adapter_contract_bytecode: Bytecode) -> Self {
191 self.adapter_contract_bytecode = Some(adapter_contract_bytecode);
192 self
193 }
194
195 pub fn disable_overwrite_tokens(mut self, disable_overwrite_tokens: HashSet<Address>) -> Self {
196 self.disable_overwrite_tokens = disable_overwrite_tokens;
197 self
198 }
199
200 pub fn self_contained_tokens(mut self, self_contained_tokens: HashSet<Address>) -> Self {
201 self.self_contained_tokens = self_contained_tokens;
202 self
203 }
204
205 pub fn block_overrides(mut self, block_overrides: Option<BlockEnvOverrides>) -> Self {
206 self.block_overrides = block_overrides;
207 self
208 }
209
210 pub async fn build(mut self, db: D) -> Result<EVMPoolState<D>, SimulationError> {
212 let engine = if let Some(engine) = &self.engine {
213 engine.clone()
214 } else {
215 self.engine = Some(self.get_default_engine(db).await?);
216 self.engine.clone().ok_or_else(|| {
217 SimulationError::FatalError(
218 "Failed to get build engine: Engine not initialized".to_string(),
219 )
220 })?
221 };
222
223 if self.adapter_contract.is_none() {
224 self.adapter_contract = Some(TychoSimulationContract::new_contract(
225 self.adapter_address,
226 self.adapter_contract_bytecode
227 .clone()
228 .ok_or_else(|| {
229 SimulationError::FatalError("Adapter contract bytecode not set".to_string())
230 })?,
231 engine.clone(),
232 )?)
233 };
234
235 let capabilities = if let Some(capabilities) = &self.capabilities {
236 capabilities.clone()
237 } else {
238 self.get_default_capabilities()?
239 };
240
241 let adapter_contract = self.adapter_contract.ok_or_else(|| {
242 SimulationError::FatalError(
243 "Failed to get build engine: Adapter contract not initialized".to_string(),
244 )
245 })?;
246
247 Ok(EVMPoolState::new(
248 self.id,
249 self.tokens,
250 self.balances,
251 self.balance_owner,
252 self.contract_balances,
253 HashMap::new(),
254 capabilities,
255 HashMap::new(),
256 self.involved_contracts
257 .unwrap_or_default(),
258 self.manual_updates.unwrap_or(false),
259 adapter_contract,
260 self.disable_overwrite_tokens,
261 self.self_contained_tokens,
262 self.block_overrides,
263 ))
264 }
265
266 async fn get_default_engine(&self, db: D) -> Result<SimulationEngine<D>, SimulationError> {
267 let engine = create_engine(db, self.trace.unwrap_or(false))?;
268
269 engine
270 .state
271 .init_account(
272 *EXTERNAL_ACCOUNT,
273 AccountInfo {
274 balance: *MAX_BALANCE,
275 nonce: 0,
276 code_hash: KECCAK_EMPTY,
277 code: None,
278 },
279 None,
280 false,
281 )
282 .map_err(|err| {
283 SimulationError::FatalError(format!(
284 "Failed to get default engine: Failed to init external account: {err:?}"
285 ))
286 })?;
287
288 if let Some(stateless_contracts) = &self.stateless_contracts {
289 for (address, bytecode) in stateless_contracts.iter() {
290 let mut addr_str = address.clone();
291 let (code, code_hash) = if bytecode.is_none() {
292 if addr_str.starts_with("call") {
293 addr_str = self
294 .get_address_from_call(&engine, &addr_str)?
295 .to_string();
296 }
297 let code = get_code_for_contract(&addr_str, None).await?;
298 (Some(code.clone()), code.hash_slow())
299 } else {
300 let code =
301 Bytecode::new_raw(Bytes::from(bytecode.clone().ok_or_else(|| {
302 SimulationError::FatalError(
303 "Failed to get default engine: Byte code from stateless contracts is None".into(),
304 )
305 })?));
306 (Some(code.clone()), code.hash_slow())
307 };
308 let account_address: Address = addr_str.parse().map_err(|_| {
309 SimulationError::FatalError(format!(
310 "Failed to get default engine: Couldn't parse address string {address}"
311 ))
312 })?;
313 engine.state.init_account(
314 Address(*account_address),
315 AccountInfo { balance: Default::default(), nonce: 0, code_hash, code },
316 None,
317 false,
318 ).map_err(|err| {
319 SimulationError::FatalError(format!(
320 "Failed to get default engine: Failed to init stateless contract account: {err:?}"
321 ))
322 })?;
323 }
324 }
325 Ok(engine)
326 }
327
328 fn get_default_capabilities(&mut self) -> Result<HashSet<Capability>, SimulationError> {
329 let mut capabilities = Vec::new();
330
331 for tokens_pair in self.tokens.iter().permutations(2) {
333 if let [t0, t1] = tokens_pair[..] {
335 let caps = self
336 .adapter_contract
337 .clone()
338 .ok_or_else(|| {
339 SimulationError::FatalError(
340 "Failed to get default capabilities: Adapter contract not initialized"
341 .to_string(),
342 )
343 })?
344 .get_capabilities(&self.id, bytes_to_address(t0)?, bytes_to_address(t1)?)?;
345 capabilities.push(caps);
346 }
347 }
348
349 let max_capabilities = capabilities
351 .iter()
352 .map(|c| c.len())
353 .max()
354 .unwrap_or(0);
355
356 let common_capabilities: HashSet<_> = capabilities
358 .iter()
359 .fold(capabilities[0].clone(), |acc, cap| acc.intersection(cap).cloned().collect());
360
361 if common_capabilities.len() < max_capabilities {
363 warn!(
364 "Warning: Pool {} has different capabilities depending on the token pair!",
365 self.id
366 );
367 }
368 Ok(common_capabilities)
369 }
370
371 fn get_address_from_call(
381 &self,
382 engine: &SimulationEngine<D>,
383 decoded: &str,
384 ) -> Result<Address, SimulationError> {
385 let method_name = decoded
386 .split(':')
387 .next_back()
388 .ok_or_else(|| {
389 SimulationError::FatalError(
390 "Failed to get address from call: Could not decode method name from call"
391 .into(),
392 )
393 })?;
394
395 let selector = {
396 let mut hasher = Keccak256::new();
397 hasher.update(method_name.as_bytes());
398 let result = hasher.finalize();
399 result[..4].to_vec()
400 };
401
402 let to_address = decoded
403 .split(':')
404 .nth(1)
405 .ok_or_else(|| {
406 SimulationError::FatalError(
407 "Failed to get address from call: Could not decode to_address from call".into(),
408 )
409 })?;
410
411 let parsed_address: Address = to_address.parse().map_err(|_| {
412 SimulationError::FatalError(format!(
413 "Failed to get address from call: Invalid address format: {to_address}"
414 ))
415 })?;
416
417 let sim_params = SimulationParameters {
418 data: selector.to_vec(),
419 to: parsed_address,
420 overrides: Some(HashMap::new()),
421 caller: *EXTERNAL_ACCOUNT,
422 value: U256::from(0u64),
423 gas_limit: None,
424 transient_storage: None,
425 block_overrides: None,
426 };
427
428 let sim_result = engine
429 .simulate(&sim_params)
430 .map_err(|err| SimulationError::FatalError(err.to_string()))?;
431
432 let address: Address = Address::abi_decode(&sim_result.result).map_err(|e| {
433 SimulationError::FatalError(format!("Failed to get address from call: Failed to decode address list from simulation result {e:?}"))
434 })?;
435
436 Ok(address)
437 }
438}
439
440#[cfg(test)]
441mod tests {
442 use std::str::FromStr;
443
444 use super::*;
445 use crate::evm::engine_db::{tycho_db::PreCachedDB, SHARED_TYCHO_DB};
446
447 #[test]
448 fn test_build_without_required_fields() {
449 let id = "pool_1".to_string();
450 let tokens =
451 vec![TychoBytes::from_str("0000000000000000000000000000000000000000").unwrap()];
452 let balances = HashMap::new();
453 let adapter_address =
454 Address::from_str("0xA2C5C98A892fD6656a7F39A2f63228C0Bc846270").unwrap();
455 let result = tokio_test::block_on(
456 EVMPoolStateBuilder::<PreCachedDB>::new(id, tokens, adapter_address)
457 .balances(balances)
458 .build(SHARED_TYCHO_DB.clone()),
459 );
460
461 assert!(result.is_err());
462 match result.unwrap_err() {
463 SimulationError::FatalError(field) => {
464 assert_eq!(field, "Adapter contract bytecode not set")
465 }
466 _ => panic!("Unexpected error type"),
467 }
468 }
469
470 #[test]
471 fn test_engine_setup() {
472 let id = "pool_1".to_string();
473 let token2 = TychoBytes::from_str("0000000000000000000000000000000000000002").unwrap();
474 let token3 = TychoBytes::from_str("0000000000000000000000000000000000000003").unwrap();
475 let tokens = vec![token2.clone(), token3.clone()];
476 let balances = HashMap::new();
477 let adapter_address =
478 Address::from_str("0xA2C5C98A892fD6656a7F39A2f63228C0Bc846270").unwrap();
479 let builder =
480 EVMPoolStateBuilder::<PreCachedDB>::new(id, tokens, adapter_address).balances(balances);
481
482 let engine = tokio_test::block_on(builder.get_default_engine(SHARED_TYCHO_DB.clone()));
483
484 assert!(engine.is_ok());
485 let engine = engine.unwrap();
486 assert!(engine
487 .state
488 .get_account_storage()
489 .expect("Failed to get account storage")
490 .account_present(&EXTERNAL_ACCOUNT));
491 }
492}