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