1#![allow(deprecated)]
2use std::{
3 any::Any,
4 collections::{HashMap, HashSet},
5 fmt::{self, Debug},
6 str::FromStr,
7};
8
9use alloy::primitives::{Address, U256};
10use itertools::Itertools;
11use num_bigint::BigUint;
12use revm::DatabaseRef;
13use serde::{Deserialize, Serialize};
14use tycho_common::{
15 dto::ProtocolStateDelta,
16 models::token::Token,
17 simulation::{
18 errors::{SimulationError, TransitionError},
19 protocol_sim::{Balances, GetAmountOutResult, ProtocolSim},
20 },
21 Bytes,
22};
23
24use super::{
25 constants::{EXTERNAL_ACCOUNT, MAX_BALANCE},
26 erc20_token::{Overwrites, TokenProxyOverwriteFactory},
27 models::Capability,
28 tycho_simulation_contract::TychoSimulationContract,
29};
30use crate::evm::{
31 engine_db::{engine_db_interface::EngineDatabaseInterface, tycho_db::PreCachedDB},
32 protocol::{
33 u256_num::{u256_to_biguint, u256_to_f64},
34 utils::bytes_to_address,
35 },
36 simulation::BlockEnvOverrides,
37};
38
39#[derive(Clone)]
40pub struct EVMPoolState<D: EngineDatabaseInterface + Clone + Debug>
41where
42 <D as DatabaseRef>::Error: Debug,
43 <D as EngineDatabaseInterface>::Error: Debug,
44{
45 id: String,
47 pub tokens: Vec<Bytes>,
49 balances: HashMap<Address, U256>,
51 #[deprecated(note = "Use contract_balances instead")]
55 balance_owner: Option<Address>,
56 spot_prices: HashMap<(Address, Address), f64>,
58 capabilities: HashSet<Capability>,
60 block_lasting_overwrites: HashMap<Address, Overwrites>,
63 involved_contracts: HashSet<Address>,
65 contract_balances: HashMap<Address, HashMap<Address, U256>>,
67 manual_updates: bool,
71 adapter_contract: TychoSimulationContract<D>,
73 disable_overwrite_tokens: HashSet<Address>,
75 block_overrides: Option<BlockEnvOverrides>,
77}
78
79impl<D> Debug for EVMPoolState<D>
80where
81 D: EngineDatabaseInterface + Clone + Debug,
82 <D as DatabaseRef>::Error: Debug,
83 <D as EngineDatabaseInterface>::Error: Debug,
84{
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 f.debug_struct("EVMPoolState")
87 .field("id", &self.id)
88 .field("tokens", &self.tokens)
89 .field("balances", &self.balances)
90 .field("involved_contracts", &self.involved_contracts)
91 .field("contract_balances", &self.contract_balances)
92 .finish_non_exhaustive()
93 }
94}
95
96impl<D> EVMPoolState<D>
97where
98 D: EngineDatabaseInterface + Clone + Debug + 'static,
99 <D as DatabaseRef>::Error: Debug,
100 <D as EngineDatabaseInterface>::Error: Debug,
101{
102 #[allow(clippy::too_many_arguments)]
107 pub fn new(
108 id: String,
109 tokens: Vec<Bytes>,
110 component_balances: HashMap<Address, U256>,
111 balance_owner: Option<Address>,
112 contract_balances: HashMap<Address, HashMap<Address, U256>>,
113 spot_prices: HashMap<(Address, Address), f64>,
114 capabilities: HashSet<Capability>,
115 block_lasting_overwrites: HashMap<Address, Overwrites>,
116 involved_contracts: HashSet<Address>,
117 manual_updates: bool,
118 adapter_contract: TychoSimulationContract<D>,
119 disable_overwrite_tokens: HashSet<Address>,
120 block_overrides: Option<BlockEnvOverrides>,
121 ) -> Self {
122 Self {
123 id,
124 tokens,
125 balances: component_balances,
126 balance_owner,
127 spot_prices,
128 capabilities,
129 block_lasting_overwrites,
130 involved_contracts,
131 contract_balances,
132 manual_updates,
133 adapter_contract,
134 disable_overwrite_tokens,
135 block_overrides,
136 }
137 }
138
139 fn ensure_capability(&self, capability: Capability) -> Result<(), SimulationError> {
150 if !self.capabilities.contains(&capability) {
151 return Err(SimulationError::FatalError(format!(
152 "capability {:?} not supported",
153 capability.to_string()
154 )));
155 }
156 Ok(())
157 }
158 pub fn set_spot_prices(
194 &mut self,
195 tokens: &HashMap<Bytes, Token>,
196 ) -> Result<(), SimulationError> {
197 match self.ensure_capability(Capability::PriceFunction) {
198 Ok(_) => {
199 for [sell_token_address, buy_token_address] in self
200 .tokens
201 .iter()
202 .permutations(2)
203 .map(|p| [p[0], p[1]])
204 {
205 let sell_token_address = bytes_to_address(sell_token_address)?;
206 let buy_token_address = bytes_to_address(buy_token_address)?;
207
208 let overwrites = Some(self.get_overwrites(
209 vec![sell_token_address, buy_token_address],
210 *MAX_BALANCE / U256::from(100),
211 )?);
212
213 let (sell_amount_limit, _) = self.get_amount_limits(
214 vec![sell_token_address, buy_token_address],
215 overwrites.clone(),
216 )?;
217 let price_result = self.adapter_contract.price(
218 &self.id,
219 sell_token_address,
220 buy_token_address,
221 vec![sell_amount_limit / U256::from(100)],
222 overwrites,
223 self.block_overrides.clone(),
224 )?;
225
226 let price = if self
227 .capabilities
228 .contains(&Capability::ScaledPrice)
229 {
230 *price_result.first().ok_or_else(|| {
231 SimulationError::FatalError(
232 "Calculated price array is empty".to_string(),
233 )
234 })?
235 } else {
236 let unscaled_price = price_result.first().ok_or_else(|| {
237 SimulationError::FatalError(
238 "Calculated price array is empty".to_string(),
239 )
240 })?;
241 let sell_token_decimals = self.get_decimals(tokens, &sell_token_address)?;
242 let buy_token_decimals = self.get_decimals(tokens, &buy_token_address)?;
243 *unscaled_price * 10f64.powi(sell_token_decimals as i32) /
244 10f64.powi(buy_token_decimals as i32)
245 };
246
247 self.spot_prices
248 .insert((sell_token_address, buy_token_address), price);
249 }
250 }
251 Err(SimulationError::FatalError(_)) => {
252 for iter_tokens in self.tokens.iter().permutations(2) {
256 let t0 = bytes_to_address(iter_tokens[0])?;
257 let t1 = bytes_to_address(iter_tokens[1])?;
258
259 let overwrites =
260 Some(self.get_overwrites(vec![t0, t1], *MAX_BALANCE / U256::from(100))?);
261
262 let x1 = self
264 .get_amount_limits(vec![t0, t1], overwrites.clone())?
265 .0 /
266 U256::from(100);
267
268 let x2 = x1 + (x1 / U256::from(100));
271
272 let y1 = self
275 .adapter_contract
276 .swap(
277 &self.id,
278 t0,
279 t1,
280 false,
281 x1,
282 overwrites.clone(),
283 self.block_overrides.clone(),
284 )?
285 .0
286 .received_amount;
287
288 let y2 = self
291 .adapter_contract
292 .swap(
293 &self.id,
294 t0,
295 t1,
296 false,
297 x2,
298 overwrites,
299 self.block_overrides.clone(),
300 )?
301 .0
302 .received_amount;
303
304 let sell_token_decimals = self.get_decimals(tokens, &t0)?;
305 let buy_token_decimals = self.get_decimals(tokens, &t1)?;
306
307 let num = y2 - y1;
308 let den = x2 - x1;
309
310 let token_correction =
312 10f64.powi(sell_token_decimals as i32 - buy_token_decimals as i32);
313 let num_f64 = u256_to_f64(num)?;
314 let den_f64 = u256_to_f64(den)?;
315 if den_f64 == 0.0 {
316 return Err(SimulationError::FatalError(
317 "Failed to compute marginal price: denominator converted to 0".into(),
318 ));
319 }
320 let marginal_price = num_f64 / den_f64 * token_correction;
321
322 self.spot_prices
323 .insert((t0, t1), marginal_price);
324 }
325 }
326 Err(e) => return Err(e),
327 }
328
329 Ok(())
330 }
331
332 fn get_decimals(
333 &self,
334 tokens: &HashMap<Bytes, Token>,
335 sell_token_address: &Address,
336 ) -> Result<usize, SimulationError> {
337 tokens
338 .get(&Bytes::from(sell_token_address.as_slice()))
339 .map(|t| t.decimals as usize)
340 .ok_or_else(|| {
341 SimulationError::FatalError(format!(
342 "Failed to scale spot prices! Pool: {} Token 0x{:x} is not available!",
343 self.id, sell_token_address
344 ))
345 })
346 }
347
348 fn get_amount_limits(
365 &self,
366 tokens: Vec<Address>,
367 overwrites: Option<HashMap<Address, HashMap<U256, U256>>>,
368 ) -> Result<(U256, U256), SimulationError> {
369 let limits = self.adapter_contract.get_limits(
370 &self.id,
371 tokens[0],
372 tokens[1],
373 overwrites,
374 self.block_overrides.clone(),
375 )?;
376
377 Ok(limits)
378 }
379
380 fn update_pool_state(
391 &mut self,
392 tokens: &HashMap<Bytes, Token>,
393 balances: &Balances,
394 ) -> Result<(), SimulationError> {
395 self.adapter_contract
397 .engine
398 .clear_temp_storage()
399 .map_err(|err| {
400 SimulationError::FatalError(format!("Failed to clear temporary storage: {err:?}",))
401 })?;
402 self.block_lasting_overwrites.clear();
403
404 if !self.balances.is_empty() {
406 if let Some(bals) = balances
408 .component_balances
409 .get(&self.id)
410 {
411 for (token, bal) in bals {
414 let addr = bytes_to_address(token).map_err(|_| {
415 SimulationError::FatalError(format!(
416 "Invalid token address in balance update: {token:?}"
417 ))
418 })?;
419 self.balances
420 .insert(addr, U256::from_be_slice(bal));
421 }
422 }
423 } else {
424 for contract in &self.involved_contracts {
426 if let Some(bals) = balances
427 .account_balances
428 .get(&Bytes::from(contract.as_slice()))
429 {
430 let contract_entry = self
431 .contract_balances
432 .entry(*contract)
433 .or_default();
434 for (token, bal) in bals {
435 let addr = bytes_to_address(token).map_err(|_| {
436 SimulationError::FatalError(format!(
437 "Invalid token address in balance update: {token:?}"
438 ))
439 })?;
440 contract_entry.insert(addr, U256::from_be_slice(bal));
441 }
442 }
443 }
444 }
445
446 self.set_spot_prices(tokens)?;
448 Ok(())
449 }
450
451 fn get_overwrites(
452 &self,
453 tokens: Vec<Address>,
454 max_amount: U256,
455 ) -> Result<HashMap<Address, Overwrites>, SimulationError> {
456 let token_overwrites = self.get_token_overwrites(tokens, max_amount)?;
457
458 let merged_overwrites =
460 self.merge(&self.block_lasting_overwrites.clone(), &token_overwrites);
461
462 Ok(merged_overwrites)
463 }
464
465 fn get_token_overwrites(
466 &self,
467 tokens: Vec<Address>,
468 max_amount: U256,
469 ) -> Result<HashMap<Address, Overwrites>, SimulationError> {
470 let sell_token = &tokens[0].clone(); let mut res: Vec<HashMap<Address, Overwrites>> = Vec::new();
472 if !self
473 .capabilities
474 .contains(&Capability::TokenBalanceIndependent)
475 {
476 res.push(self.get_balance_overwrites()?);
477 }
478
479 let mut overwrites = TokenProxyOverwriteFactory::new(*sell_token, None);
480
481 overwrites.set_balance(max_amount, Address::from_slice(&*EXTERNAL_ACCOUNT.0));
482
483 overwrites.set_allowance(max_amount, self.adapter_contract.address, *EXTERNAL_ACCOUNT);
485
486 res.push(overwrites.get_overwrites());
487
488 Ok(res
490 .into_iter()
491 .fold(HashMap::new(), |acc, overwrite| self.merge(&acc, &overwrite)))
492 }
493
494 fn get_balance_overwrites(&self) -> Result<HashMap<Address, Overwrites>, SimulationError> {
505 let mut balance_overwrites: HashMap<Address, Overwrites> = HashMap::new();
506
507 let address = match self.balance_owner {
509 Some(owner) => Some(owner),
510 None if !self.contract_balances.is_empty() => None,
511 None => Some(self.id.parse().map_err(|_| {
512 SimulationError::FatalError(
513 "Failed to get balance overwrites: Pool ID is not an address".into(),
514 )
515 })?),
516 };
517
518 if let Some(address) = address {
519 for (token, bal) in &self.balances {
522 let mut overwrites = TokenProxyOverwriteFactory::new(*token, None);
523 overwrites.set_balance(*bal, address);
524 balance_overwrites.extend(overwrites.get_overwrites());
525 }
526 }
527
528 for (contract, balances) in &self.contract_balances {
531 for (token, balance) in balances {
532 let mut overwrites = TokenProxyOverwriteFactory::new(*token, None);
533 overwrites.set_balance(*balance, *contract);
534 balance_overwrites.extend(overwrites.get_overwrites());
535 }
536 }
537
538 for token in &self.disable_overwrite_tokens {
540 balance_overwrites.remove(token);
541 }
542
543 Ok(balance_overwrites)
544 }
545
546 fn merge(
547 &self,
548 target: &HashMap<Address, Overwrites>,
549 source: &HashMap<Address, Overwrites>,
550 ) -> HashMap<Address, Overwrites> {
551 let mut merged = target.clone();
552
553 for (key, source_inner) in source {
554 merged
555 .entry(*key)
556 .or_default()
557 .extend(source_inner.clone());
558 }
559
560 merged
561 }
562
563 #[cfg(test)]
564 pub fn get_involved_contracts(&self) -> HashSet<Address> {
565 self.involved_contracts.clone()
566 }
567
568 #[cfg(test)]
569 pub fn get_manual_updates(&self) -> bool {
570 self.manual_updates
571 }
572
573 #[cfg(test)]
574 pub fn get_balance_owner(&self) -> Option<Address> {
575 self.balance_owner
576 }
577
578 pub fn get_balances(&self) -> &HashMap<Address, U256> {
580 &self.balances
581 }
582
583 #[cfg(test)]
584 pub fn get_block_overrides(&self) -> Option<BlockEnvOverrides> {
585 self.block_overrides.clone()
586 }
587}
588
589impl<D> Serialize for EVMPoolState<D>
590where
591 D: EngineDatabaseInterface + Clone + Debug,
592 <D as DatabaseRef>::Error: Debug,
593 <D as EngineDatabaseInterface>::Error: Debug,
594{
595 fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
596 where
597 S: serde::Serializer,
598 {
599 Err(serde::ser::Error::custom("not supported due vm state deps"))
600 }
601}
602
603impl<'de, D> Deserialize<'de> for EVMPoolState<D>
604where
605 D: EngineDatabaseInterface + Clone + Debug,
606 <D as DatabaseRef>::Error: Debug,
607 <D as EngineDatabaseInterface>::Error: Debug,
608{
609 fn deserialize<De>(_deserializer: De) -> Result<Self, De::Error>
610 where
611 De: serde::Deserializer<'de>,
612 {
613 Err(serde::de::Error::custom("not supported due vm state deps"))
614 }
615}
616
617#[typetag::serialize]
618impl<D> ProtocolSim for EVMPoolState<D>
619where
620 D: EngineDatabaseInterface + Clone + Debug + 'static,
621 <D as DatabaseRef>::Error: Debug,
622 <D as EngineDatabaseInterface>::Error: Debug,
623{
624 fn fee(&self) -> f64 {
625 todo!()
626 }
627
628 fn spot_price(&self, base: &Token, quote: &Token) -> Result<f64, SimulationError> {
629 let base_address = bytes_to_address(&base.address)?;
630 let quote_address = bytes_to_address("e.address)?;
631 self.spot_prices
632 .get(&(base_address, quote_address))
633 .cloned()
634 .ok_or(SimulationError::FatalError(format!(
635 "Spot price not found for base token {base_address} and quote token {quote_address}"
636 )))
637 }
638
639 fn get_amount_out(
640 &self,
641 amount_in: BigUint,
642 token_in: &Token,
643 token_out: &Token,
644 ) -> Result<GetAmountOutResult, SimulationError> {
645 let sell_token_address = bytes_to_address(&token_in.address)?;
646 let buy_token_address = bytes_to_address(&token_out.address)?;
647 let sell_amount = U256::from_be_slice(&amount_in.to_bytes_be());
648 let overwrites = self.get_overwrites(
649 vec![sell_token_address, buy_token_address],
650 *MAX_BALANCE / U256::from(100),
651 )?;
652 let (sell_amount_limit, _) = self.get_amount_limits(
653 vec![sell_token_address, buy_token_address],
654 Some(overwrites.clone()),
655 )?;
656 let (sell_amount_respecting_limit, sell_amount_exceeds_limit) = if self
657 .capabilities
658 .contains(&Capability::HardLimits) &&
659 sell_amount_limit < sell_amount
660 {
661 (sell_amount_limit, true)
662 } else {
663 (sell_amount, false)
664 };
665
666 let overwrites_with_sell_limit =
667 self.get_overwrites(vec![sell_token_address, buy_token_address], sell_amount_limit)?;
668 let complete_overwrites = self.merge(&overwrites, &overwrites_with_sell_limit);
669
670 let (trade, state_changes) = self.adapter_contract.swap(
671 &self.id,
672 sell_token_address,
673 buy_token_address,
674 false,
675 sell_amount_respecting_limit,
676 Some(complete_overwrites),
677 self.block_overrides.clone(),
678 )?;
679
680 let mut new_state = self.clone();
681
682 for (address, state_update) in state_changes {
684 if let Some(storage) = state_update.storage {
685 let block_overwrites = new_state
686 .block_lasting_overwrites
687 .entry(address)
688 .or_default();
689 for (slot, value) in storage {
690 let slot = U256::from_str(&slot.to_string()).map_err(|_| {
691 SimulationError::FatalError("Failed to decode slot index".to_string())
692 })?;
693 let value = U256::from_str(&value.to_string()).map_err(|_| {
694 SimulationError::FatalError("Failed to decode slot overwrite".to_string())
695 })?;
696 block_overwrites.insert(slot, value);
697 }
698 }
699 }
700
701 let tokens = HashMap::from([
703 (token_in.address.clone(), token_in.clone()),
704 (token_out.address.clone(), token_out.clone()),
705 ]);
706 let _ = new_state.set_spot_prices(&tokens);
707
708 let buy_amount = trade.received_amount;
709
710 if sell_amount_exceeds_limit {
711 return Err(SimulationError::InvalidInput(
712 format!("Sell amount exceeds limit {sell_amount_limit}"),
713 Some(GetAmountOutResult::new(
714 u256_to_biguint(buy_amount),
715 u256_to_biguint(trade.gas_used),
716 Box::new(new_state.clone()),
717 )),
718 ));
719 }
720 Ok(GetAmountOutResult::new(
721 u256_to_biguint(buy_amount),
722 u256_to_biguint(trade.gas_used),
723 Box::new(new_state.clone()),
724 ))
725 }
726
727 fn get_limits(
728 &self,
729 sell_token: Bytes,
730 buy_token: Bytes,
731 ) -> Result<(BigUint, BigUint), SimulationError> {
732 let sell_token = bytes_to_address(&sell_token)?;
733 let buy_token = bytes_to_address(&buy_token)?;
734 let overwrites =
735 self.get_overwrites(vec![sell_token, buy_token], *MAX_BALANCE / U256::from(100))?;
736 let limits = self.get_amount_limits(vec![sell_token, buy_token], Some(overwrites))?;
737 Ok((u256_to_biguint(limits.0), u256_to_biguint(limits.1)))
738 }
739
740 fn delta_transition(
741 &mut self,
742 delta: ProtocolStateDelta,
743 tokens: &HashMap<Bytes, Token>,
744 balances: &Balances,
745 ) -> Result<(), TransitionError> {
746 if let Some(block_number) = delta
747 .updated_attributes
748 .get("override_block_number")
749 {
750 let number = <[u8; 8]>::try_from(block_number.as_ref())
751 .map(u64::from_be_bytes)
752 .map_err(|_| {
753 TransitionError::DecodeError(
754 "override_block_number attribute must be an 8-byte big-endian u64"
755 .to_string(),
756 )
757 })?;
758 self.block_overrides
759 .get_or_insert_with(BlockEnvOverrides::default)
760 .number = Some(number);
761 }
762
763 if let Some(block_timestamp) = delta
764 .updated_attributes
765 .get("override_block_timestamp")
766 {
767 let timestamp = <[u8; 8]>::try_from(block_timestamp.as_ref())
768 .map(u64::from_be_bytes)
769 .map_err(|_| {
770 TransitionError::DecodeError(
771 "override_block_timestamp attribute must be an 8-byte big-endian u64"
772 .to_string(),
773 )
774 })?;
775 self.block_overrides
776 .get_or_insert_with(BlockEnvOverrides::default)
777 .timestamp = Some(timestamp);
778 }
779
780 if self.manual_updates {
781 if let Some(marker) = delta
783 .updated_attributes
784 .get("update_marker")
785 {
786 if !marker.is_empty() && marker[0] != 0 {
788 self.update_pool_state(tokens, balances)?;
789 }
790 }
791 } else {
792 self.update_pool_state(tokens, balances)?;
793 }
794
795 Ok(())
796 }
797
798 fn query_pool_swap(
799 &self,
800 params: &tycho_common::simulation::protocol_sim::QueryPoolSwapParams,
801 ) -> Result<tycho_common::simulation::protocol_sim::PoolSwap, SimulationError> {
802 crate::evm::query_pool_swap::query_pool_swap(self, params)
803 }
804
805 fn clone_box(&self) -> Box<dyn ProtocolSim> {
806 Box::new(self.clone())
807 }
808
809 fn as_any(&self) -> &dyn Any {
810 self
811 }
812
813 fn as_any_mut(&mut self) -> &mut dyn Any {
814 self
815 }
816
817 fn eq(&self, other: &dyn ProtocolSim) -> bool {
818 if let Some(other_state) = other
819 .as_any()
820 .downcast_ref::<EVMPoolState<PreCachedDB>>()
821 {
822 self.id == other_state.id
823 } else {
824 false
825 }
826 }
827
828 fn typetag_deserialize(&self) {
830 unreachable!("Only to catch missing typetag attribute on impl blocks. Not called.")
832 }
833}
834
835#[cfg(test)]
836mod tests {
837 use std::default::Default;
838
839 use num_traits::One;
840 use revm::{
841 primitives::KECCAK_EMPTY,
842 state::{AccountInfo, Bytecode},
843 };
844 use serde_json::Value;
845 use tycho_client::feed::BlockHeader;
846 use tycho_common::models::Chain;
847
848 use super::*;
849 use crate::evm::{
850 engine_db::{create_engine, SHARED_TYCHO_DB},
851 protocol::vm::{
852 constants::{BALANCER_V2, ERC20_PROXY_BYTECODE},
853 state_builder::EVMPoolStateBuilder,
854 },
855 simulation::SimulationEngine,
856 tycho_models::AccountUpdate,
857 };
858
859 fn dai() -> Token {
860 Token::new(
861 &Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(),
862 "DAI",
863 18,
864 0,
865 &[Some(10_000)],
866 Chain::Ethereum,
867 100,
868 )
869 }
870
871 fn bal() -> Token {
872 Token::new(
873 &Bytes::from_str("0xba100000625a3754423978a60c9317c58a424e3d").unwrap(),
874 "BAL",
875 18,
876 0,
877 &[Some(10_000)],
878 Chain::Ethereum,
879 100,
880 )
881 }
882
883 fn dai_addr() -> Address {
884 bytes_to_address(&dai().address).unwrap()
885 }
886
887 fn bal_addr() -> Address {
888 bytes_to_address(&bal().address).unwrap()
889 }
890
891 async fn setup_pool_state() -> EVMPoolState<PreCachedDB> {
892 let data_str = include_str!("assets/balancer_contract_storage_block_20463609.json");
893 let data: Value = serde_json::from_str(data_str).expect("Failed to parse JSON");
894
895 let accounts: Vec<AccountUpdate> = serde_json::from_value(data["accounts"].clone())
896 .expect("Expected accounts to match AccountUpdate structure");
897
898 let db = SHARED_TYCHO_DB.clone();
899 let engine: SimulationEngine<_> = create_engine(db.clone(), false).unwrap();
900
901 let block = BlockHeader {
902 number: 20463609,
903 hash: Bytes::from_str(
904 "0x4315fd1afc25cc2ebc72029c543293f9fd833eeb305e2e30159459c827733b1b",
905 )
906 .unwrap(),
907 timestamp: 1722875891,
908 ..Default::default()
909 };
910
911 for account in accounts.clone() {
912 engine
913 .state
914 .init_account(
915 account.address,
916 AccountInfo {
917 balance: account.balance.unwrap_or_default(),
918 nonce: 0u64,
919 code_hash: KECCAK_EMPTY,
920 code: account
921 .code
922 .clone()
923 .map(|arg0: Vec<u8>| Bytecode::new_raw(arg0.into())),
924 },
925 None,
926 false,
927 )
928 .expect("Failed to initialize account");
929 }
930 db.update(accounts, Some(block))
931 .unwrap();
932
933 let tokens = vec![dai().address, bal().address];
934 for token in &tokens {
935 engine
936 .state
937 .init_account(
938 bytes_to_address(token).unwrap(),
939 AccountInfo {
940 balance: U256::from(0),
941 nonce: 0,
942 code_hash: KECCAK_EMPTY,
943 code: Some(Bytecode::new_raw(ERC20_PROXY_BYTECODE.into())),
944 },
945 None,
946 true,
947 )
948 .expect("Failed to initialize account");
949 }
950
951 let block = BlockHeader {
952 number: 18485417,
953 hash: Bytes::from_str(
954 "0x28d41d40f2ac275a4f5f621a636b9016b527d11d37d610a45ac3a821346ebf8c",
955 )
956 .expect("Invalid block hash"),
957 timestamp: 0,
958 ..Default::default()
959 };
960 db.update(vec![], Some(block.clone()))
961 .unwrap();
962
963 let pool_id: String =
964 "0x4626d81b3a1711beb79f4cecff2413886d461677000200000000000000000011".into();
965
966 let stateless_contracts = HashMap::from([(
967 String::from("0x3de27efa2f1aa663ae5d458857e731c129069f29"),
968 Some(Vec::new()),
969 )]);
970
971 let balances = HashMap::from([
972 (dai_addr(), U256::from_str("178754012737301807104").unwrap()),
973 (bal_addr(), U256::from_str("91082987763369885696").unwrap()),
974 ]);
975 let adapter_address =
976 Address::from_str("0xA2C5C98A892fD6656a7F39A2f63228C0Bc846270").unwrap();
977
978 EVMPoolStateBuilder::new(pool_id, tokens, adapter_address)
979 .balances(balances)
980 .balance_owner(Address::from_str("0xBA12222222228d8Ba445958a75a0704d566BF2C8").unwrap())
981 .adapter_contract_bytecode(Bytecode::new_raw(BALANCER_V2.into()))
982 .stateless_contracts(stateless_contracts)
983 .build(SHARED_TYCHO_DB.clone())
984 .await
985 .expect("Failed to build pool state")
986 }
987
988 #[tokio::test]
989 async fn test_init() {
990 SHARED_TYCHO_DB
992 .clear()
993 .expect("Failed to cleared SHARED TX");
994 let pool_state = setup_pool_state().await;
995
996 let expected_capabilities = vec![
997 Capability::SellSide,
998 Capability::BuySide,
999 Capability::PriceFunction,
1000 Capability::HardLimits,
1001 ]
1002 .into_iter()
1003 .collect::<HashSet<_>>();
1004
1005 let capabilities_adapter_contract = pool_state
1006 .adapter_contract
1007 .get_capabilities(
1008 &pool_state.id,
1009 bytes_to_address(&pool_state.tokens[0]).unwrap(),
1010 bytes_to_address(&pool_state.tokens[1]).unwrap(),
1011 )
1012 .unwrap();
1013
1014 assert_eq!(capabilities_adapter_contract, expected_capabilities.clone());
1015
1016 let capabilities_state = pool_state.clone().capabilities;
1017
1018 assert_eq!(capabilities_state, expected_capabilities.clone());
1019
1020 for capability in expected_capabilities.clone() {
1021 assert!(pool_state
1022 .clone()
1023 .ensure_capability(capability)
1024 .is_ok());
1025 }
1026
1027 assert!(pool_state
1028 .clone()
1029 .ensure_capability(Capability::MarginalPrice)
1030 .is_err());
1031
1032 let engine_accounts = pool_state
1034 .adapter_contract
1035 .engine
1036 .state
1037 .clone()
1038 .get_account_storage()
1039 .expect("Failed to get account storage");
1040 for token in pool_state.tokens.clone() {
1041 let account = engine_accounts
1042 .get_account_info(&bytes_to_address(&token).unwrap())
1043 .unwrap();
1044 assert_eq!(account.balance, U256::from(0));
1045 assert_eq!(account.nonce, 0u64);
1046 assert_eq!(account.code_hash, KECCAK_EMPTY);
1047 assert!(account.code.is_some());
1048 }
1049
1050 let external_account = engine_accounts
1052 .get_account_info(&EXTERNAL_ACCOUNT)
1053 .unwrap();
1054 assert_eq!(external_account.balance, U256::from(*MAX_BALANCE));
1055 assert_eq!(external_account.nonce, 0u64);
1056 assert_eq!(external_account.code_hash, KECCAK_EMPTY);
1057 assert!(external_account.code.is_none());
1058 }
1059
1060 #[tokio::test]
1061 async fn test_get_amount_out() -> Result<(), Box<dyn std::error::Error>> {
1062 let pool_state = setup_pool_state().await;
1063
1064 let result = pool_state
1065 .get_amount_out(BigUint::from_str("1000000000000000000").unwrap(), &dai(), &bal())
1066 .unwrap();
1067 let new_state = result
1068 .new_state
1069 .as_any()
1070 .downcast_ref::<EVMPoolState<PreCachedDB>>()
1071 .unwrap();
1072 assert_eq!(result.amount, BigUint::from_str("137780051463393923").unwrap());
1073 assert_ne!(new_state.spot_prices, pool_state.spot_prices);
1074 assert!(pool_state
1075 .block_lasting_overwrites
1076 .is_empty());
1077 Ok(())
1078 }
1079
1080 #[tokio::test]
1081 async fn test_sequential_get_amount_outs() {
1082 let pool_state = setup_pool_state().await;
1083
1084 let result = pool_state
1085 .get_amount_out(BigUint::from_str("1000000000000000000").unwrap(), &dai(), &bal())
1086 .unwrap();
1087 let new_state = result
1088 .new_state
1089 .as_any()
1090 .downcast_ref::<EVMPoolState<PreCachedDB>>()
1091 .unwrap();
1092 assert_eq!(result.amount, BigUint::from_str("137780051463393923").unwrap());
1093 assert_ne!(new_state.spot_prices, pool_state.spot_prices);
1094
1095 let new_result = new_state
1096 .get_amount_out(BigUint::from_str("1000000000000000000").unwrap(), &dai(), &bal())
1097 .unwrap();
1098 let new_state_second_swap = new_result
1099 .new_state
1100 .as_any()
1101 .downcast_ref::<EVMPoolState<PreCachedDB>>()
1102 .unwrap();
1103
1104 assert_eq!(new_result.amount, BigUint::from_str("136964651490065626").unwrap());
1105 assert_ne!(new_state_second_swap.spot_prices, new_state.spot_prices);
1106 }
1107
1108 #[tokio::test]
1109 async fn test_get_amount_out_dust() {
1110 let pool_state = setup_pool_state().await;
1111
1112 let result = pool_state
1113 .get_amount_out(BigUint::one(), &dai(), &bal())
1114 .unwrap();
1115
1116 let _ = result
1117 .new_state
1118 .as_any()
1119 .downcast_ref::<EVMPoolState<PreCachedDB>>()
1120 .unwrap();
1121 assert_eq!(result.amount, BigUint::ZERO);
1122 }
1123
1124 #[tokio::test]
1125 async fn test_get_amount_out_sell_limit() {
1126 let pool_state = setup_pool_state().await;
1127
1128 let result = pool_state.get_amount_out(
1129 BigUint::from_str("100379494253364362835").unwrap(),
1131 &dai(),
1132 &bal(),
1133 );
1134
1135 assert!(result.is_err());
1136
1137 match result {
1138 Err(SimulationError::InvalidInput(msg1, amount_out_result)) => {
1139 assert_eq!(msg1, "Sell amount exceeds limit 100279494253364362835");
1140 assert!(amount_out_result.is_some());
1141 }
1142 _ => panic!("Test failed: was expecting an Err(SimulationError::RetryDifferentInput(_, _)) value"),
1143 }
1144 }
1145
1146 #[tokio::test]
1147 async fn test_get_amount_limits() {
1148 let pool_state = setup_pool_state().await;
1149
1150 let overwrites = pool_state
1151 .get_overwrites(
1152 vec![
1153 bytes_to_address(&pool_state.tokens[0]).unwrap(),
1154 bytes_to_address(&pool_state.tokens[1]).unwrap(),
1155 ],
1156 *MAX_BALANCE / U256::from(100),
1157 )
1158 .unwrap();
1159 let (dai_limit, _) = pool_state
1160 .get_amount_limits(vec![dai_addr(), bal_addr()], Some(overwrites.clone()))
1161 .unwrap();
1162 assert_eq!(dai_limit, U256::from_str("100279494253364362835").unwrap());
1163
1164 let (bal_limit, _) = pool_state
1165 .get_amount_limits(
1166 vec![
1167 bytes_to_address(&pool_state.tokens[1]).unwrap(),
1168 bytes_to_address(&pool_state.tokens[0]).unwrap(),
1169 ],
1170 Some(overwrites),
1171 )
1172 .unwrap();
1173 assert_eq!(bal_limit, U256::from_str("13997408640689987484").unwrap());
1174 }
1175
1176 #[tokio::test]
1177 async fn test_set_spot_prices() {
1178 let mut pool_state = setup_pool_state().await;
1179
1180 pool_state
1181 .set_spot_prices(
1182 &vec![bal(), dai()]
1183 .into_iter()
1184 .map(|t| (t.address.clone(), t))
1185 .collect(),
1186 )
1187 .unwrap();
1188
1189 let dai_bal_spot_price = pool_state
1190 .spot_prices
1191 .get(&(
1192 bytes_to_address(&pool_state.tokens[0]).unwrap(),
1193 bytes_to_address(&pool_state.tokens[1]).unwrap(),
1194 ))
1195 .unwrap();
1196 let bal_dai_spot_price = pool_state
1197 .spot_prices
1198 .get(&(
1199 bytes_to_address(&pool_state.tokens[1]).unwrap(),
1200 bytes_to_address(&pool_state.tokens[0]).unwrap(),
1201 ))
1202 .unwrap();
1203 assert_eq!(dai_bal_spot_price, &0.137_778_914_319_047_9);
1204 assert_eq!(bal_dai_spot_price, &7.071_503_245_428_246);
1205 }
1206
1207 #[tokio::test]
1208 async fn test_set_spot_prices_without_capability() {
1209 let mut pool_state = setup_pool_state().await;
1211
1212 pool_state
1213 .capabilities
1214 .remove(&Capability::PriceFunction);
1215
1216 pool_state
1217 .set_spot_prices(
1218 &vec![bal(), dai()]
1219 .into_iter()
1220 .map(|t| (t.address.clone(), t))
1221 .collect(),
1222 )
1223 .unwrap();
1224
1225 let dai_bal_spot_price = pool_state
1226 .spot_prices
1227 .get(&(
1228 bytes_to_address(&pool_state.tokens[0]).unwrap(),
1229 bytes_to_address(&pool_state.tokens[1]).unwrap(),
1230 ))
1231 .unwrap();
1232 let bal_dai_spot_price = pool_state
1233 .spot_prices
1234 .get(&(
1235 bytes_to_address(&pool_state.tokens[1]).unwrap(),
1236 bytes_to_address(&pool_state.tokens[0]).unwrap(),
1237 ))
1238 .unwrap();
1239 assert_eq!(dai_bal_spot_price, &0.13736685496467538);
1240 assert_eq!(bal_dai_spot_price, &7.050354297665408);
1241 }
1242
1243 #[tokio::test]
1244 async fn test_get_balance_overwrites_with_component_balances() {
1245 let pool_state: EVMPoolState<PreCachedDB> = setup_pool_state().await;
1246
1247 let overwrites = pool_state
1248 .get_balance_overwrites()
1249 .unwrap();
1250
1251 let dai_address = dai_addr();
1252 let bal_address = bal_addr();
1253 assert!(overwrites.contains_key(&dai_address));
1254 assert!(overwrites.contains_key(&bal_address));
1255 }
1256
1257 #[tokio::test]
1258 async fn test_get_balance_overwrites_with_contract_balances() {
1259 let mut pool_state: EVMPoolState<PreCachedDB> = setup_pool_state().await;
1260
1261 let contract_address =
1262 Address::from_str("0xBA12222222228d8Ba445958a75a0704d566BF2C8").unwrap();
1263
1264 pool_state.balances.clear();
1266 pool_state.balance_owner = None;
1267
1268 let dai_address = dai_addr();
1270 let bal_address = bal_addr();
1271 pool_state.contract_balances = HashMap::from([(
1272 contract_address,
1273 HashMap::from([
1274 (dai_address, U256::from_str("7500000000000000000000").unwrap()), (bal_address, U256::from_str("1500000000000000000000").unwrap()), ]),
1277 )]);
1278
1279 let overwrites = pool_state
1280 .get_balance_overwrites()
1281 .unwrap();
1282
1283 assert!(overwrites.contains_key(&dai_address));
1284 assert!(overwrites.contains_key(&bal_address));
1285 }
1286
1287 #[tokio::test]
1288 async fn test_balance_merging_during_delta_transition() {
1289 use std::str::FromStr;
1290
1291 let mut pool_state = setup_pool_state().await;
1292 let pool_id = pool_state.id.clone();
1293
1294 let dai_addr = dai_addr();
1297 let bal_addr = bal_addr();
1298 let new_token = Address::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); pool_state.balances.clear();
1302 pool_state
1303 .balances
1304 .insert(dai_addr, U256::from(1000000000u64));
1305 pool_state
1306 .balances
1307 .insert(bal_addr, U256::from(2000000000u64));
1308 pool_state
1309 .balances
1310 .insert(new_token, U256::from(3000000000u64));
1311
1312 let mut tokens = HashMap::new();
1314 tokens.insert(dai().address.clone(), dai());
1315 tokens.insert(bal().address.clone(), bal());
1316
1317 let mut component_balances = HashMap::new();
1319 let mut delta_balances = HashMap::new();
1320 delta_balances.insert(dai().address.clone(), Bytes::from(vec![0x77, 0x35, 0x94, 0x00])); component_balances.insert(pool_id.clone(), delta_balances);
1323
1324 let balances = Balances { component_balances, account_balances: HashMap::new() };
1325
1326 let initial_balance_count = pool_state.balances.len();
1328 assert_eq!(initial_balance_count, 3);
1329
1330 pool_state
1332 .update_pool_state(&tokens, &balances)
1333 .unwrap();
1334
1335 assert_eq!(
1337 pool_state.balances.len(),
1338 3,
1339 "All balances should be preserved after delta transition"
1340 );
1341 assert!(
1342 pool_state
1343 .balances
1344 .contains_key(&dai_addr),
1345 "DAI balance should be present"
1346 );
1347 assert!(
1348 pool_state
1349 .balances
1350 .contains_key(&bal_addr),
1351 "BAL balance should be present"
1352 );
1353 assert!(
1354 pool_state
1355 .balances
1356 .contains_key(&new_token),
1357 "New token balance should be preserved from before delta"
1358 );
1359
1360 assert_eq!(
1362 pool_state.balances[&dai_addr],
1363 U256::from(2000000000u64),
1364 "DAI balance should be updated"
1365 );
1366
1367 assert_eq!(
1369 pool_state.balances[&bal_addr],
1370 U256::from(2000000000u64),
1371 "BAL balance should be unchanged"
1372 );
1373 assert_eq!(
1374 pool_state.balances[&new_token],
1375 U256::from(3000000000u64),
1376 "New token balance should be unchanged"
1377 );
1378 }
1379
1380 #[tokio::test]
1381 async fn test_delta_transition_updates_block_overrides() {
1382 let mut pool_state = setup_pool_state().await;
1383 pool_state.manual_updates = true;
1384 pool_state.block_overrides = None;
1385
1386 let delta = ProtocolStateDelta {
1387 component_id: pool_state.id.clone(),
1388 updated_attributes: HashMap::from([
1389 ("override_block_number".to_string(), Bytes::from(123_u64.to_be_bytes().to_vec())),
1390 (
1391 "override_block_timestamp".to_string(),
1392 Bytes::from(456_u64.to_be_bytes().to_vec()),
1393 ),
1394 ]),
1395 deleted_attributes: HashSet::new(),
1396 };
1397
1398 pool_state
1399 .delta_transition(delta, &HashMap::new(), &Balances::default())
1400 .unwrap();
1401
1402 assert_eq!(
1403 pool_state.block_overrides,
1404 Some(BlockEnvOverrides { number: Some(123), timestamp: Some(456) })
1405 );
1406 }
1407
1408 #[tokio::test]
1409 async fn test_delta_transition_updates_partial_block_overrides() {
1410 let mut pool_state = setup_pool_state().await;
1411 pool_state.manual_updates = true;
1412 pool_state.block_overrides =
1413 Some(BlockEnvOverrides { number: Some(123), timestamp: Some(456) });
1414
1415 let delta = ProtocolStateDelta {
1416 component_id: pool_state.id.clone(),
1417 updated_attributes: HashMap::from([(
1418 "override_block_number".to_string(),
1419 Bytes::from(789_u64.to_be_bytes().to_vec()),
1420 )]),
1421 deleted_attributes: HashSet::new(),
1422 };
1423
1424 pool_state
1425 .delta_transition(delta, &HashMap::new(), &Balances::default())
1426 .unwrap();
1427
1428 assert_eq!(
1429 pool_state.block_overrides,
1430 Some(BlockEnvOverrides { number: Some(789), timestamp: Some(456) })
1431 );
1432 }
1433
1434 #[test]
1435 fn should_not_panic_at_typetag_deserialize() {
1436 let deserialized: Result<Box<dyn ProtocolSim>, _> = serde_json::from_str(
1437 r#"{"protocol":"EVMPoolState","state":{"reserve_0":1,"reserve_1":2}}"#,
1438 );
1439
1440 assert!(deserialized.is_err());
1441 }
1442}