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