1#![allow(deprecated)]
2use std::{
3 any::Any,
4 collections::{HashMap, HashSet},
5 fmt::{self, Debug},
6 str::FromStr,
7 time::{SystemTime, UNIX_EPOCH},
8};
9
10use alloy::primitives::{Address, U256};
11use itertools::Itertools;
12use num_bigint::BigUint;
13use revm::DatabaseRef;
14use serde::{Deserialize, Serialize};
15use tokio::sync::watch;
16use tracing::{debug, warn};
17use tycho_common::{
18 dto::ProtocolStateDelta,
19 models::token::Token,
20 simulation::{
21 errors::{SimulationError, TransitionError},
22 protocol_sim::{Balances, GetAmountOutResult, ProtocolSim},
23 },
24 Bytes,
25};
26
27use super::{
28 constants::{EXTERNAL_ACCOUNT, MAX_BALANCE},
29 erc20_token::{Overwrites, TokenProxyOverwriteFactory},
30 models::Capability,
31 tycho_simulation_contract::TychoSimulationContract,
32};
33use crate::evm::{
34 engine_db::{engine_db_interface::EngineDatabaseInterface, tycho_db::PreCachedDB},
35 override_stream::{FailurePolicy, OverrideSnapshot},
36 protocol::{
37 u256_num::{u256_to_biguint, u256_to_f64},
38 utils::bytes_to_address,
39 },
40 simulation::BlockEnvOverrides,
41};
42
43#[derive(Clone)]
44pub struct EVMPoolState<D: EngineDatabaseInterface + Clone + Debug>
45where
46 <D as DatabaseRef>::Error: Debug,
47 <D as EngineDatabaseInterface>::Error: Debug,
48{
49 id: String,
51 pub tokens: Vec<Bytes>,
53 balances: HashMap<Address, U256>,
55 #[deprecated(note = "Use contract_balances instead")]
59 balance_owner: Option<Address>,
60 spot_prices: HashMap<(Address, Address), f64>,
62 capabilities: HashSet<Capability>,
64 block_lasting_overwrites: HashMap<Address, Overwrites>,
67 involved_contracts: HashSet<Address>,
69 contract_balances: HashMap<Address, HashMap<Address, U256>>,
71 manual_updates: bool,
75 spot_price_caller: Option<Address>,
78 adapter_contract: TychoSimulationContract<D>,
80 disable_overwrite_tokens: HashSet<Address>,
82 self_contained_tokens: HashSet<Address>,
90 block_overrides: Option<BlockEnvOverrides>,
92 live_overrides: Option<watch::Receiver<OverrideSnapshot>>,
99}
100
101impl<D> Debug for EVMPoolState<D>
102where
103 D: EngineDatabaseInterface + Clone + Debug,
104 <D as DatabaseRef>::Error: Debug,
105 <D as EngineDatabaseInterface>::Error: Debug,
106{
107 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 f.debug_struct("EVMPoolState")
109 .field("id", &self.id)
110 .field("tokens", &self.tokens)
111 .field("balances", &self.balances)
112 .field("involved_contracts", &self.involved_contracts)
113 .field("contract_balances", &self.contract_balances)
114 .finish_non_exhaustive()
115 }
116}
117
118impl<D> EVMPoolState<D>
119where
120 D: EngineDatabaseInterface + Clone + Debug + 'static,
121 <D as DatabaseRef>::Error: Debug,
122 <D as EngineDatabaseInterface>::Error: Debug,
123{
124 #[allow(clippy::too_many_arguments)]
129 pub fn new(
130 id: String,
131 tokens: Vec<Bytes>,
132 component_balances: HashMap<Address, U256>,
133 balance_owner: Option<Address>,
134 contract_balances: HashMap<Address, HashMap<Address, U256>>,
135 spot_prices: HashMap<(Address, Address), f64>,
136 capabilities: HashSet<Capability>,
137 block_lasting_overwrites: HashMap<Address, Overwrites>,
138 involved_contracts: HashSet<Address>,
139 manual_updates: bool,
140 adapter_contract: TychoSimulationContract<D>,
141 disable_overwrite_tokens: HashSet<Address>,
142 self_contained_tokens: HashSet<Address>,
143 block_overrides: Option<BlockEnvOverrides>,
144 spot_price_caller: Option<Address>,
145 ) -> Self {
146 Self {
147 id,
148 tokens,
149 balances: component_balances,
150 balance_owner,
151 spot_prices,
152 capabilities,
153 block_lasting_overwrites,
154 involved_contracts,
155 contract_balances,
156 manual_updates,
157 adapter_contract,
158 disable_overwrite_tokens,
159 self_contained_tokens,
160 block_overrides,
161 spot_price_caller,
162 live_overrides: None,
163 }
164 }
165
166 pub fn set_live_overrides(&mut self, receiver: watch::Receiver<OverrideSnapshot>) {
170 self.live_overrides = Some(receiver);
171 }
172
173 fn get_live_snapshot(&self) -> Option<OverrideSnapshot> {
183 let snapshot = self
184 .live_overrides
185 .as_ref()
186 .map(|receiver| receiver.borrow().clone())?;
187 let now = SystemTime::now()
188 .duration_since(UNIX_EPOCH)
189 .map(|elapsed| elapsed.as_secs())
190 .unwrap_or(0);
191 if snapshot.is_expired(now) {
192 return None;
193 }
194 Some(snapshot)
195 }
196
197 fn run_with_indexed_fallback<T>(
206 pool_id: &str,
207 operation: &str,
208 live_snapshot: Option<&OverrideSnapshot>,
209 mut simulate: impl FnMut(Option<&OverrideSnapshot>) -> Result<T, SimulationError>,
210 ) -> Result<T, SimulationError> {
211 let attempt = simulate(live_snapshot);
212 let Err(error) = &attempt else { return attempt };
213 if matches!(error, SimulationError::InvalidInput(..)) {
214 return attempt;
215 }
216 let Some(snapshot) = live_snapshot
217 .filter(|snapshot| snapshot.failure_policy == FailurePolicy::FallbackToIndexedState)
218 else {
219 return attempt;
220 };
221 debug!(
222 pool = %pool_id,
223 %error,
224 snapshot_block = ?snapshot.block_number,
225 snapshot_ts = ?snapshot.block_timestamp,
226 expires_at = ?snapshot.expires_at,
227 override_accounts = snapshot.storage.len(),
228 "{operation} failed with live overrides; retrying on indexed state"
229 );
230 let retry = simulate(None);
231 if let Err(retry_error) = &retry {
232 warn!(pool = %pool_id, %retry_error, "{operation} retry on indexed state also failed");
233 }
234 retry
235 }
236
237 fn block_env(&self, live: Option<&OverrideSnapshot>) -> Option<BlockEnvOverrides> {
241 let base = self.block_overrides.to_owned();
242 let Some(snapshot) = live else {
243 return base;
244 };
245 if snapshot.block_number.is_none() && snapshot.block_timestamp.is_none() {
246 return base;
247 }
248 let mut overrides = base.unwrap_or_default();
249 if snapshot.block_number.is_some() {
250 overrides.number = snapshot.block_number;
251 }
252 if snapshot.block_timestamp.is_some() {
253 overrides.timestamp = snapshot.block_timestamp;
254 }
255 Some(overrides)
256 }
257
258 fn ensure_capability(&self, capability: Capability) -> Result<(), SimulationError> {
269 if !self.capabilities.contains(&capability) {
270 return Err(SimulationError::FatalError(format!(
271 "capability {:?} not supported",
272 capability.to_string()
273 )));
274 }
275 Ok(())
276 }
277 pub fn set_spot_prices(
313 &mut self,
314 tokens: &HashMap<Bytes, Token>,
315 ) -> Result<(), SimulationError> {
316 let live_snapshot = self.get_live_snapshot();
319 let pool_id = self.id.clone();
320 Self::run_with_indexed_fallback(
321 &pool_id,
322 "Spot prices",
323 live_snapshot.as_ref(),
324 |snapshot| self.set_spot_prices_with(tokens, snapshot),
325 )
326 }
327
328 fn set_spot_prices_with(
331 &mut self,
332 tokens: &HashMap<Bytes, Token>,
333 live_snapshot: Option<&OverrideSnapshot>,
334 ) -> Result<(), SimulationError> {
335 let block_overrides = self.block_env(live_snapshot);
336 match self.ensure_capability(Capability::PriceFunction) {
337 Ok(_) => {
338 for [sell_token_address, buy_token_address] in self
339 .tokens
340 .iter()
341 .permutations(2)
342 .map(|p| [p[0], p[1]])
343 {
344 let sell_token_address = bytes_to_address(sell_token_address)?;
345 let buy_token_address = bytes_to_address(buy_token_address)?;
346
347 let overwrites = Some(self.get_overwrites(
348 vec![sell_token_address, buy_token_address],
349 *MAX_BALANCE / U256::from(100),
350 live_snapshot,
351 )?);
352
353 let (sell_amount_limit, _) = self.get_amount_limits(
354 vec![sell_token_address, buy_token_address],
355 overwrites.clone(),
356 block_overrides.clone(),
357 )?;
358 let price_result = self.adapter_contract.price(
359 &self.id,
360 sell_token_address,
361 buy_token_address,
362 vec![sell_amount_limit / U256::from(100)],
363 overwrites,
364 self.spot_price_caller,
365 block_overrides.clone(),
366 )?;
367
368 let price = if self
369 .capabilities
370 .contains(&Capability::ScaledPrice)
371 {
372 *price_result.first().ok_or_else(|| {
373 SimulationError::FatalError(
374 "Calculated price array is empty".to_string(),
375 )
376 })?
377 } else {
378 let unscaled_price = price_result.first().ok_or_else(|| {
379 SimulationError::FatalError(
380 "Calculated price array is empty".to_string(),
381 )
382 })?;
383 let sell_token_decimals = self.get_decimals(tokens, &sell_token_address)?;
384 let buy_token_decimals = self.get_decimals(tokens, &buy_token_address)?;
385 *unscaled_price * 10f64.powi(sell_token_decimals as i32) /
386 10f64.powi(buy_token_decimals as i32)
387 };
388
389 self.spot_prices
390 .insert((sell_token_address, buy_token_address), price);
391 }
392 }
393 Err(SimulationError::FatalError(_)) => {
394 for iter_tokens in self.tokens.iter().permutations(2) {
398 let t0 = bytes_to_address(iter_tokens[0])?;
399 let t1 = bytes_to_address(iter_tokens[1])?;
400
401 let overwrites = Some(self.get_overwrites(
402 vec![t0, t1],
403 *MAX_BALANCE / U256::from(100),
404 live_snapshot,
405 )?);
406
407 let x1 =
409 self.get_amount_limits(
410 vec![t0, t1],
411 overwrites.clone(),
412 block_overrides.clone(),
413 )?
414 .0 / U256::from(100);
415
416 let x2 = x1 + (x1 / U256::from(100));
419
420 let y1 = self
423 .adapter_contract
424 .swap(
425 &self.id,
426 t0,
427 t1,
428 false,
429 x1,
430 overwrites.clone(),
431 block_overrides.clone(),
432 )?
433 .0
434 .received_amount;
435
436 let y2 = self
439 .adapter_contract
440 .swap(&self.id, t0, t1, false, x2, overwrites, block_overrides.clone())?
441 .0
442 .received_amount;
443
444 let sell_token_decimals = self.get_decimals(tokens, &t0)?;
445 let buy_token_decimals = self.get_decimals(tokens, &t1)?;
446
447 let num = y2 - y1;
448 let den = x2 - x1;
449
450 let token_correction =
452 10f64.powi(sell_token_decimals as i32 - buy_token_decimals as i32);
453 let num_f64 = u256_to_f64(num)?;
454 let den_f64 = u256_to_f64(den)?;
455 if den_f64 == 0.0 {
456 return Err(SimulationError::FatalError(
457 "Failed to compute marginal price: denominator converted to 0".into(),
458 ));
459 }
460 let marginal_price = num_f64 / den_f64 * token_correction;
461
462 self.spot_prices
463 .insert((t0, t1), marginal_price);
464 }
465 }
466 Err(e) => return Err(e),
467 }
468
469 Ok(())
470 }
471
472 fn get_decimals(
473 &self,
474 tokens: &HashMap<Bytes, Token>,
475 sell_token_address: &Address,
476 ) -> Result<usize, SimulationError> {
477 tokens
478 .get(&Bytes::from(sell_token_address.as_slice()))
479 .map(|t| t.decimals as usize)
480 .ok_or_else(|| {
481 SimulationError::FatalError(format!(
482 "Failed to scale spot prices! Pool: {} Token 0x{:x} is not available!",
483 self.id, sell_token_address
484 ))
485 })
486 }
487
488 fn get_amount_limits(
505 &self,
506 tokens: Vec<Address>,
507 overwrites: Option<HashMap<Address, HashMap<U256, U256>>>,
508 block_overrides: Option<BlockEnvOverrides>,
509 ) -> Result<(U256, U256), SimulationError> {
510 let limits = self.adapter_contract.get_limits(
511 &self.id,
512 tokens[0],
513 tokens[1],
514 overwrites,
515 block_overrides,
516 )?;
517
518 Ok(limits)
519 }
520
521 fn update_pool_state(
532 &mut self,
533 tokens: &HashMap<Bytes, Token>,
534 balances: &Balances,
535 ) -> Result<(), SimulationError> {
536 self.adapter_contract
538 .engine
539 .clear_temp_storage()
540 .map_err(|err| {
541 SimulationError::FatalError(format!("Failed to clear temporary storage: {err:?}",))
542 })?;
543 self.block_lasting_overwrites.clear();
544
545 if let Some(bals) = balances
553 .component_balances
554 .get(&self.id)
555 {
556 for (token, bal) in bals {
559 let addr = bytes_to_address(token).map_err(|_| {
560 SimulationError::FatalError(format!(
561 "Invalid token address in balance update: {token:?}"
562 ))
563 })?;
564 self.balances
565 .insert(addr, U256::from_be_slice(bal));
566 }
567 }
568 for contract in &self.involved_contracts {
569 if let Some(bals) = balances
570 .account_balances
571 .get(&Bytes::from(contract.as_slice()))
572 {
573 let contract_entry = self
574 .contract_balances
575 .entry(*contract)
576 .or_default();
577 for (token, bal) in bals {
578 let addr = bytes_to_address(token).map_err(|_| {
579 SimulationError::FatalError(format!(
580 "Invalid token address in balance update: {token:?}"
581 ))
582 })?;
583 contract_entry.insert(addr, U256::from_be_slice(bal));
584 }
585 }
586 }
587
588 self.set_spot_prices(tokens)?;
590 Ok(())
591 }
592
593 fn get_overwrites(
594 &self,
595 tokens: Vec<Address>,
596 max_amount: U256,
597 live: Option<&OverrideSnapshot>,
598 ) -> Result<HashMap<Address, Overwrites>, SimulationError> {
599 let token_overwrites = self.get_token_overwrites(tokens, max_amount)?;
600
601 let mut merged_overwrites =
603 self.merge(self.block_lasting_overwrites.clone(), token_overwrites);
604
605 if let Some(live) = live {
607 if !live.storage.is_empty() {
608 merged_overwrites = self.merge(merged_overwrites, live.storage.as_ref().clone());
609 }
610 }
611
612 Ok(merged_overwrites)
613 }
614
615 fn get_token_overwrites(
616 &self,
617 tokens: Vec<Address>,
618 max_amount: U256,
619 ) -> Result<HashMap<Address, Overwrites>, SimulationError> {
620 let sell_token = &tokens[0].clone(); let mut res: Vec<HashMap<Address, Overwrites>> = Vec::new();
622 if !self
623 .capabilities
624 .contains(&Capability::TokenBalanceIndependent)
625 {
626 res.push(self.get_balance_overwrites()?);
627 }
628
629 let mut overwrites = TokenProxyOverwriteFactory::new(*sell_token, None);
630
631 overwrites.set_balance(max_amount, Address::from_slice(&*EXTERNAL_ACCOUNT.0));
632
633 overwrites.set_allowance(max_amount, self.adapter_contract.address, *EXTERNAL_ACCOUNT);
635
636 res.push(overwrites.get_overwrites());
637
638 for token in tokens.iter().skip(1) {
642 if self
643 .self_contained_tokens
644 .contains(token) &&
645 !self
646 .disable_overwrite_tokens
647 .contains(token)
648 {
649 let mut recipient = TokenProxyOverwriteFactory::new(*token, None);
650 recipient.set_balance(U256::ZERO, *EXTERNAL_ACCOUNT);
651 res.push(recipient.get_overwrites());
652 }
653 }
654
655 Ok(res
657 .into_iter()
658 .fold(HashMap::new(), |acc, overwrite| self.merge(acc, overwrite)))
659 }
660
661 fn get_balance_overwrites(&self) -> Result<HashMap<Address, Overwrites>, SimulationError> {
672 let mut balance_overwrites: HashMap<Address, Overwrites> = HashMap::new();
673
674 let address = match self.balance_owner {
676 Some(owner) => Some(owner),
677 None if !self.contract_balances.is_empty() => None,
678 None => Some(self.id.parse().map_err(|_| {
679 SimulationError::FatalError(
680 "Failed to get balance overwrites: Pool ID is not an address".into(),
681 )
682 })?),
683 };
684
685 if let Some(address) = address {
686 for (token, bal) in &self.balances {
689 let mut overwrites = TokenProxyOverwriteFactory::new(*token, None);
690 overwrites.set_balance(*bal, address);
691 if self
694 .self_contained_tokens
695 .contains(token)
696 {
697 overwrites.set_has_custom_approval(address);
698 }
699 balance_overwrites.extend(overwrites.get_overwrites());
700 }
701 }
702
703 for (contract, balances) in &self.contract_balances {
706 for (token, balance) in balances {
707 let mut overwrites = TokenProxyOverwriteFactory::new(*token, None);
708 overwrites.set_balance(*balance, *contract);
709 if self
712 .self_contained_tokens
713 .contains(token)
714 {
715 overwrites.set_has_custom_approval(*contract);
716 }
717 balance_overwrites.extend(overwrites.get_overwrites());
718 }
719 }
720
721 for token in &self.disable_overwrite_tokens {
723 balance_overwrites.remove(token);
724 }
725
726 Ok(balance_overwrites)
727 }
728
729 fn merge(
731 &self,
732 mut target: HashMap<Address, Overwrites>,
733 source: HashMap<Address, Overwrites>,
734 ) -> HashMap<Address, Overwrites> {
735 for (key, source_inner) in source {
736 target
737 .entry(key)
738 .or_default()
739 .extend(source_inner);
740 }
741
742 target
743 }
744
745 #[cfg(test)]
746 pub fn get_involved_contracts(&self) -> HashSet<Address> {
747 self.involved_contracts.clone()
748 }
749
750 #[cfg(test)]
751 pub fn get_manual_updates(&self) -> bool {
752 self.manual_updates
753 }
754
755 #[cfg(test)]
756 pub fn get_spot_price_caller(&self) -> Option<Address> {
757 self.spot_price_caller
758 }
759
760 fn get_amount_out_with(
763 &self,
764 amount_in: &BigUint,
765 token_in: &Token,
766 token_out: &Token,
767 live_snapshot: Option<&OverrideSnapshot>,
768 ) -> Result<GetAmountOutResult, SimulationError> {
769 let sell_token_address = bytes_to_address(&token_in.address)?;
770 let buy_token_address = bytes_to_address(&token_out.address)?;
771 let sell_amount = U256::from_be_slice(&amount_in.to_bytes_be());
772 let block_overrides = self.block_env(live_snapshot);
773 let overwrites = self.get_overwrites(
774 vec![sell_token_address, buy_token_address],
775 *MAX_BALANCE / U256::from(100),
776 live_snapshot,
777 )?;
778 let (sell_amount_limit, _) = self.get_amount_limits(
779 vec![sell_token_address, buy_token_address],
780 Some(overwrites.clone()),
781 block_overrides.clone(),
782 )?;
783 let (sell_amount_respecting_limit, sell_amount_exceeds_limit) = if self
784 .capabilities
785 .contains(&Capability::HardLimits) &&
786 sell_amount_limit < sell_amount
787 {
788 (sell_amount_limit, true)
789 } else {
790 (sell_amount, false)
791 };
792
793 let overwrites_with_sell_limit = self.get_overwrites(
794 vec![sell_token_address, buy_token_address],
795 sell_amount_limit,
796 live_snapshot,
797 )?;
798 let complete_overwrites = self.merge(overwrites, overwrites_with_sell_limit);
799
800 let (trade, state_changes) = self.adapter_contract.swap(
801 &self.id,
802 sell_token_address,
803 buy_token_address,
804 false,
805 sell_amount_respecting_limit,
806 Some(complete_overwrites),
807 block_overrides,
808 )?;
809
810 let mut new_state = self.clone();
811
812 for (address, state_update) in state_changes {
814 if let Some(storage) = state_update.storage {
815 let block_overwrites = new_state
816 .block_lasting_overwrites
817 .entry(address)
818 .or_default();
819 for (slot, value) in storage {
820 let slot = U256::from_str(&slot.to_string()).map_err(|_| {
821 SimulationError::FatalError("Failed to decode slot index".to_string())
822 })?;
823 let value = U256::from_str(&value.to_string()).map_err(|_| {
824 SimulationError::FatalError("Failed to decode slot overwrite".to_string())
825 })?;
826 block_overwrites.insert(slot, value);
827 }
828 }
829 }
830
831 let tokens = HashMap::from([
833 (token_in.address.clone(), token_in.clone()),
834 (token_out.address.clone(), token_out.clone()),
835 ]);
836 let _ = new_state.set_spot_prices(&tokens);
837
838 let buy_amount = trade.received_amount;
839
840 if sell_amount_exceeds_limit {
841 return Err(SimulationError::InvalidInput(
842 format!("Sell amount exceeds limit {sell_amount_limit}"),
843 Some(GetAmountOutResult::new(
844 u256_to_biguint(buy_amount),
845 u256_to_biguint(trade.gas_used),
846 Box::new(new_state.clone()),
847 )),
848 ));
849 }
850 Ok(GetAmountOutResult::new(
851 u256_to_biguint(buy_amount),
852 u256_to_biguint(trade.gas_used),
853 Box::new(new_state.clone()),
854 ))
855 }
856
857 fn get_limits_with(
860 &self,
861 sell_token: &Bytes,
862 buy_token: &Bytes,
863 live_snapshot: Option<&OverrideSnapshot>,
864 ) -> Result<(BigUint, BigUint), SimulationError> {
865 let sell_token = bytes_to_address(sell_token)?;
866 let buy_token = bytes_to_address(buy_token)?;
867 let overwrites = self.get_overwrites(
868 vec![sell_token, buy_token],
869 *MAX_BALANCE / U256::from(100),
870 live_snapshot,
871 )?;
872 let limits = self.get_amount_limits(
873 vec![sell_token, buy_token],
874 Some(overwrites),
875 self.block_env(live_snapshot),
876 )?;
877 Ok((u256_to_biguint(limits.0), u256_to_biguint(limits.1)))
878 }
879
880 #[cfg(test)]
881 pub fn get_balance_owner(&self) -> Option<Address> {
882 self.balance_owner
883 }
884
885 pub fn get_balances(&self) -> &HashMap<Address, U256> {
887 &self.balances
888 }
889
890 #[cfg(test)]
891 pub fn get_block_overrides(&self) -> Option<BlockEnvOverrides> {
892 let live_snapshot = self.get_live_snapshot();
893 self.block_env(live_snapshot.as_ref())
894 }
895}
896
897impl<D> Serialize for EVMPoolState<D>
898where
899 D: EngineDatabaseInterface + Clone + Debug,
900 <D as DatabaseRef>::Error: Debug,
901 <D as EngineDatabaseInterface>::Error: Debug,
902{
903 fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
904 where
905 S: serde::Serializer,
906 {
907 Err(serde::ser::Error::custom("not supported due vm state deps"))
908 }
909}
910
911impl<'de, D> Deserialize<'de> for EVMPoolState<D>
912where
913 D: EngineDatabaseInterface + Clone + Debug,
914 <D as DatabaseRef>::Error: Debug,
915 <D as EngineDatabaseInterface>::Error: Debug,
916{
917 fn deserialize<De>(_deserializer: De) -> Result<Self, De::Error>
918 where
919 De: serde::Deserializer<'de>,
920 {
921 Err(serde::de::Error::custom("not supported due vm state deps"))
922 }
923}
924
925#[typetag::serialize]
926impl<D> ProtocolSim for EVMPoolState<D>
927where
928 D: EngineDatabaseInterface + Clone + Debug + 'static,
929 <D as DatabaseRef>::Error: Debug,
930 <D as EngineDatabaseInterface>::Error: Debug,
931{
932 fn fee(&self) -> f64 {
933 todo!()
934 }
935
936 fn spot_price(&self, base: &Token, quote: &Token) -> Result<f64, SimulationError> {
937 let base_address = bytes_to_address(&base.address)?;
938 let quote_address = bytes_to_address("e.address)?;
939 self.spot_prices
940 .get(&(base_address, quote_address))
941 .cloned()
942 .ok_or(SimulationError::FatalError(format!(
943 "Spot price not found for base token {base_address} and quote token {quote_address}"
944 )))
945 }
946
947 fn get_amount_out(
948 &self,
949 amount_in: BigUint,
950 token_in: &Token,
951 token_out: &Token,
952 ) -> Result<GetAmountOutResult, SimulationError> {
953 let live_snapshot = self.get_live_snapshot();
955 Self::run_with_indexed_fallback(&self.id, "Swap", live_snapshot.as_ref(), |snapshot| {
956 self.get_amount_out_with(&amount_in, token_in, token_out, snapshot)
957 })
958 }
959
960 fn get_limits(
961 &self,
962 sell_token: Bytes,
963 buy_token: Bytes,
964 ) -> Result<(BigUint, BigUint), SimulationError> {
965 let live_snapshot = self.get_live_snapshot();
966 Self::run_with_indexed_fallback(&self.id, "Limits", live_snapshot.as_ref(), |snapshot| {
967 self.get_limits_with(&sell_token, &buy_token, snapshot)
968 })
969 }
970
971 fn delta_transition(
972 &mut self,
973 delta: ProtocolStateDelta,
974 tokens: &HashMap<Bytes, Token>,
975 balances: &Balances,
976 ) -> Result<(), TransitionError> {
977 if let Some(block_number) = delta
978 .updated_attributes
979 .get("override_block_number")
980 {
981 let number = <[u8; 8]>::try_from(block_number.as_ref())
982 .map(u64::from_be_bytes)
983 .map_err(|_| {
984 TransitionError::DecodeError(
985 "override_block_number attribute must be an 8-byte big-endian u64"
986 .to_string(),
987 )
988 })?;
989 self.block_overrides
990 .get_or_insert_with(BlockEnvOverrides::default)
991 .number = Some(number);
992 }
993
994 if let Some(block_timestamp) = delta
995 .updated_attributes
996 .get("override_block_timestamp")
997 {
998 let timestamp = <[u8; 8]>::try_from(block_timestamp.as_ref())
999 .map(u64::from_be_bytes)
1000 .map_err(|_| {
1001 TransitionError::DecodeError(
1002 "override_block_timestamp attribute must be an 8-byte big-endian u64"
1003 .to_string(),
1004 )
1005 })?;
1006 self.block_overrides
1007 .get_or_insert_with(BlockEnvOverrides::default)
1008 .timestamp = Some(timestamp);
1009 }
1010
1011 if self.manual_updates {
1012 if let Some(marker) = delta
1014 .updated_attributes
1015 .get("update_marker")
1016 {
1017 if !marker.is_empty() && marker[0] != 0 {
1019 self.update_pool_state(tokens, balances)?;
1020 }
1021 }
1022 } else {
1023 self.update_pool_state(tokens, balances)?;
1024 }
1025
1026 Ok(())
1027 }
1028
1029 fn query_pool_swap(
1030 &self,
1031 params: &tycho_common::simulation::protocol_sim::QueryPoolSwapParams,
1032 ) -> Result<tycho_common::simulation::protocol_sim::PoolSwap, SimulationError> {
1033 crate::evm::query_pool_swap::query_pool_swap(self, params)
1034 }
1035
1036 fn clone_box(&self) -> Box<dyn ProtocolSim> {
1037 Box::new(self.clone())
1038 }
1039
1040 fn as_any(&self) -> &dyn Any {
1041 self
1042 }
1043
1044 fn as_any_mut(&mut self) -> &mut dyn Any {
1045 self
1046 }
1047
1048 fn eq(&self, other: &dyn ProtocolSim) -> bool {
1049 if let Some(other_state) = other
1050 .as_any()
1051 .downcast_ref::<EVMPoolState<PreCachedDB>>()
1052 {
1053 self.id == other_state.id
1054 } else {
1055 false
1056 }
1057 }
1058
1059 fn typetag_deserialize(&self) {
1061 unreachable!("Only to catch missing typetag attribute on impl blocks. Not called.")
1063 }
1064}
1065
1066#[cfg(test)]
1067mod tests {
1068 use std::default::Default;
1069
1070 use num_traits::One;
1071 use revm::{
1072 primitives::KECCAK_EMPTY,
1073 state::{AccountInfo, Bytecode},
1074 };
1075 use serde_json::Value;
1076 use tycho_client::feed::BlockHeader;
1077 use tycho_common::models::Chain;
1078
1079 use super::*;
1080 use crate::evm::{
1081 engine_db::{create_engine, SHARED_TYCHO_DB},
1082 protocol::vm::{
1083 constants::{BALANCER_V2, ERC20_PROXY_BYTECODE},
1084 state_builder::EVMPoolStateBuilder,
1085 },
1086 simulation::SimulationEngine,
1087 tycho_models::AccountUpdate,
1088 };
1089
1090 fn dai() -> Token {
1091 Token::new(
1092 &Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(),
1093 "DAI",
1094 18,
1095 0,
1096 &[Some(10_000)],
1097 Chain::Ethereum,
1098 100,
1099 )
1100 }
1101
1102 fn bal() -> Token {
1103 Token::new(
1104 &Bytes::from_str("0xba100000625a3754423978a60c9317c58a424e3d").unwrap(),
1105 "BAL",
1106 18,
1107 0,
1108 &[Some(10_000)],
1109 Chain::Ethereum,
1110 100,
1111 )
1112 }
1113
1114 fn dai_addr() -> Address {
1115 bytes_to_address(&dai().address).unwrap()
1116 }
1117
1118 fn bal_addr() -> Address {
1119 bytes_to_address(&bal().address).unwrap()
1120 }
1121
1122 async fn setup_pool_state() -> EVMPoolState<PreCachedDB> {
1123 let data_str = include_str!("assets/balancer_contract_storage_block_20463609.json");
1124 let data: Value = serde_json::from_str(data_str).expect("Failed to parse JSON");
1125
1126 let accounts: Vec<AccountUpdate> = serde_json::from_value(data["accounts"].clone())
1127 .expect("Expected accounts to match AccountUpdate structure");
1128
1129 let db = SHARED_TYCHO_DB.clone();
1130 let engine: SimulationEngine<_> = create_engine(db.clone(), false).unwrap();
1131
1132 let block = BlockHeader {
1133 number: 20463609,
1134 hash: Bytes::from_str(
1135 "0x4315fd1afc25cc2ebc72029c543293f9fd833eeb305e2e30159459c827733b1b",
1136 )
1137 .unwrap(),
1138 timestamp: 1722875891,
1139 ..Default::default()
1140 };
1141
1142 for account in accounts.clone() {
1143 engine
1144 .state
1145 .init_account(
1146 account.address,
1147 AccountInfo {
1148 balance: account.balance.unwrap_or_default(),
1149 nonce: 0u64,
1150 code_hash: KECCAK_EMPTY,
1151 code: account
1152 .code
1153 .clone()
1154 .map(|arg0: Vec<u8>| Bytecode::new_raw(arg0.into())),
1155 },
1156 None,
1157 false,
1158 )
1159 .expect("Failed to initialize account");
1160 }
1161 db.update(accounts, Some(block))
1162 .unwrap();
1163
1164 let tokens = vec![dai().address, bal().address];
1165 for token in &tokens {
1166 engine
1167 .state
1168 .init_account(
1169 bytes_to_address(token).unwrap(),
1170 AccountInfo {
1171 balance: U256::from(0),
1172 nonce: 0,
1173 code_hash: KECCAK_EMPTY,
1174 code: Some(Bytecode::new_raw(ERC20_PROXY_BYTECODE.into())),
1175 },
1176 None,
1177 true,
1178 )
1179 .expect("Failed to initialize account");
1180 }
1181
1182 let block = BlockHeader {
1183 number: 18485417,
1184 hash: Bytes::from_str(
1185 "0x28d41d40f2ac275a4f5f621a636b9016b527d11d37d610a45ac3a821346ebf8c",
1186 )
1187 .expect("Invalid block hash"),
1188 timestamp: 0,
1189 ..Default::default()
1190 };
1191 db.update(vec![], Some(block.clone()))
1192 .unwrap();
1193
1194 let pool_id: String =
1195 "0x4626d81b3a1711beb79f4cecff2413886d461677000200000000000000000011".into();
1196
1197 let stateless_contracts = HashMap::from([(
1198 String::from("0x3de27efa2f1aa663ae5d458857e731c129069f29"),
1199 Some(Vec::new()),
1200 )]);
1201
1202 let balances = HashMap::from([
1203 (dai_addr(), U256::from_str("178754012737301807104").unwrap()),
1204 (bal_addr(), U256::from_str("91082987763369885696").unwrap()),
1205 ]);
1206 let adapter_address =
1207 Address::from_str("0xA2C5C98A892fD6656a7F39A2f63228C0Bc846270").unwrap();
1208
1209 EVMPoolStateBuilder::new(pool_id, tokens, adapter_address)
1210 .balances(balances)
1211 .balance_owner(Address::from_str("0xBA12222222228d8Ba445958a75a0704d566BF2C8").unwrap())
1212 .adapter_contract_bytecode(Bytecode::new_raw(BALANCER_V2.into()))
1213 .stateless_contracts(stateless_contracts)
1214 .build(SHARED_TYCHO_DB.clone())
1215 .await
1216 .expect("Failed to build pool state")
1217 }
1218
1219 #[tokio::test]
1220 async fn test_init() {
1221 SHARED_TYCHO_DB
1223 .clear()
1224 .expect("Failed to cleared SHARED TX");
1225 let pool_state = setup_pool_state().await;
1226
1227 let expected_capabilities = vec![
1228 Capability::SellSide,
1229 Capability::BuySide,
1230 Capability::PriceFunction,
1231 Capability::HardLimits,
1232 ]
1233 .into_iter()
1234 .collect::<HashSet<_>>();
1235
1236 let capabilities_adapter_contract = pool_state
1237 .adapter_contract
1238 .get_capabilities(
1239 &pool_state.id,
1240 bytes_to_address(&pool_state.tokens[0]).unwrap(),
1241 bytes_to_address(&pool_state.tokens[1]).unwrap(),
1242 )
1243 .unwrap();
1244
1245 assert_eq!(capabilities_adapter_contract, expected_capabilities.clone());
1246
1247 let capabilities_state = pool_state.clone().capabilities;
1248
1249 assert_eq!(capabilities_state, expected_capabilities.clone());
1250
1251 for capability in expected_capabilities.clone() {
1252 assert!(pool_state
1253 .clone()
1254 .ensure_capability(capability)
1255 .is_ok());
1256 }
1257
1258 assert!(pool_state
1259 .clone()
1260 .ensure_capability(Capability::MarginalPrice)
1261 .is_err());
1262
1263 let engine_accounts = pool_state
1265 .adapter_contract
1266 .engine
1267 .state
1268 .clone()
1269 .get_account_storage()
1270 .expect("Failed to get account storage");
1271 for token in pool_state.tokens.clone() {
1272 let account = engine_accounts
1273 .get_account_info(&bytes_to_address(&token).unwrap())
1274 .unwrap();
1275 assert_eq!(account.balance, U256::from(0));
1276 assert_eq!(account.nonce, 0u64);
1277 assert_eq!(account.code_hash, KECCAK_EMPTY);
1278 assert!(account.code.is_some());
1279 }
1280
1281 let external_account = engine_accounts
1283 .get_account_info(&EXTERNAL_ACCOUNT)
1284 .unwrap();
1285 assert_eq!(external_account.balance, U256::from(*MAX_BALANCE));
1286 assert_eq!(external_account.nonce, 0u64);
1287 assert_eq!(external_account.code_hash, KECCAK_EMPTY);
1288 assert!(external_account.code.is_none());
1289 }
1290
1291 #[tokio::test]
1292 async fn test_get_amount_out() -> Result<(), Box<dyn std::error::Error>> {
1293 let pool_state = setup_pool_state().await;
1294
1295 let result = pool_state
1296 .get_amount_out(BigUint::from_str("1000000000000000000").unwrap(), &dai(), &bal())
1297 .unwrap();
1298 let new_state = result
1299 .new_state
1300 .as_any()
1301 .downcast_ref::<EVMPoolState<PreCachedDB>>()
1302 .unwrap();
1303 assert_eq!(result.amount, BigUint::from_str("137780051463393923").unwrap());
1304 assert_ne!(new_state.spot_prices, pool_state.spot_prices);
1305 assert!(pool_state
1306 .block_lasting_overwrites
1307 .is_empty());
1308 Ok(())
1309 }
1310
1311 #[tokio::test]
1312 async fn test_sequential_get_amount_outs() {
1313 let pool_state = setup_pool_state().await;
1314
1315 let result = pool_state
1316 .get_amount_out(BigUint::from_str("1000000000000000000").unwrap(), &dai(), &bal())
1317 .unwrap();
1318 let new_state = result
1319 .new_state
1320 .as_any()
1321 .downcast_ref::<EVMPoolState<PreCachedDB>>()
1322 .unwrap();
1323 assert_eq!(result.amount, BigUint::from_str("137780051463393923").unwrap());
1324 assert_ne!(new_state.spot_prices, pool_state.spot_prices);
1325
1326 let new_result = new_state
1327 .get_amount_out(BigUint::from_str("1000000000000000000").unwrap(), &dai(), &bal())
1328 .unwrap();
1329 let new_state_second_swap = new_result
1330 .new_state
1331 .as_any()
1332 .downcast_ref::<EVMPoolState<PreCachedDB>>()
1333 .unwrap();
1334
1335 assert_eq!(new_result.amount, BigUint::from_str("136964651490065626").unwrap());
1336 assert_ne!(new_state_second_swap.spot_prices, new_state.spot_prices);
1337 }
1338
1339 #[tokio::test]
1340 async fn test_get_amount_out_dust() {
1341 let pool_state = setup_pool_state().await;
1342
1343 let result = pool_state
1344 .get_amount_out(BigUint::one(), &dai(), &bal())
1345 .unwrap();
1346
1347 let _ = result
1348 .new_state
1349 .as_any()
1350 .downcast_ref::<EVMPoolState<PreCachedDB>>()
1351 .unwrap();
1352 assert_eq!(result.amount, BigUint::ZERO);
1353 }
1354
1355 #[tokio::test]
1356 async fn test_get_amount_out_sell_limit() {
1357 let pool_state = setup_pool_state().await;
1358
1359 let result = pool_state.get_amount_out(
1360 BigUint::from_str("100379494253364362835").unwrap(),
1362 &dai(),
1363 &bal(),
1364 );
1365
1366 assert!(result.is_err());
1367
1368 match result {
1369 Err(SimulationError::InvalidInput(msg1, amount_out_result)) => {
1370 assert_eq!(msg1, "Sell amount exceeds limit 100279494253364362835");
1371 assert!(amount_out_result.is_some());
1372 }
1373 _ => panic!("Test failed: was expecting an Err(SimulationError::RetryDifferentInput(_, _)) value"),
1374 }
1375 }
1376
1377 #[tokio::test]
1378 async fn test_get_amount_limits() {
1379 let pool_state = setup_pool_state().await;
1380
1381 let overwrites = pool_state
1382 .get_overwrites(
1383 vec![
1384 bytes_to_address(&pool_state.tokens[0]).unwrap(),
1385 bytes_to_address(&pool_state.tokens[1]).unwrap(),
1386 ],
1387 *MAX_BALANCE / U256::from(100),
1388 None,
1389 )
1390 .unwrap();
1391 let (dai_limit, _) = pool_state
1392 .get_amount_limits(
1393 vec![dai_addr(), bal_addr()],
1394 Some(overwrites.clone()),
1395 pool_state.block_env(None),
1396 )
1397 .unwrap();
1398 assert_eq!(dai_limit, U256::from_str("100279494253364362835").unwrap());
1399
1400 let (bal_limit, _) = pool_state
1401 .get_amount_limits(
1402 vec![
1403 bytes_to_address(&pool_state.tokens[1]).unwrap(),
1404 bytes_to_address(&pool_state.tokens[0]).unwrap(),
1405 ],
1406 Some(overwrites),
1407 pool_state.block_env(None),
1408 )
1409 .unwrap();
1410 assert_eq!(bal_limit, U256::from_str("13997408640689987484").unwrap());
1411 }
1412
1413 #[tokio::test]
1414 async fn test_set_spot_prices() {
1415 let mut pool_state = setup_pool_state().await;
1416
1417 pool_state
1418 .set_spot_prices(
1419 &vec![bal(), dai()]
1420 .into_iter()
1421 .map(|t| (t.address.clone(), t))
1422 .collect(),
1423 )
1424 .unwrap();
1425
1426 let dai_bal_spot_price = pool_state
1427 .spot_prices
1428 .get(&(
1429 bytes_to_address(&pool_state.tokens[0]).unwrap(),
1430 bytes_to_address(&pool_state.tokens[1]).unwrap(),
1431 ))
1432 .unwrap();
1433 let bal_dai_spot_price = pool_state
1434 .spot_prices
1435 .get(&(
1436 bytes_to_address(&pool_state.tokens[1]).unwrap(),
1437 bytes_to_address(&pool_state.tokens[0]).unwrap(),
1438 ))
1439 .unwrap();
1440 assert_eq!(dai_bal_spot_price, &0.137_778_914_319_047_9);
1441 assert_eq!(bal_dai_spot_price, &7.071_503_245_428_246);
1442 }
1443
1444 #[tokio::test]
1445 async fn test_set_spot_prices_without_capability() {
1446 let mut pool_state = setup_pool_state().await;
1448
1449 pool_state
1450 .capabilities
1451 .remove(&Capability::PriceFunction);
1452
1453 pool_state
1454 .set_spot_prices(
1455 &vec![bal(), dai()]
1456 .into_iter()
1457 .map(|t| (t.address.clone(), t))
1458 .collect(),
1459 )
1460 .unwrap();
1461
1462 let dai_bal_spot_price = pool_state
1463 .spot_prices
1464 .get(&(
1465 bytes_to_address(&pool_state.tokens[0]).unwrap(),
1466 bytes_to_address(&pool_state.tokens[1]).unwrap(),
1467 ))
1468 .unwrap();
1469 let bal_dai_spot_price = pool_state
1470 .spot_prices
1471 .get(&(
1472 bytes_to_address(&pool_state.tokens[1]).unwrap(),
1473 bytes_to_address(&pool_state.tokens[0]).unwrap(),
1474 ))
1475 .unwrap();
1476 assert_eq!(dai_bal_spot_price, &0.13736685496467538);
1477 assert_eq!(bal_dai_spot_price, &7.050354297665408);
1478 }
1479
1480 #[tokio::test]
1481 async fn test_get_balance_overwrites_with_component_balances() {
1482 let pool_state: EVMPoolState<PreCachedDB> = setup_pool_state().await;
1483
1484 let overwrites = pool_state
1485 .get_balance_overwrites()
1486 .unwrap();
1487
1488 let dai_address = dai_addr();
1489 let bal_address = bal_addr();
1490 assert!(overwrites.contains_key(&dai_address));
1491 assert!(overwrites.contains_key(&bal_address));
1492 }
1493
1494 #[tokio::test]
1495 async fn test_get_balance_overwrites_with_contract_balances() {
1496 let mut pool_state: EVMPoolState<PreCachedDB> = setup_pool_state().await;
1497
1498 let contract_address =
1499 Address::from_str("0xBA12222222228d8Ba445958a75a0704d566BF2C8").unwrap();
1500
1501 pool_state.balances.clear();
1503 pool_state.balance_owner = None;
1504
1505 let dai_address = dai_addr();
1507 let bal_address = bal_addr();
1508 pool_state.contract_balances = HashMap::from([(
1509 contract_address,
1510 HashMap::from([
1511 (dai_address, U256::from_str("7500000000000000000000").unwrap()), (bal_address, U256::from_str("1500000000000000000000").unwrap()), ]),
1514 )]);
1515
1516 let overwrites = pool_state
1517 .get_balance_overwrites()
1518 .unwrap();
1519
1520 assert!(overwrites.contains_key(&dai_address));
1521 assert!(overwrites.contains_key(&bal_address));
1522 }
1523
1524 #[tokio::test]
1525 async fn test_balance_merging_during_delta_transition() {
1526 use std::str::FromStr;
1527
1528 let mut pool_state = setup_pool_state().await;
1529 let pool_id = pool_state.id.clone();
1530
1531 let dai_addr = dai_addr();
1534 let bal_addr = bal_addr();
1535 let new_token = Address::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); pool_state.balances.clear();
1539 pool_state
1540 .balances
1541 .insert(dai_addr, U256::from(1000000000u64));
1542 pool_state
1543 .balances
1544 .insert(bal_addr, U256::from(2000000000u64));
1545 pool_state
1546 .balances
1547 .insert(new_token, U256::from(3000000000u64));
1548
1549 let mut tokens = HashMap::new();
1551 tokens.insert(dai().address.clone(), dai());
1552 tokens.insert(bal().address.clone(), bal());
1553
1554 let mut component_balances = HashMap::new();
1556 let mut delta_balances = HashMap::new();
1557 delta_balances.insert(dai().address.clone(), Bytes::from(vec![0x77, 0x35, 0x94, 0x00])); component_balances.insert(pool_id.clone(), delta_balances);
1560
1561 let balances = Balances { component_balances, account_balances: HashMap::new() };
1562
1563 let initial_balance_count = pool_state.balances.len();
1565 assert_eq!(initial_balance_count, 3);
1566
1567 pool_state
1569 .update_pool_state(&tokens, &balances)
1570 .unwrap();
1571
1572 assert_eq!(
1574 pool_state.balances.len(),
1575 3,
1576 "All balances should be preserved after delta transition"
1577 );
1578 assert!(
1579 pool_state
1580 .balances
1581 .contains_key(&dai_addr),
1582 "DAI balance should be present"
1583 );
1584 assert!(
1585 pool_state
1586 .balances
1587 .contains_key(&bal_addr),
1588 "BAL balance should be present"
1589 );
1590 assert!(
1591 pool_state
1592 .balances
1593 .contains_key(&new_token),
1594 "New token balance should be preserved from before delta"
1595 );
1596
1597 assert_eq!(
1599 pool_state.balances[&dai_addr],
1600 U256::from(2000000000u64),
1601 "DAI balance should be updated"
1602 );
1603
1604 assert_eq!(
1606 pool_state.balances[&bal_addr],
1607 U256::from(2000000000u64),
1608 "BAL balance should be unchanged"
1609 );
1610 assert_eq!(
1611 pool_state.balances[&new_token],
1612 U256::from(3000000000u64),
1613 "New token balance should be unchanged"
1614 );
1615 }
1616
1617 #[tokio::test]
1618 async fn test_delta_transition_updates_block_overrides() {
1619 let mut pool_state = setup_pool_state().await;
1620 pool_state.manual_updates = true;
1621 pool_state.block_overrides = None;
1622
1623 let delta = ProtocolStateDelta {
1624 component_id: pool_state.id.clone(),
1625 updated_attributes: HashMap::from([
1626 ("override_block_number".to_string(), Bytes::from(123_u64.to_be_bytes().to_vec())),
1627 (
1628 "override_block_timestamp".to_string(),
1629 Bytes::from(456_u64.to_be_bytes().to_vec()),
1630 ),
1631 ]),
1632 deleted_attributes: HashSet::new(),
1633 };
1634
1635 pool_state
1636 .delta_transition(delta, &HashMap::new(), &Balances::default())
1637 .unwrap();
1638
1639 assert_eq!(
1640 pool_state.block_overrides,
1641 Some(BlockEnvOverrides { number: Some(123), timestamp: Some(456) })
1642 );
1643 }
1644
1645 #[tokio::test]
1646 async fn test_delta_transition_updates_partial_block_overrides() {
1647 let mut pool_state = setup_pool_state().await;
1648 pool_state.manual_updates = true;
1649 pool_state.block_overrides =
1650 Some(BlockEnvOverrides { number: Some(123), timestamp: Some(456) });
1651
1652 let delta = ProtocolStateDelta {
1653 component_id: pool_state.id.clone(),
1654 updated_attributes: HashMap::from([(
1655 "override_block_number".to_string(),
1656 Bytes::from(789_u64.to_be_bytes().to_vec()),
1657 )]),
1658 deleted_attributes: HashSet::new(),
1659 };
1660
1661 pool_state
1662 .delta_transition(delta, &HashMap::new(), &Balances::default())
1663 .unwrap();
1664
1665 assert_eq!(
1666 pool_state.block_overrides,
1667 Some(BlockEnvOverrides { number: Some(789), timestamp: Some(456) })
1668 );
1669 }
1670
1671 #[tokio::test]
1676 async fn test_delta_transition_refreshes_both_balance_maps() {
1677 let mut pool_state = setup_pool_state().await;
1678 let vault = Address::from_str("0xBA12222222228d8Ba445958a75a0704d566BF2C8").unwrap();
1679 pool_state.manual_updates = false;
1682 pool_state.involved_contracts = HashSet::from([vault]);
1683 pool_state.contract_balances =
1684 HashMap::from([(vault, HashMap::from([(dai_addr(), U256::from(1u64))]))]);
1685
1686 let delta = ProtocolStateDelta {
1687 component_id: pool_state.id.clone(),
1688 updated_attributes: HashMap::new(),
1689 deleted_attributes: HashSet::new(),
1690 };
1691 let balances = Balances {
1692 component_balances: HashMap::new(),
1693 account_balances: HashMap::from([(
1694 Bytes::from(vault.as_slice()),
1695 HashMap::from([(dai().address.clone(), Bytes::from(42u64).lpad(32, 0))]),
1696 )]),
1697 };
1698
1699 let _ = pool_state.delta_transition(delta, &HashMap::new(), &balances);
1702
1703 assert_eq!(pool_state.contract_balances[&vault][&dai_addr()], U256::from(42u64));
1706 assert_eq!(
1708 pool_state.balances[&dai_addr()],
1709 U256::from_str("178754012737301807104").unwrap()
1710 );
1711 }
1712
1713 #[test]
1714 fn should_not_panic_at_typetag_deserialize() {
1715 let deserialized: Result<Box<dyn ProtocolSim>, _> = serde_json::from_str(
1716 r#"{"protocol":"EVMPoolState","state":{"reserve_0":1,"reserve_1":2}}"#,
1717 );
1718
1719 assert!(deserialized.is_err());
1720 }
1721
1722 #[tokio::test]
1725 async fn test_block_env_prefers_live_overrides() {
1726 let mut pool_state = setup_pool_state().await;
1727 pool_state.block_overrides =
1728 Some(BlockEnvOverrides { number: Some(100), timestamp: Some(1_000) });
1729
1730 assert_eq!(
1732 pool_state.block_env(None),
1733 Some(BlockEnvOverrides { number: Some(100), timestamp: Some(1_000) })
1734 );
1735
1736 let empty = OverrideSnapshot::default();
1738 assert_eq!(
1739 pool_state.block_env(Some(&empty)),
1740 Some(BlockEnvOverrides { number: Some(100), timestamp: Some(1_000) })
1741 );
1742
1743 let live = OverrideSnapshot {
1745 block_number: Some(200),
1746 block_timestamp: None,
1747 ..Default::default()
1748 };
1749 assert_eq!(
1750 pool_state.block_env(Some(&live)),
1751 Some(BlockEnvOverrides { number: Some(200), timestamp: Some(1_000) })
1752 );
1753
1754 pool_state.block_overrides = None;
1756 let live = OverrideSnapshot {
1757 block_number: Some(300),
1758 block_timestamp: Some(3_000),
1759 ..Default::default()
1760 };
1761 assert_eq!(
1762 pool_state.block_env(Some(&live)),
1763 Some(BlockEnvOverrides { number: Some(300), timestamp: Some(3_000) })
1764 );
1765 }
1766
1767 #[tokio::test]
1771 async fn test_get_overwrites_applies_live_storage() {
1772 let pool_state = setup_pool_state().await;
1773 let tokens = vec![dai_addr(), bal_addr()];
1774 let max = *MAX_BALANCE / U256::from(100);
1775
1776 let baseline = pool_state
1777 .get_overwrites(tokens.clone(), max, None)
1778 .unwrap();
1779
1780 let empty = OverrideSnapshot::default();
1782 assert_eq!(
1783 pool_state
1784 .get_overwrites(tokens.clone(), max, Some(&empty))
1785 .unwrap(),
1786 baseline
1787 );
1788
1789 let (conflict_addr, conflict_slot, baseline_val) = {
1791 let (addr, slots) = baseline
1792 .iter()
1793 .next()
1794 .expect("baseline has overwrites");
1795 let (slot, val) = slots
1796 .iter()
1797 .next()
1798 .expect("address has slots");
1799 (*addr, *slot, *val)
1800 };
1801 let sentinel = baseline_val + U256::from(1);
1802 let fresh = Address::from([0xAB; 20]);
1803 assert!(
1804 !baseline.contains_key(&fresh),
1805 "fresh address must originate from the live snapshot"
1806 );
1807
1808 let live = OverrideSnapshot {
1809 storage: std::sync::Arc::new(HashMap::from([
1810 (fresh, HashMap::from([(U256::from(7), U256::from(123))])),
1811 (conflict_addr, HashMap::from([(conflict_slot, sentinel)])),
1812 ])),
1813 ..Default::default()
1814 };
1815 let with_live = pool_state
1816 .get_overwrites(tokens, max, Some(&live))
1817 .unwrap();
1818
1819 assert_eq!(
1820 with_live
1821 .get(&fresh)
1822 .and_then(|slots| slots.get(&U256::from(7))),
1823 Some(&U256::from(123)),
1824 "live storage for a fresh contract must be merged in"
1825 );
1826 assert_eq!(
1827 with_live
1828 .get(&conflict_addr)
1829 .and_then(|slots| slots.get(&conflict_slot)),
1830 Some(&sentinel),
1831 "live override must win on slot conflict"
1832 );
1833 }
1834
1835 #[tokio::test]
1840 async fn test_get_live_snapshot_drops_expired() {
1841 let mut pool_state = setup_pool_state().await;
1842
1843 assert!(pool_state.get_live_snapshot().is_none());
1845
1846 let never =
1848 OverrideSnapshot { block_number: Some(1), expires_at: None, ..Default::default() };
1849 let (_never_tx, never_rx) = watch::channel(never);
1850 pool_state.set_live_overrides(never_rx);
1851 assert_eq!(
1852 pool_state
1853 .get_live_snapshot()
1854 .and_then(|snapshot| snapshot.block_number),
1855 Some(1)
1856 );
1857
1858 let fresh = OverrideSnapshot {
1860 block_number: Some(42),
1861 expires_at: Some(u64::MAX),
1862 ..Default::default()
1863 };
1864 let (_fresh_tx, fresh_rx) = watch::channel(fresh);
1865 pool_state.set_live_overrides(fresh_rx);
1866 assert_eq!(
1867 pool_state
1868 .get_live_snapshot()
1869 .and_then(|snapshot| snapshot.block_number),
1870 Some(42)
1871 );
1872
1873 let expired =
1875 OverrideSnapshot { block_number: Some(42), expires_at: Some(1), ..Default::default() };
1876 let (_expired_tx, expired_rx) = watch::channel(expired);
1877 pool_state.set_live_overrides(expired_rx);
1878 assert!(pool_state.get_live_snapshot().is_none());
1879 }
1880
1881 fn poison_snapshot(failure_policy: FailurePolicy) -> OverrideSnapshot {
1884 let vault: Address = "0xBA12222222228d8Ba445958a75a0704d566BF2C8"
1885 .parse()
1886 .unwrap();
1887 let poisoned_slots = (0u64..10)
1888 .map(|slot| (U256::from(slot), U256::MAX))
1889 .collect();
1890 OverrideSnapshot {
1891 storage: std::sync::Arc::new(HashMap::from([(vault, poisoned_slots)])),
1892 failure_policy,
1893 ..Default::default()
1894 }
1895 }
1896
1897 #[tokio::test]
1900 async fn test_failing_overrides_error_by_default() {
1901 let mut pool_state = setup_pool_state().await;
1902 let poison = poison_snapshot(FailurePolicy::Error);
1903 let (_tx, rx) = watch::channel(poison);
1904 pool_state.set_live_overrides(rx);
1905
1906 assert!(pool_state
1907 .get_amount_out(BigUint::from_str("1000000000000000000").unwrap(), &dai(), &bal())
1908 .is_err());
1909 }
1910
1911 #[tokio::test]
1915 async fn test_failing_overrides_fall_back_to_indexed_state() {
1916 let pool_state = setup_pool_state().await;
1917 let expected = pool_state
1918 .get_amount_out(BigUint::from_str("1000000000000000000").unwrap(), &dai(), &bal())
1919 .unwrap();
1920 let expected_limits = pool_state
1921 .get_limits(dai().address.clone(), bal().address.clone())
1922 .unwrap();
1923
1924 let mut pool_state = pool_state;
1925 let poison = poison_snapshot(FailurePolicy::FallbackToIndexedState);
1926 let (_tx, rx) = watch::channel(poison);
1927 pool_state.set_live_overrides(rx);
1928
1929 let result = pool_state
1930 .get_amount_out(BigUint::from_str("1000000000000000000").unwrap(), &dai(), &bal())
1931 .expect("must fall back to indexed state");
1932 assert_eq!(result.amount, expected.amount);
1933
1934 let limits = pool_state
1935 .get_limits(dai().address.clone(), bal().address.clone())
1936 .expect("limits must fall back to indexed state");
1937 assert_eq!(limits, expected_limits);
1938
1939 let tokens =
1940 HashMap::from([(dai().address.clone(), dai()), (bal().address.clone(), bal())]);
1941 pool_state
1942 .set_spot_prices(&tokens)
1943 .expect("spot prices must fall back to indexed state");
1944 }
1945}