1#![allow(deprecated)]
2use std::{
3 any::Any,
4 collections::{HashMap, HashSet},
5 fmt::{self, Debug},
6 str::FromStr,
7 sync::{PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard},
8 time::{SystemTime, UNIX_EPOCH},
9};
10
11use alloy::primitives::{Address, U256};
12use itertools::Itertools;
13use num_bigint::BigUint;
14use revm::DatabaseRef;
15use rustc_hash::FxHashMap;
16use serde::{Deserialize, Serialize};
17use tokio::sync::watch;
18use tracing::{debug, warn};
19use tycho_common::{
20 dto::ProtocolStateDelta,
21 models::token::Token,
22 simulation::{
23 errors::{SimulationError, TransitionError},
24 protocol_sim::{Balances, GetAmountOutResult, ProtocolSim},
25 },
26 Bytes,
27};
28
29use super::{
30 constants::{EXTERNAL_ACCOUNT, MAX_BALANCE},
31 erc20_token::{Overwrites, TokenProxyOverwriteFactory},
32 models::Capability,
33 tycho_simulation_contract::TychoSimulationContract,
34};
35use crate::evm::{
36 engine_db::{engine_db_interface::EngineDatabaseInterface, tycho_db::PreCachedDB},
37 override_stream::{FailurePolicy, OverrideSnapshot},
38 protocol::{
39 u256_num::{u256_to_biguint, u256_to_f64},
40 utils::bytes_to_address,
41 },
42 simulation::BlockEnvOverrides,
43};
44
45#[derive(Default)]
62struct DerivedCaches {
63 inner: RwLock<CacheState>,
64}
65
66#[derive(Clone, Default)]
67struct CacheState {
68 spot_prices: FxHashMap<(Address, Address), f64>,
71 limits: FxHashMap<(Address, Address), (U256, U256)>,
73 limit_context: Option<LimitContext>,
75}
76
77#[derive(Clone, Debug, PartialEq)]
80struct LimitContext {
81 block: Option<(u64, u64, Option<u32>)>,
83 block_env: Option<BlockEnvOverrides>,
84}
85
86impl DerivedCaches {
87 fn read(&self) -> RwLockReadGuard<'_, CacheState> {
88 self.inner
89 .read()
90 .unwrap_or_else(PoisonError::into_inner)
91 }
92
93 fn write(&self) -> RwLockWriteGuard<'_, CacheState> {
94 self.inner
95 .write()
96 .unwrap_or_else(PoisonError::into_inner)
97 }
98
99 fn get_spot_price(&self, key: (Address, Address)) -> Option<f64> {
100 self.read()
101 .spot_prices
102 .get(&key)
103 .copied()
104 }
105
106 fn insert_spot_price(&self, key: (Address, Address), price: f64) {
107 self.write()
108 .spot_prices
109 .insert(key, price);
110 }
111
112 fn get_limits(&self, key: (Address, Address), context: &LimitContext) -> Option<(U256, U256)> {
113 let state = self.read();
114 if state.limit_context.as_ref() != Some(context) {
115 return None;
116 }
117 state.limits.get(&key).copied()
118 }
119
120 fn insert_limits(&self, key: (Address, Address), limits: (U256, U256), context: LimitContext) {
121 let mut state = self.write();
122 if state.limit_context.as_ref() != Some(&context) {
123 state.limits.clear();
124 state.limit_context = Some(context);
125 }
126 state.limits.insert(key, limits);
127 }
128
129 fn invalidate(&self) {
132 let mut state = self.write();
133 state.spot_prices.clear();
134 state.limits.clear();
135 state.limit_context = None;
136 }
137}
138
139impl Clone for DerivedCaches {
140 fn clone(&self) -> Self {
142 Self { inner: RwLock::new(self.read().clone()) }
143 }
144}
145
146#[derive(Clone)]
147pub struct EVMPoolState<D: EngineDatabaseInterface + Clone + Debug>
148where
149 <D as DatabaseRef>::Error: Debug,
150 <D as EngineDatabaseInterface>::Error: Debug,
151{
152 id: String,
154 pub tokens: Vec<Bytes>,
156 balances: HashMap<Address, U256>,
158 #[deprecated(note = "Use contract_balances instead")]
162 balance_owner: Option<Address>,
163 caches: DerivedCaches,
171 capabilities: HashSet<Capability>,
173 block_lasting_overwrites: HashMap<Address, Overwrites>,
176 involved_contracts: HashSet<Address>,
178 contract_balances: HashMap<Address, HashMap<Address, U256>>,
180 manual_updates: bool,
184 spot_price_caller: Option<Address>,
187 adapter_contract: TychoSimulationContract<D>,
189 disable_overwrite_tokens: HashSet<Address>,
191 self_contained_tokens: HashSet<Address>,
199 block_overrides: Option<BlockEnvOverrides>,
201 live_overrides: Option<watch::Receiver<OverrideSnapshot>>,
208}
209
210impl<D> Debug for EVMPoolState<D>
211where
212 D: EngineDatabaseInterface + Clone + Debug,
213 <D as DatabaseRef>::Error: Debug,
214 <D as EngineDatabaseInterface>::Error: Debug,
215{
216 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217 f.debug_struct("EVMPoolState")
218 .field("id", &self.id)
219 .field("tokens", &self.tokens)
220 .field("balances", &self.balances)
221 .field("involved_contracts", &self.involved_contracts)
222 .field("contract_balances", &self.contract_balances)
223 .finish_non_exhaustive()
224 }
225}
226
227impl<D> EVMPoolState<D>
228where
229 D: EngineDatabaseInterface + Clone + Debug + 'static,
230 <D as DatabaseRef>::Error: Debug,
231 <D as EngineDatabaseInterface>::Error: Debug,
232{
233 #[allow(clippy::too_many_arguments)]
238 pub fn new(
239 id: String,
240 tokens: Vec<Bytes>,
241 component_balances: HashMap<Address, U256>,
242 balance_owner: Option<Address>,
243 contract_balances: HashMap<Address, HashMap<Address, U256>>,
244 spot_prices: HashMap<(Address, Address), f64>,
245 capabilities: HashSet<Capability>,
246 block_lasting_overwrites: HashMap<Address, Overwrites>,
247 involved_contracts: HashSet<Address>,
248 manual_updates: bool,
249 adapter_contract: TychoSimulationContract<D>,
250 disable_overwrite_tokens: HashSet<Address>,
251 self_contained_tokens: HashSet<Address>,
252 block_overrides: Option<BlockEnvOverrides>,
253 spot_price_caller: Option<Address>,
254 ) -> Self {
255 let caches = DerivedCaches::default();
256 caches.write().spot_prices = spot_prices.into_iter().collect();
257 Self {
258 id,
259 tokens,
260 balances: component_balances,
261 balance_owner,
262 caches,
263 capabilities,
264 block_lasting_overwrites,
265 involved_contracts,
266 contract_balances,
267 manual_updates,
268 adapter_contract,
269 disable_overwrite_tokens,
270 self_contained_tokens,
271 block_overrides,
272 spot_price_caller,
273 live_overrides: None,
274 }
275 }
276
277 pub fn set_live_overrides(&mut self, receiver: watch::Receiver<OverrideSnapshot>) {
281 self.live_overrides = Some(receiver);
282 self.caches.invalidate();
285 }
286
287 fn clone_invalidated(&self) -> Self {
293 Self {
294 id: self.id.clone(),
295 tokens: self.tokens.clone(),
296 balances: self.balances.clone(),
297 balance_owner: self.balance_owner,
298 caches: DerivedCaches::default(),
299 capabilities: self.capabilities.clone(),
300 block_lasting_overwrites: self.block_lasting_overwrites.clone(),
301 involved_contracts: self.involved_contracts.clone(),
302 contract_balances: self.contract_balances.clone(),
303 manual_updates: self.manual_updates,
304 adapter_contract: self.adapter_contract.clone(),
305 disable_overwrite_tokens: self.disable_overwrite_tokens.clone(),
306 self_contained_tokens: self.self_contained_tokens.clone(),
307 block_overrides: self.block_overrides.clone(),
308 spot_price_caller: self.spot_price_caller,
309 live_overrides: self.live_overrides.clone(),
310 }
311 }
312
313 fn current_engine_block(&self) -> Option<(u64, u64, Option<u32>)> {
316 self.adapter_contract
317 .engine
318 .state
319 .get_current_block()
320 .map(|block| (block.number, block.timestamp, block.partial_block_index))
321 }
322
323 fn get_live_snapshot(&self) -> Option<OverrideSnapshot> {
333 let snapshot = self
334 .live_overrides
335 .as_ref()
336 .map(|receiver| receiver.borrow().clone())?;
337 let now = SystemTime::now()
338 .duration_since(UNIX_EPOCH)
339 .map(|elapsed| elapsed.as_secs())
340 .unwrap_or(0);
341 if snapshot.is_expired(now) {
342 return None;
343 }
344 Some(snapshot)
345 }
346
347 fn run_with_indexed_fallback<T>(
356 pool_id: &str,
357 operation: &str,
358 live_snapshot: Option<&OverrideSnapshot>,
359 mut simulate: impl FnMut(Option<&OverrideSnapshot>) -> Result<T, SimulationError>,
360 ) -> Result<T, SimulationError> {
361 let attempt = simulate(live_snapshot);
362 let Err(error) = &attempt else { return attempt };
363 if matches!(error, SimulationError::InvalidInput(..)) {
364 return attempt;
365 }
366 let Some(snapshot) = live_snapshot
367 .filter(|snapshot| snapshot.failure_policy == FailurePolicy::FallbackToIndexedState)
368 else {
369 return attempt;
370 };
371 debug!(
372 pool = %pool_id,
373 %error,
374 snapshot_block = ?snapshot.block_number,
375 snapshot_ts = ?snapshot.block_timestamp,
376 expires_at = ?snapshot.expires_at,
377 override_accounts = snapshot.storage.len(),
378 "{operation} failed with live overrides; retrying on indexed state"
379 );
380 let retry = simulate(None);
381 if let Err(retry_error) = &retry {
382 warn!(pool = %pool_id, %retry_error, "{operation} retry on indexed state also failed");
383 }
384 retry
385 }
386
387 fn block_env(&self, live: Option<&OverrideSnapshot>) -> Option<BlockEnvOverrides> {
391 let base = self.block_overrides.to_owned();
392 let Some(snapshot) = live else {
393 return base;
394 };
395 if snapshot.block_number.is_none() && snapshot.block_timestamp.is_none() {
396 return base;
397 }
398 let mut overrides = base.unwrap_or_default();
399 if snapshot.block_number.is_some() {
400 overrides.number = snapshot.block_number;
401 }
402 if snapshot.block_timestamp.is_some() {
403 overrides.timestamp = snapshot.block_timestamp;
404 }
405 Some(overrides)
406 }
407
408 pub fn set_spot_prices(&self, tokens: &HashMap<Bytes, Token>) -> Result<(), SimulationError> {
444 let live_snapshot = self.get_live_snapshot();
447 Self::run_with_indexed_fallback(
448 &self.id,
449 "Spot prices",
450 live_snapshot.as_ref(),
451 |snapshot| self.set_spot_prices_with(tokens, snapshot),
452 )
453 }
454
455 fn compute_spot_price(
460 &self,
461 tokens: &HashMap<Bytes, Token>,
462 sell_token_address: Address,
463 buy_token_address: Address,
464 live_snapshot: Option<&OverrideSnapshot>,
465 ) -> Result<f64, SimulationError> {
466 let block_overrides = self.block_env(live_snapshot);
467 if self
468 .capabilities
469 .contains(&Capability::PriceFunction)
470 {
471 let overwrites = Some(self.get_overwrites(
472 vec![sell_token_address, buy_token_address],
473 *MAX_BALANCE / U256::from(100),
474 live_snapshot,
475 )?);
476
477 let (sell_amount_limit, _) = self.get_amount_limits(
478 vec![sell_token_address, buy_token_address],
479 overwrites.clone(),
480 block_overrides.clone(),
481 )?;
482 let price_result = self.adapter_contract.price(
483 &self.id,
484 sell_token_address,
485 buy_token_address,
486 vec![sell_amount_limit / U256::from(100)],
487 overwrites,
488 self.spot_price_caller,
489 block_overrides.clone(),
490 )?;
491
492 if self
493 .capabilities
494 .contains(&Capability::ScaledPrice)
495 {
496 Ok(*price_result.first().ok_or_else(|| {
497 SimulationError::FatalError("Calculated price array is empty".to_string())
498 })?)
499 } else {
500 let unscaled_price = price_result.first().ok_or_else(|| {
501 SimulationError::FatalError("Calculated price array is empty".to_string())
502 })?;
503 let sell_token_decimals = self.get_decimals(tokens, &sell_token_address)?;
504 let buy_token_decimals = self.get_decimals(tokens, &buy_token_address)?;
505 Ok(*unscaled_price * 10f64.powi(sell_token_decimals as i32) /
506 10f64.powi(buy_token_decimals as i32))
507 }
508 } else {
509 let overwrites = Some(self.get_overwrites(
512 vec![sell_token_address, buy_token_address],
513 *MAX_BALANCE / U256::from(100),
514 live_snapshot,
515 )?);
516
517 let x1 =
519 self.get_amount_limits(
520 vec![sell_token_address, buy_token_address],
521 overwrites.clone(),
522 block_overrides.clone(),
523 )?
524 .0 / U256::from(100);
525
526 let x2 = x1 + (x1 / U256::from(100));
528
529 let y1 = self
532 .adapter_contract
533 .swap(
534 &self.id,
535 sell_token_address,
536 buy_token_address,
537 false,
538 x1,
539 overwrites.clone(),
540 block_overrides.clone(),
541 )?
542 .0
543 .received_amount;
544
545 let y2 = self
548 .adapter_contract
549 .swap(
550 &self.id,
551 sell_token_address,
552 buy_token_address,
553 false,
554 x2,
555 overwrites,
556 block_overrides.clone(),
557 )?
558 .0
559 .received_amount;
560
561 let sell_token_decimals = self.get_decimals(tokens, &sell_token_address)?;
562 let buy_token_decimals = self.get_decimals(tokens, &buy_token_address)?;
563
564 let num = y2 - y1;
565 let den = x2 - x1;
566
567 let token_correction =
569 10f64.powi(sell_token_decimals as i32 - buy_token_decimals as i32);
570 let num_f64 = u256_to_f64(num)?;
571 let den_f64 = u256_to_f64(den)?;
572 if den_f64 == 0.0 {
573 return Err(SimulationError::FatalError(
574 "Failed to compute marginal price: denominator converted to 0".into(),
575 ));
576 }
577 Ok(num_f64 / den_f64 * token_correction)
578 }
579 }
580
581 fn set_spot_prices_with(
584 &self,
585 tokens: &HashMap<Bytes, Token>,
586 live_snapshot: Option<&OverrideSnapshot>,
587 ) -> Result<(), SimulationError> {
588 for [sell_token_address, buy_token_address] in self
589 .tokens
590 .iter()
591 .permutations(2)
592 .map(|p| [p[0], p[1]])
593 {
594 let sell_token_address = bytes_to_address(sell_token_address)?;
595 let buy_token_address = bytes_to_address(buy_token_address)?;
596
597 let price = self.compute_spot_price(
598 tokens,
599 sell_token_address,
600 buy_token_address,
601 live_snapshot,
602 )?;
603
604 self.caches
605 .insert_spot_price((sell_token_address, buy_token_address), price);
606 }
607
608 Ok(())
609 }
610
611 fn get_decimals(
612 &self,
613 tokens: &HashMap<Bytes, Token>,
614 sell_token_address: &Address,
615 ) -> Result<usize, SimulationError> {
616 tokens
617 .get(&Bytes::from(sell_token_address.as_slice()))
618 .map(|t| t.decimals as usize)
619 .ok_or_else(|| {
620 SimulationError::FatalError(format!(
621 "Failed to scale spot prices! Pool: {} Token 0x{:x} is not available!",
622 self.id, sell_token_address
623 ))
624 })
625 }
626
627 fn get_amount_limits(
652 &self,
653 tokens: Vec<Address>,
654 overwrites: Option<HashMap<Address, HashMap<U256, U256>>>,
655 block_overrides: Option<BlockEnvOverrides>,
656 ) -> Result<(U256, U256), SimulationError> {
657 if self.live_overrides.is_some() {
658 return self.adapter_contract.get_limits(
659 &self.id,
660 tokens[0],
661 tokens[1],
662 overwrites,
663 block_overrides,
664 );
665 }
666
667 let key = (tokens[0], tokens[1]);
668 let context =
669 LimitContext { block: self.current_engine_block(), block_env: block_overrides.clone() };
670 if let Some(limits) = self.caches.get_limits(key, &context) {
671 return Ok(limits);
672 }
673
674 let limits = self.adapter_contract.get_limits(
675 &self.id,
676 tokens[0],
677 tokens[1],
678 overwrites,
679 block_overrides,
680 )?;
681 self.caches
682 .insert_limits(key, limits, context);
683
684 Ok(limits)
685 }
686
687 fn update_pool_state(
698 &mut self,
699 tokens: &HashMap<Bytes, Token>,
700 balances: &Balances,
701 ) -> Result<(), SimulationError> {
702 self.adapter_contract
704 .engine
705 .clear_temp_storage()
706 .map_err(|err| {
707 SimulationError::FatalError(format!("Failed to clear temporary storage: {err:?}",))
708 })?;
709 self.block_lasting_overwrites.clear();
710 self.caches.invalidate();
711
712 if let Some(bals) = balances
720 .component_balances
721 .get(&self.id)
722 {
723 for (token, bal) in bals {
726 let addr = bytes_to_address(token).map_err(|_| {
727 SimulationError::FatalError(format!(
728 "Invalid token address in balance update: {token:?}"
729 ))
730 })?;
731 self.balances
732 .insert(addr, U256::from_be_slice(bal));
733 }
734 }
735 for contract in &self.involved_contracts {
736 if let Some(bals) = balances
737 .account_balances
738 .get(&Bytes::from(contract.as_slice()))
739 {
740 let contract_entry = self
741 .contract_balances
742 .entry(*contract)
743 .or_default();
744 for (token, bal) in bals {
745 let addr = bytes_to_address(token).map_err(|_| {
746 SimulationError::FatalError(format!(
747 "Invalid token address in balance update: {token:?}"
748 ))
749 })?;
750 contract_entry.insert(addr, U256::from_be_slice(bal));
751 }
752 }
753 }
754
755 self.set_spot_prices(tokens)?;
757 Ok(())
758 }
759
760 fn get_overwrites(
761 &self,
762 tokens: Vec<Address>,
763 max_amount: U256,
764 live: Option<&OverrideSnapshot>,
765 ) -> Result<HashMap<Address, Overwrites>, SimulationError> {
766 let token_overwrites = self.get_token_overwrites(tokens, max_amount)?;
767
768 let mut merged_overwrites =
770 self.merge(self.block_lasting_overwrites.clone(), token_overwrites);
771
772 if let Some(live) = live {
774 if !live.storage.is_empty() {
775 merged_overwrites = self.merge(merged_overwrites, live.storage.as_ref().clone());
776 }
777 }
778
779 Ok(merged_overwrites)
780 }
781
782 fn get_token_overwrites(
783 &self,
784 tokens: Vec<Address>,
785 max_amount: U256,
786 ) -> Result<HashMap<Address, Overwrites>, SimulationError> {
787 let sell_token = &tokens[0].clone(); let mut res: Vec<HashMap<Address, Overwrites>> = Vec::new();
789 if !self
790 .capabilities
791 .contains(&Capability::TokenBalanceIndependent)
792 {
793 res.push(self.get_balance_overwrites()?);
794 }
795
796 let mut overwrites = TokenProxyOverwriteFactory::new(*sell_token, None);
797
798 overwrites.set_balance(max_amount, Address::from_slice(&*EXTERNAL_ACCOUNT.0));
799
800 overwrites.set_allowance(max_amount, self.adapter_contract.address, *EXTERNAL_ACCOUNT);
802
803 res.push(overwrites.get_overwrites());
804
805 for token in tokens.iter().skip(1) {
809 if self
810 .self_contained_tokens
811 .contains(token) &&
812 !self
813 .disable_overwrite_tokens
814 .contains(token)
815 {
816 let mut recipient = TokenProxyOverwriteFactory::new(*token, None);
817 recipient.set_balance(U256::ZERO, *EXTERNAL_ACCOUNT);
818 res.push(recipient.get_overwrites());
819 }
820 }
821
822 Ok(res
824 .into_iter()
825 .fold(HashMap::new(), |acc, overwrite| self.merge(acc, overwrite)))
826 }
827
828 fn get_balance_overwrites(&self) -> Result<HashMap<Address, Overwrites>, SimulationError> {
839 let mut balance_overwrites: HashMap<Address, Overwrites> = HashMap::new();
840
841 let address = match self.balance_owner {
843 Some(owner) => Some(owner),
844 None if !self.contract_balances.is_empty() => None,
845 None => Some(self.id.parse().map_err(|_| {
846 SimulationError::FatalError(
847 "Failed to get balance overwrites: Pool ID is not an address".into(),
848 )
849 })?),
850 };
851
852 if let Some(address) = address {
853 for (token, bal) in &self.balances {
856 let mut overwrites = TokenProxyOverwriteFactory::new(*token, None);
857 overwrites.set_balance(*bal, address);
858 if self
861 .self_contained_tokens
862 .contains(token)
863 {
864 overwrites.set_has_custom_approval(address);
865 }
866 balance_overwrites.extend(overwrites.get_overwrites());
867 }
868 }
869
870 for (contract, balances) in &self.contract_balances {
873 for (token, balance) in balances {
874 let mut overwrites = TokenProxyOverwriteFactory::new(*token, None);
875 overwrites.set_balance(*balance, *contract);
876 if self
879 .self_contained_tokens
880 .contains(token)
881 {
882 overwrites.set_has_custom_approval(*contract);
883 }
884 balance_overwrites.extend(overwrites.get_overwrites());
885 }
886 }
887
888 for token in &self.disable_overwrite_tokens {
890 balance_overwrites.remove(token);
891 }
892
893 Ok(balance_overwrites)
894 }
895
896 fn merge(
898 &self,
899 mut target: HashMap<Address, Overwrites>,
900 source: HashMap<Address, Overwrites>,
901 ) -> HashMap<Address, Overwrites> {
902 for (key, source_inner) in source {
903 target
904 .entry(key)
905 .or_default()
906 .extend(source_inner);
907 }
908
909 target
910 }
911
912 #[cfg(test)]
913 pub fn get_involved_contracts(&self) -> HashSet<Address> {
914 self.involved_contracts.clone()
915 }
916
917 #[cfg(test)]
918 pub fn get_manual_updates(&self) -> bool {
919 self.manual_updates
920 }
921
922 #[cfg(test)]
923 pub fn get_spot_price_caller(&self) -> Option<Address> {
924 self.spot_price_caller
925 }
926
927 fn get_amount_out_with(
930 &self,
931 amount_in: &BigUint,
932 token_in: &Token,
933 token_out: &Token,
934 live_snapshot: Option<&OverrideSnapshot>,
935 ) -> Result<GetAmountOutResult, SimulationError> {
936 let sell_token_address = bytes_to_address(&token_in.address)?;
937 let buy_token_address = bytes_to_address(&token_out.address)?;
938 let sell_amount = U256::from_be_slice(&amount_in.to_bytes_be());
939 let block_overrides = self.block_env(live_snapshot);
940 let overwrites = self.get_overwrites(
941 vec![sell_token_address, buy_token_address],
942 *MAX_BALANCE / U256::from(100),
943 live_snapshot,
944 )?;
945 let (sell_amount_limit, _) = self.get_amount_limits(
946 vec![sell_token_address, buy_token_address],
947 Some(overwrites.clone()),
948 block_overrides.clone(),
949 )?;
950 let (sell_amount_respecting_limit, sell_amount_exceeds_limit) = if self
951 .capabilities
952 .contains(&Capability::HardLimits) &&
953 sell_amount_limit < sell_amount
954 {
955 (sell_amount_limit, true)
956 } else {
957 (sell_amount, false)
958 };
959
960 let overwrites_with_sell_limit = self.get_overwrites(
961 vec![sell_token_address, buy_token_address],
962 sell_amount_limit,
963 live_snapshot,
964 )?;
965 let complete_overwrites = self.merge(overwrites, overwrites_with_sell_limit);
966
967 let (trade, state_changes) = self.adapter_contract.swap(
968 &self.id,
969 sell_token_address,
970 buy_token_address,
971 false,
972 sell_amount_respecting_limit,
973 Some(complete_overwrites),
974 block_overrides,
975 )?;
976
977 let mut new_state = self.clone_invalidated();
978
979 for (address, state_update) in state_changes {
981 if let Some(storage) = state_update.storage {
982 let block_overwrites = new_state
983 .block_lasting_overwrites
984 .entry(address)
985 .or_default();
986 for (slot, value) in storage {
987 let slot = U256::from_str(&slot.to_string()).map_err(|_| {
988 SimulationError::FatalError("Failed to decode slot index".to_string())
989 })?;
990 let value = U256::from_str(&value.to_string()).map_err(|_| {
991 SimulationError::FatalError("Failed to decode slot overwrite".to_string())
992 })?;
993 block_overwrites.insert(slot, value);
994 }
995 }
996 }
997
998 if new_state.live_overrides.is_some() {
999 let tokens = HashMap::from([
1004 (token_in.address.clone(), token_in.clone()),
1005 (token_out.address.clone(), token_out.clone()),
1006 ]);
1007 let _ = new_state.set_spot_prices(&tokens);
1008 }
1009
1010 let buy_amount = trade.received_amount;
1011
1012 if sell_amount_exceeds_limit {
1013 return Err(SimulationError::InvalidInput(
1014 format!("Sell amount exceeds limit {sell_amount_limit}"),
1015 Some(GetAmountOutResult::new(
1016 u256_to_biguint(buy_amount),
1017 u256_to_biguint(trade.gas_used),
1018 Box::new(new_state),
1019 )),
1020 ));
1021 }
1022 Ok(GetAmountOutResult::new(
1023 u256_to_biguint(buy_amount),
1024 u256_to_biguint(trade.gas_used),
1025 Box::new(new_state),
1026 ))
1027 }
1028
1029 fn get_limits_with(
1032 &self,
1033 sell_token: &Bytes,
1034 buy_token: &Bytes,
1035 live_snapshot: Option<&OverrideSnapshot>,
1036 ) -> Result<(BigUint, BigUint), SimulationError> {
1037 let sell_token = bytes_to_address(sell_token)?;
1038 let buy_token = bytes_to_address(buy_token)?;
1039 let overwrites = self.get_overwrites(
1040 vec![sell_token, buy_token],
1041 *MAX_BALANCE / U256::from(100),
1042 live_snapshot,
1043 )?;
1044 let limits = self.get_amount_limits(
1045 vec![sell_token, buy_token],
1046 Some(overwrites),
1047 self.block_env(live_snapshot),
1048 )?;
1049 Ok((u256_to_biguint(limits.0), u256_to_biguint(limits.1)))
1050 }
1051
1052 #[cfg(test)]
1053 pub fn get_balance_owner(&self) -> Option<Address> {
1054 self.balance_owner
1055 }
1056
1057 pub fn get_balances(&self) -> &HashMap<Address, U256> {
1059 &self.balances
1060 }
1061
1062 #[cfg(test)]
1063 pub fn get_block_overrides(&self) -> Option<BlockEnvOverrides> {
1064 let live_snapshot = self.get_live_snapshot();
1065 self.block_env(live_snapshot.as_ref())
1066 }
1067}
1068
1069impl<D> Serialize for EVMPoolState<D>
1070where
1071 D: EngineDatabaseInterface + Clone + Debug,
1072 <D as DatabaseRef>::Error: Debug,
1073 <D as EngineDatabaseInterface>::Error: Debug,
1074{
1075 fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
1076 where
1077 S: serde::Serializer,
1078 {
1079 Err(serde::ser::Error::custom("not supported due vm state deps"))
1080 }
1081}
1082
1083impl<'de, D> Deserialize<'de> for EVMPoolState<D>
1084where
1085 D: EngineDatabaseInterface + Clone + Debug,
1086 <D as DatabaseRef>::Error: Debug,
1087 <D as EngineDatabaseInterface>::Error: Debug,
1088{
1089 fn deserialize<De>(_deserializer: De) -> Result<Self, De::Error>
1090 where
1091 De: serde::Deserializer<'de>,
1092 {
1093 Err(serde::de::Error::custom("not supported due vm state deps"))
1094 }
1095}
1096
1097#[typetag::serialize]
1098impl<D> ProtocolSim for EVMPoolState<D>
1099where
1100 D: EngineDatabaseInterface + Clone + Debug + 'static,
1101 <D as DatabaseRef>::Error: Debug,
1102 <D as EngineDatabaseInterface>::Error: Debug,
1103{
1104 fn fee(&self) -> f64 {
1105 todo!()
1106 }
1107
1108 fn spot_price(&self, base: &Token, quote: &Token) -> Result<f64, SimulationError> {
1109 let base_address = bytes_to_address(&base.address)?;
1110 let quote_address = bytes_to_address("e.address)?;
1111
1112 if let Some(price) = self
1113 .caches
1114 .get_spot_price((base_address, quote_address))
1115 {
1116 return Ok(price);
1117 }
1118
1119 if !self.tokens.contains(&base.address) || !self.tokens.contains("e.address) {
1122 return Err(SimulationError::FatalError(format!(
1123 "Spot price not found for base token {base_address} and quote token {quote_address}"
1124 )));
1125 }
1126
1127 if self.live_overrides.is_some() {
1131 return Err(SimulationError::FatalError(format!(
1132 "Spot price not found for base token {base_address} and quote token {quote_address}"
1133 )));
1134 }
1135
1136 let tokens = HashMap::from([
1138 (base.address.clone(), base.clone()),
1139 (quote.address.clone(), quote.clone()),
1140 ]);
1141 let price = self.compute_spot_price(&tokens, base_address, quote_address, None)?;
1142 self.caches
1143 .insert_spot_price((base_address, quote_address), price);
1144 Ok(price)
1145 }
1146
1147 fn get_amount_out(
1148 &self,
1149 amount_in: BigUint,
1150 token_in: &Token,
1151 token_out: &Token,
1152 ) -> Result<GetAmountOutResult, SimulationError> {
1153 let live_snapshot = self.get_live_snapshot();
1155 Self::run_with_indexed_fallback(&self.id, "Swap", live_snapshot.as_ref(), |snapshot| {
1156 self.get_amount_out_with(&amount_in, token_in, token_out, snapshot)
1157 })
1158 }
1159
1160 fn get_limits(
1161 &self,
1162 sell_token: Bytes,
1163 buy_token: Bytes,
1164 ) -> Result<(BigUint, BigUint), SimulationError> {
1165 let live_snapshot = self.get_live_snapshot();
1166 Self::run_with_indexed_fallback(&self.id, "Limits", live_snapshot.as_ref(), |snapshot| {
1167 self.get_limits_with(&sell_token, &buy_token, snapshot)
1168 })
1169 }
1170
1171 fn delta_transition(
1172 &mut self,
1173 delta: ProtocolStateDelta,
1174 tokens: &HashMap<Bytes, Token>,
1175 balances: &Balances,
1176 ) -> Result<(), TransitionError> {
1177 if let Some(block_number) = delta
1178 .updated_attributes
1179 .get("override_block_number")
1180 {
1181 let number = <[u8; 8]>::try_from(block_number.as_ref())
1182 .map(u64::from_be_bytes)
1183 .map_err(|_| {
1184 TransitionError::DecodeError(
1185 "override_block_number attribute must be an 8-byte big-endian u64"
1186 .to_string(),
1187 )
1188 })?;
1189 self.block_overrides
1190 .get_or_insert_with(BlockEnvOverrides::default)
1191 .number = Some(number);
1192 }
1193
1194 if let Some(block_timestamp) = delta
1195 .updated_attributes
1196 .get("override_block_timestamp")
1197 {
1198 let timestamp = <[u8; 8]>::try_from(block_timestamp.as_ref())
1199 .map(u64::from_be_bytes)
1200 .map_err(|_| {
1201 TransitionError::DecodeError(
1202 "override_block_timestamp attribute must be an 8-byte big-endian u64"
1203 .to_string(),
1204 )
1205 })?;
1206 self.block_overrides
1207 .get_or_insert_with(BlockEnvOverrides::default)
1208 .timestamp = Some(timestamp);
1209 }
1210
1211 if self.manual_updates {
1217 if let Some(marker) = delta
1219 .updated_attributes
1220 .get("update_marker")
1221 {
1222 if !marker.is_empty() && marker[0] != 0 {
1224 self.update_pool_state(tokens, balances)?;
1225 }
1226 }
1227 } else {
1228 self.update_pool_state(tokens, balances)?;
1229 }
1230
1231 Ok(())
1232 }
1233
1234 fn query_pool_swap(
1235 &self,
1236 params: &tycho_common::simulation::protocol_sim::QueryPoolSwapParams,
1237 ) -> Result<tycho_common::simulation::protocol_sim::PoolSwap, SimulationError> {
1238 crate::evm::query_pool_swap::query_pool_swap(self, params)
1239 }
1240
1241 fn clone_box(&self) -> Box<dyn ProtocolSim> {
1242 Box::new(self.clone())
1243 }
1244
1245 fn as_any(&self) -> &dyn Any {
1246 self
1247 }
1248
1249 fn as_any_mut(&mut self) -> &mut dyn Any {
1250 self
1251 }
1252
1253 fn eq(&self, other: &dyn ProtocolSim) -> bool {
1254 if let Some(other_state) = other
1255 .as_any()
1256 .downcast_ref::<EVMPoolState<PreCachedDB>>()
1257 {
1258 self.id == other_state.id
1259 } else {
1260 false
1261 }
1262 }
1263
1264 fn typetag_deserialize(&self) {
1266 unreachable!("Only to catch missing typetag attribute on impl blocks. Not called.")
1268 }
1269}
1270
1271#[cfg(test)]
1272mod tests {
1273 use std::default::Default;
1274
1275 use num_traits::One;
1276 use revm::{
1277 primitives::KECCAK_EMPTY,
1278 state::{AccountInfo, Bytecode},
1279 };
1280 use serde_json::Value;
1281 use tycho_client::feed::BlockHeader;
1282 use tycho_common::models::Chain;
1283
1284 use super::*;
1285 use crate::evm::{
1286 engine_db::{create_engine, SHARED_TYCHO_DB},
1287 protocol::vm::{
1288 constants::{BALANCER_V2, ERC20_PROXY_BYTECODE},
1289 state_builder::EVMPoolStateBuilder,
1290 },
1291 simulation::SimulationEngine,
1292 tycho_models::AccountUpdate,
1293 };
1294
1295 fn dai() -> Token {
1296 Token::new(
1297 &Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(),
1298 "DAI",
1299 18,
1300 0,
1301 &[Some(10_000)],
1302 Chain::Ethereum,
1303 100,
1304 )
1305 }
1306
1307 fn bal() -> Token {
1308 Token::new(
1309 &Bytes::from_str("0xba100000625a3754423978a60c9317c58a424e3d").unwrap(),
1310 "BAL",
1311 18,
1312 0,
1313 &[Some(10_000)],
1314 Chain::Ethereum,
1315 100,
1316 )
1317 }
1318
1319 fn dai_addr() -> Address {
1320 bytes_to_address(&dai().address).unwrap()
1321 }
1322
1323 fn bal_addr() -> Address {
1324 bytes_to_address(&bal().address).unwrap()
1325 }
1326
1327 async fn setup_pool_state() -> EVMPoolState<PreCachedDB> {
1328 let data_str = include_str!("assets/balancer_contract_storage_block_20463609.json");
1329 let data: Value = serde_json::from_str(data_str).expect("Failed to parse JSON");
1330
1331 let accounts: Vec<AccountUpdate> = serde_json::from_value(data["accounts"].clone())
1332 .expect("Expected accounts to match AccountUpdate structure");
1333
1334 let db = PreCachedDB::new().expect("failed to create test database");
1337 let engine: SimulationEngine<_> = create_engine(db.clone(), false).unwrap();
1338
1339 let block = BlockHeader {
1340 number: 20463609,
1341 hash: Bytes::from_str(
1342 "0x4315fd1afc25cc2ebc72029c543293f9fd833eeb305e2e30159459c827733b1b",
1343 )
1344 .unwrap(),
1345 timestamp: 1722875891,
1346 ..Default::default()
1347 };
1348
1349 for account in accounts.clone() {
1350 engine
1351 .state
1352 .init_account(
1353 account.address,
1354 AccountInfo {
1355 balance: account.balance.unwrap_or_default(),
1356 nonce: 0u64,
1357 code_hash: KECCAK_EMPTY,
1358 code: account
1359 .code
1360 .clone()
1361 .map(|arg0: Vec<u8>| Bytecode::new_raw(arg0.into())),
1362 },
1363 None,
1364 false,
1365 )
1366 .expect("Failed to initialize account");
1367 }
1368 db.update(accounts, Some(block))
1369 .unwrap();
1370
1371 let tokens = vec![dai().address, bal().address];
1372 for token in &tokens {
1373 engine
1374 .state
1375 .init_account(
1376 bytes_to_address(token).unwrap(),
1377 AccountInfo {
1378 balance: U256::from(0),
1379 nonce: 0,
1380 code_hash: KECCAK_EMPTY,
1381 code: Some(Bytecode::new_raw(ERC20_PROXY_BYTECODE.into())),
1382 },
1383 None,
1384 true,
1385 )
1386 .expect("Failed to initialize account");
1387 }
1388
1389 let block = BlockHeader {
1390 number: 18485417,
1391 hash: Bytes::from_str(
1392 "0x28d41d40f2ac275a4f5f621a636b9016b527d11d37d610a45ac3a821346ebf8c",
1393 )
1394 .expect("Invalid block hash"),
1395 timestamp: 0,
1396 ..Default::default()
1397 };
1398 db.update(vec![], Some(block.clone()))
1399 .unwrap();
1400
1401 let pool_id: String =
1402 "0x4626d81b3a1711beb79f4cecff2413886d461677000200000000000000000011".into();
1403
1404 let stateless_contracts = HashMap::from([(
1405 String::from("0x3de27efa2f1aa663ae5d458857e731c129069f29"),
1406 Some(Vec::new()),
1407 )]);
1408
1409 let balances = HashMap::from([
1410 (dai_addr(), U256::from_str("178754012737301807104").unwrap()),
1411 (bal_addr(), U256::from_str("91082987763369885696").unwrap()),
1412 ]);
1413 let adapter_address =
1414 Address::from_str("0xA2C5C98A892fD6656a7F39A2f63228C0Bc846270").unwrap();
1415
1416 EVMPoolStateBuilder::new(pool_id, tokens, adapter_address)
1417 .balances(balances)
1418 .balance_owner(Address::from_str("0xBA12222222228d8Ba445958a75a0704d566BF2C8").unwrap())
1419 .adapter_contract_bytecode(Bytecode::new_raw(BALANCER_V2.into()))
1420 .stateless_contracts(stateless_contracts)
1421 .build(db)
1422 .await
1423 .expect("Failed to build pool state")
1424 }
1425
1426 #[tokio::test]
1427 async fn test_init() {
1428 let pool_state = setup_pool_state().await;
1429
1430 let expected_capabilities = vec![
1431 Capability::SellSide,
1432 Capability::BuySide,
1433 Capability::PriceFunction,
1434 Capability::HardLimits,
1435 ]
1436 .into_iter()
1437 .collect::<HashSet<_>>();
1438
1439 let capabilities_adapter_contract = pool_state
1440 .adapter_contract
1441 .get_capabilities(
1442 &pool_state.id,
1443 bytes_to_address(&pool_state.tokens[0]).unwrap(),
1444 bytes_to_address(&pool_state.tokens[1]).unwrap(),
1445 )
1446 .unwrap();
1447
1448 assert_eq!(capabilities_adapter_contract, expected_capabilities.clone());
1449
1450 let capabilities_state = pool_state.clone().capabilities;
1451
1452 assert_eq!(capabilities_state, expected_capabilities.clone());
1453
1454 let engine_accounts = pool_state
1456 .adapter_contract
1457 .engine
1458 .state
1459 .clone()
1460 .get_account_storage()
1461 .expect("Failed to get account storage");
1462 for token in pool_state.tokens.clone() {
1463 let account = engine_accounts
1464 .get_account_info(&bytes_to_address(&token).unwrap())
1465 .unwrap();
1466 assert_eq!(account.balance, U256::from(0));
1467 assert_eq!(account.nonce, 0u64);
1468 assert_eq!(account.code_hash, KECCAK_EMPTY);
1469 assert!(account.code.is_some());
1470 }
1471
1472 let external_account = engine_accounts
1474 .get_account_info(&EXTERNAL_ACCOUNT)
1475 .unwrap();
1476 assert_eq!(external_account.balance, U256::from(*MAX_BALANCE));
1477 assert_eq!(external_account.nonce, 0u64);
1478 assert_eq!(external_account.code_hash, KECCAK_EMPTY);
1479 assert!(external_account.code.is_none());
1480 }
1481
1482 #[tokio::test]
1483 async fn test_get_amount_out() -> Result<(), Box<dyn std::error::Error>> {
1484 let pool_state = setup_pool_state().await;
1485
1486 let result = pool_state
1487 .get_amount_out(BigUint::from_str("1000000000000000000").unwrap(), &dai(), &bal())
1488 .unwrap();
1489 let new_state = result
1490 .new_state
1491 .as_any()
1492 .downcast_ref::<EVMPoolState<PreCachedDB>>()
1493 .unwrap();
1494 assert_eq!(result.amount, BigUint::from_str("137780051463393923").unwrap());
1495 let pre = pool_state
1498 .spot_price(&dai(), &bal())
1499 .unwrap();
1500 let post = new_state
1501 .spot_price(&dai(), &bal())
1502 .unwrap();
1503 assert_ne!(pre, post);
1504 assert!(pool_state
1505 .block_lasting_overwrites
1506 .is_empty());
1507 Ok(())
1508 }
1509
1510 #[tokio::test]
1511 async fn test_sequential_get_amount_outs() {
1512 let pool_state = setup_pool_state().await;
1513
1514 let result = pool_state
1515 .get_amount_out(BigUint::from_str("1000000000000000000").unwrap(), &dai(), &bal())
1516 .unwrap();
1517 let new_state = result
1518 .new_state
1519 .as_any()
1520 .downcast_ref::<EVMPoolState<PreCachedDB>>()
1521 .unwrap();
1522 assert_eq!(result.amount, BigUint::from_str("137780051463393923").unwrap());
1523 let price_before_first_swap = pool_state
1526 .spot_price(&dai(), &bal())
1527 .unwrap();
1528 let price_after_first_swap = new_state
1529 .spot_price(&dai(), &bal())
1530 .unwrap();
1531 assert_ne!(price_before_first_swap, price_after_first_swap);
1532
1533 let new_result = new_state
1534 .get_amount_out(BigUint::from_str("1000000000000000000").unwrap(), &dai(), &bal())
1535 .unwrap();
1536 let new_state_second_swap = new_result
1537 .new_state
1538 .as_any()
1539 .downcast_ref::<EVMPoolState<PreCachedDB>>()
1540 .unwrap();
1541
1542 assert_eq!(new_result.amount, BigUint::from_str("136964651490065626").unwrap());
1543 let price_after_second_swap = new_state_second_swap
1546 .spot_price(&dai(), &bal())
1547 .unwrap();
1548 assert_ne!(price_after_first_swap, price_after_second_swap);
1549 }
1550
1551 #[tokio::test]
1552 async fn test_get_amount_out_dust() {
1553 let pool_state = setup_pool_state().await;
1554
1555 let result = pool_state
1556 .get_amount_out(BigUint::one(), &dai(), &bal())
1557 .unwrap();
1558
1559 let _ = result
1560 .new_state
1561 .as_any()
1562 .downcast_ref::<EVMPoolState<PreCachedDB>>()
1563 .unwrap();
1564 assert_eq!(result.amount, BigUint::ZERO);
1565 }
1566
1567 #[tokio::test]
1568 async fn test_get_amount_out_sell_limit() {
1569 let pool_state = setup_pool_state().await;
1570
1571 let result = pool_state.get_amount_out(
1572 BigUint::from_str("100379494253364362835").unwrap(),
1574 &dai(),
1575 &bal(),
1576 );
1577
1578 assert!(result.is_err());
1579
1580 match result {
1581 Err(SimulationError::InvalidInput(msg1, amount_out_result)) => {
1582 assert_eq!(msg1, "Sell amount exceeds limit 100279494253364362835");
1583 assert!(amount_out_result.is_some());
1584 }
1585 _ => panic!("Test failed: was expecting an Err(SimulationError::RetryDifferentInput(_, _)) value"),
1586 }
1587 }
1588
1589 #[tokio::test]
1590 async fn test_get_amount_limits() {
1591 let pool_state = setup_pool_state().await;
1592
1593 let overwrites = pool_state
1594 .get_overwrites(
1595 vec![
1596 bytes_to_address(&pool_state.tokens[0]).unwrap(),
1597 bytes_to_address(&pool_state.tokens[1]).unwrap(),
1598 ],
1599 *MAX_BALANCE / U256::from(100),
1600 None,
1601 )
1602 .unwrap();
1603 let (dai_limit, _) = pool_state
1604 .get_amount_limits(
1605 vec![dai_addr(), bal_addr()],
1606 Some(overwrites.clone()),
1607 pool_state.block_env(None),
1608 )
1609 .unwrap();
1610 assert_eq!(dai_limit, U256::from_str("100279494253364362835").unwrap());
1611
1612 let (bal_limit, _) = pool_state
1613 .get_amount_limits(
1614 vec![
1615 bytes_to_address(&pool_state.tokens[1]).unwrap(),
1616 bytes_to_address(&pool_state.tokens[0]).unwrap(),
1617 ],
1618 Some(overwrites),
1619 pool_state.block_env(None),
1620 )
1621 .unwrap();
1622 assert_eq!(bal_limit, U256::from_str("13997408640689987484").unwrap());
1623 }
1624
1625 #[tokio::test]
1626 async fn test_get_limits_cached_matches_fresh() {
1627 let pool_state = setup_pool_state().await;
1628 let overwrites = pool_state
1629 .get_overwrites(vec![dai_addr(), bal_addr()], *MAX_BALANCE / U256::from(100), None)
1630 .unwrap();
1631
1632 let fresh = pool_state
1634 .get_amount_limits(vec![dai_addr(), bal_addr()], Some(overwrites.clone()), None)
1635 .unwrap();
1636 assert_eq!(
1637 pool_state
1638 .caches
1639 .read()
1640 .limits
1641 .get(&(dai_addr(), bal_addr()))
1642 .copied(),
1643 Some(fresh)
1644 );
1645
1646 let cached = pool_state
1648 .get_amount_limits(vec![dai_addr(), bal_addr()], Some(overwrites), None)
1649 .unwrap();
1650 assert_eq!(fresh, cached);
1651
1652 let cold = setup_pool_state().await;
1656 let different_overwrites = cold
1657 .get_overwrites(vec![dai_addr(), bal_addr()], *MAX_BALANCE / U256::from(50), None)
1658 .unwrap();
1659 let independent = cold
1660 .get_amount_limits(vec![dai_addr(), bal_addr()], Some(different_overwrites), None)
1661 .unwrap();
1662 assert_eq!(independent, fresh);
1663 }
1664
1665 #[tokio::test]
1669 async fn test_engine_block_advance_invalidates_cached_limits() {
1670 let pool_state = setup_pool_state().await;
1671 let overwrites = pool_state
1672 .get_overwrites(vec![dai_addr(), bal_addr()], *MAX_BALANCE / U256::from(100), None)
1673 .unwrap();
1674 let real = pool_state
1675 .get_amount_limits(vec![dai_addr(), bal_addr()], Some(overwrites.clone()), None)
1676 .unwrap();
1677 let sentinel = (U256::from(1u64), U256::from(1u64));
1678 assert_ne!(real, sentinel);
1679 pool_state
1680 .caches
1681 .write()
1682 .limits
1683 .insert((dai_addr(), bal_addr()), sentinel);
1684
1685 let served = pool_state
1687 .get_amount_limits(vec![dai_addr(), bal_addr()], Some(overwrites.clone()), None)
1688 .unwrap();
1689 assert_eq!(served, sentinel);
1690
1691 let mut block = SHARED_TYCHO_DB
1693 .get_current_block()
1694 .expect("fixture sets an engine block");
1695 block.number += 1;
1696 block.timestamp += 12;
1697 SHARED_TYCHO_DB
1698 .update(vec![], Some(block))
1699 .expect("engine block update");
1700
1701 let after = pool_state
1703 .get_amount_limits(vec![dai_addr(), bal_addr()], Some(overwrites), None)
1704 .unwrap();
1705 assert_ne!(after, sentinel);
1706 assert_eq!(after, real);
1707 }
1708
1709 #[tokio::test]
1710 async fn test_set_spot_prices() {
1711 let pool_state = setup_pool_state().await;
1712
1713 pool_state
1714 .set_spot_prices(
1715 &vec![bal(), dai()]
1716 .into_iter()
1717 .map(|t| (t.address.clone(), t))
1718 .collect(),
1719 )
1720 .unwrap();
1721
1722 let dai_bal_spot_price = pool_state
1723 .caches
1724 .get_spot_price((
1725 bytes_to_address(&pool_state.tokens[0]).unwrap(),
1726 bytes_to_address(&pool_state.tokens[1]).unwrap(),
1727 ))
1728 .unwrap();
1729 let bal_dai_spot_price = pool_state
1730 .caches
1731 .get_spot_price((
1732 bytes_to_address(&pool_state.tokens[1]).unwrap(),
1733 bytes_to_address(&pool_state.tokens[0]).unwrap(),
1734 ))
1735 .unwrap();
1736 assert_eq!(dai_bal_spot_price, 0.137_778_914_319_047_9);
1737 assert_eq!(bal_dai_spot_price, 7.071_503_245_428_246);
1738 }
1739
1740 #[tokio::test]
1741 async fn test_set_spot_prices_without_capability() {
1742 let mut pool_state = setup_pool_state().await;
1744
1745 pool_state
1746 .capabilities
1747 .remove(&Capability::PriceFunction);
1748
1749 pool_state
1750 .set_spot_prices(
1751 &vec![bal(), dai()]
1752 .into_iter()
1753 .map(|t| (t.address.clone(), t))
1754 .collect(),
1755 )
1756 .unwrap();
1757
1758 let dai_bal_spot_price = pool_state
1759 .caches
1760 .get_spot_price((
1761 bytes_to_address(&pool_state.tokens[0]).unwrap(),
1762 bytes_to_address(&pool_state.tokens[1]).unwrap(),
1763 ))
1764 .unwrap();
1765 let bal_dai_spot_price = pool_state
1766 .caches
1767 .get_spot_price((
1768 bytes_to_address(&pool_state.tokens[1]).unwrap(),
1769 bytes_to_address(&pool_state.tokens[0]).unwrap(),
1770 ))
1771 .unwrap();
1772 assert_eq!(dai_bal_spot_price, 0.13736685496467538);
1773 assert_eq!(bal_dai_spot_price, 7.050354297665408);
1774 }
1775
1776 #[tokio::test]
1777 async fn test_spot_price_lazy_computes_on_miss() {
1778 let pool_state = setup_pool_state().await;
1779 assert!(pool_state
1781 .caches
1782 .read()
1783 .spot_prices
1784 .is_empty());
1785
1786 let dai_bal = pool_state
1789 .spot_price(&dai(), &bal())
1790 .unwrap();
1791 assert_eq!(dai_bal, 0.137_778_914_319_047_9);
1792 let bal_dai = pool_state
1793 .spot_price(&bal(), &dai())
1794 .unwrap();
1795 assert_eq!(bal_dai, 7.071_503_245_428_246);
1796 assert_eq!(
1797 pool_state
1798 .caches
1799 .get_spot_price((dai_addr(), bal_addr())),
1800 Some(dai_bal)
1801 );
1802 }
1803
1804 #[tokio::test]
1805 async fn test_get_balance_overwrites_with_component_balances() {
1806 let pool_state: EVMPoolState<PreCachedDB> = setup_pool_state().await;
1807
1808 let overwrites = pool_state
1809 .get_balance_overwrites()
1810 .unwrap();
1811
1812 let dai_address = dai_addr();
1813 let bal_address = bal_addr();
1814 assert!(overwrites.contains_key(&dai_address));
1815 assert!(overwrites.contains_key(&bal_address));
1816 }
1817
1818 #[tokio::test]
1819 async fn test_get_balance_overwrites_with_contract_balances() {
1820 let mut pool_state: EVMPoolState<PreCachedDB> = setup_pool_state().await;
1821
1822 let contract_address =
1823 Address::from_str("0xBA12222222228d8Ba445958a75a0704d566BF2C8").unwrap();
1824
1825 pool_state.balances.clear();
1827 pool_state.balance_owner = None;
1828
1829 let dai_address = dai_addr();
1831 let bal_address = bal_addr();
1832 pool_state.contract_balances = HashMap::from([(
1833 contract_address,
1834 HashMap::from([
1835 (dai_address, U256::from_str("7500000000000000000000").unwrap()), (bal_address, U256::from_str("1500000000000000000000").unwrap()), ]),
1838 )]);
1839
1840 let overwrites = pool_state
1841 .get_balance_overwrites()
1842 .unwrap();
1843
1844 assert!(overwrites.contains_key(&dai_address));
1845 assert!(overwrites.contains_key(&bal_address));
1846 }
1847
1848 #[tokio::test]
1849 async fn test_balance_merging_during_delta_transition() {
1850 use std::str::FromStr;
1851
1852 let mut pool_state = setup_pool_state().await;
1853 let pool_id = pool_state.id.clone();
1854
1855 let dai_addr = dai_addr();
1858 let bal_addr = bal_addr();
1859 let new_token = Address::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); pool_state.balances.clear();
1863 pool_state
1864 .balances
1865 .insert(dai_addr, U256::from(1000000000u64));
1866 pool_state
1867 .balances
1868 .insert(bal_addr, U256::from(2000000000u64));
1869 pool_state
1870 .balances
1871 .insert(new_token, U256::from(3000000000u64));
1872
1873 let mut tokens = HashMap::new();
1875 tokens.insert(dai().address.clone(), dai());
1876 tokens.insert(bal().address.clone(), bal());
1877
1878 let mut component_balances = HashMap::new();
1880 let mut delta_balances = HashMap::new();
1881 delta_balances.insert(dai().address.clone(), Bytes::from(vec![0x77, 0x35, 0x94, 0x00])); component_balances.insert(pool_id.clone(), delta_balances);
1884
1885 let balances = Balances { component_balances, account_balances: HashMap::new() };
1886
1887 let initial_balance_count = pool_state.balances.len();
1889 assert_eq!(initial_balance_count, 3);
1890
1891 pool_state
1893 .update_pool_state(&tokens, &balances)
1894 .unwrap();
1895
1896 assert_eq!(
1898 pool_state.balances.len(),
1899 3,
1900 "All balances should be preserved after delta transition"
1901 );
1902 assert!(
1903 pool_state
1904 .balances
1905 .contains_key(&dai_addr),
1906 "DAI balance should be present"
1907 );
1908 assert!(
1909 pool_state
1910 .balances
1911 .contains_key(&bal_addr),
1912 "BAL balance should be present"
1913 );
1914 assert!(
1915 pool_state
1916 .balances
1917 .contains_key(&new_token),
1918 "New token balance should be preserved from before delta"
1919 );
1920
1921 assert_eq!(
1923 pool_state.balances[&dai_addr],
1924 U256::from(2000000000u64),
1925 "DAI balance should be updated"
1926 );
1927
1928 assert_eq!(
1930 pool_state.balances[&bal_addr],
1931 U256::from(2000000000u64),
1932 "BAL balance should be unchanged"
1933 );
1934 assert_eq!(
1935 pool_state.balances[&new_token],
1936 U256::from(3000000000u64),
1937 "New token balance should be unchanged"
1938 );
1939 }
1940
1941 #[tokio::test]
1942 async fn test_update_pool_state_warms_caches() {
1943 let mut pool_state = setup_pool_state().await;
1944 let tokens =
1945 HashMap::from([(dai().address.clone(), dai()), (bal().address.clone(), bal())]);
1946 let balances =
1947 Balances { component_balances: HashMap::new(), account_balances: HashMap::new() };
1948
1949 pool_state
1950 .update_pool_state(&tokens, &balances)
1951 .unwrap();
1952
1953 let caches = pool_state.caches.read();
1954 assert!(!caches.spot_prices.is_empty());
1955 assert!(!caches.limits.is_empty());
1956 }
1957
1958 #[tokio::test]
1959 async fn test_delta_transition_updates_block_overrides() {
1960 let mut pool_state = setup_pool_state().await;
1961 pool_state.manual_updates = true;
1962 pool_state.block_overrides = None;
1963
1964 let delta = ProtocolStateDelta {
1965 component_id: pool_state.id.clone(),
1966 updated_attributes: HashMap::from([
1967 ("override_block_number".to_string(), Bytes::from(123_u64.to_be_bytes().to_vec())),
1968 (
1969 "override_block_timestamp".to_string(),
1970 Bytes::from(456_u64.to_be_bytes().to_vec()),
1971 ),
1972 ]),
1973 deleted_attributes: HashSet::new(),
1974 };
1975
1976 pool_state
1977 .delta_transition(delta, &HashMap::new(), &Balances::default())
1978 .unwrap();
1979
1980 assert_eq!(
1981 pool_state.block_overrides,
1982 Some(BlockEnvOverrides { number: Some(123), timestamp: Some(456) })
1983 );
1984 }
1985
1986 #[tokio::test]
1987 async fn test_delta_transition_updates_partial_block_overrides() {
1988 let mut pool_state = setup_pool_state().await;
1989 pool_state.manual_updates = true;
1990 pool_state.block_overrides =
1991 Some(BlockEnvOverrides { number: Some(123), timestamp: Some(456) });
1992
1993 let delta = ProtocolStateDelta {
1994 component_id: pool_state.id.clone(),
1995 updated_attributes: HashMap::from([(
1996 "override_block_number".to_string(),
1997 Bytes::from(789_u64.to_be_bytes().to_vec()),
1998 )]),
1999 deleted_attributes: HashSet::new(),
2000 };
2001
2002 pool_state
2003 .delta_transition(delta, &HashMap::new(), &Balances::default())
2004 .unwrap();
2005
2006 assert_eq!(
2007 pool_state.block_overrides,
2008 Some(BlockEnvOverrides { number: Some(789), timestamp: Some(456) })
2009 );
2010 }
2011
2012 #[tokio::test]
2017 async fn test_delta_transition_refreshes_both_balance_maps() {
2018 let mut pool_state = setup_pool_state().await;
2019 let vault = Address::from_str("0xBA12222222228d8Ba445958a75a0704d566BF2C8").unwrap();
2020 pool_state.manual_updates = false;
2023 pool_state.involved_contracts = HashSet::from([vault]);
2024 pool_state.contract_balances =
2025 HashMap::from([(vault, HashMap::from([(dai_addr(), U256::from(1u64))]))]);
2026
2027 let delta = ProtocolStateDelta {
2028 component_id: pool_state.id.clone(),
2029 updated_attributes: HashMap::new(),
2030 deleted_attributes: HashSet::new(),
2031 };
2032 let balances = Balances {
2033 component_balances: HashMap::new(),
2034 account_balances: HashMap::from([(
2035 Bytes::from(vault.as_slice()),
2036 HashMap::from([(dai().address.clone(), Bytes::from(42u64).lpad(32, 0))]),
2037 )]),
2038 };
2039
2040 let _ = pool_state.delta_transition(delta, &HashMap::new(), &balances);
2043
2044 assert_eq!(pool_state.contract_balances[&vault][&dai_addr()], U256::from(42u64));
2047 assert_eq!(
2049 pool_state.balances[&dai_addr()],
2050 U256::from_str("178754012737301807104").unwrap()
2051 );
2052 }
2053
2054 #[tokio::test]
2060 async fn test_block_override_delta_invalidates_cached_limits() {
2061 let mut pool_state = setup_pool_state().await;
2062 pool_state.manual_updates = true;
2063
2064 let overwrites = pool_state
2067 .get_overwrites(vec![dai_addr(), bal_addr()], *MAX_BALANCE / U256::from(100), None)
2068 .unwrap();
2069 let real = pool_state
2070 .get_amount_limits(
2071 vec![dai_addr(), bal_addr()],
2072 Some(overwrites.clone()),
2073 pool_state.block_env(None),
2074 )
2075 .unwrap();
2076 let sentinel = (U256::from(1u64), U256::from(1u64));
2077 assert_ne!(real, sentinel);
2078 pool_state
2079 .caches
2080 .write()
2081 .limits
2082 .insert((dai_addr(), bal_addr()), sentinel);
2083
2084 let delta = ProtocolStateDelta {
2086 component_id: pool_state.id.clone(),
2087 updated_attributes: HashMap::from([(
2088 "override_block_number".to_string(),
2089 Bytes::from(1234_u64.to_be_bytes().to_vec()),
2090 )]),
2091 deleted_attributes: HashSet::new(),
2092 };
2093 pool_state
2094 .delta_transition(delta, &HashMap::new(), &Balances::default())
2095 .unwrap();
2096
2097 let after = pool_state
2099 .get_amount_limits(
2100 vec![dai_addr(), bal_addr()],
2101 Some(overwrites),
2102 pool_state.block_env(None),
2103 )
2104 .unwrap();
2105 assert_ne!(after, sentinel);
2106 }
2107
2108 #[test]
2109 fn should_not_panic_at_typetag_deserialize() {
2110 let deserialized: Result<Box<dyn ProtocolSim>, _> = serde_json::from_str(
2111 r#"{"protocol":"EVMPoolState","state":{"reserve_0":1,"reserve_1":2}}"#,
2112 );
2113
2114 assert!(deserialized.is_err());
2115 }
2116
2117 #[tokio::test]
2120 async fn test_block_env_prefers_live_overrides() {
2121 let mut pool_state = setup_pool_state().await;
2122 pool_state.block_overrides =
2123 Some(BlockEnvOverrides { number: Some(100), timestamp: Some(1_000) });
2124
2125 assert_eq!(
2127 pool_state.block_env(None),
2128 Some(BlockEnvOverrides { number: Some(100), timestamp: Some(1_000) })
2129 );
2130
2131 let empty = OverrideSnapshot::default();
2133 assert_eq!(
2134 pool_state.block_env(Some(&empty)),
2135 Some(BlockEnvOverrides { number: Some(100), timestamp: Some(1_000) })
2136 );
2137
2138 let live = OverrideSnapshot {
2140 block_number: Some(200),
2141 block_timestamp: None,
2142 ..Default::default()
2143 };
2144 assert_eq!(
2145 pool_state.block_env(Some(&live)),
2146 Some(BlockEnvOverrides { number: Some(200), timestamp: Some(1_000) })
2147 );
2148
2149 pool_state.block_overrides = None;
2151 let live = OverrideSnapshot {
2152 block_number: Some(300),
2153 block_timestamp: Some(3_000),
2154 ..Default::default()
2155 };
2156 assert_eq!(
2157 pool_state.block_env(Some(&live)),
2158 Some(BlockEnvOverrides { number: Some(300), timestamp: Some(3_000) })
2159 );
2160 }
2161
2162 #[tokio::test]
2166 async fn test_get_overwrites_applies_live_storage() {
2167 let pool_state = setup_pool_state().await;
2168 let tokens = vec![dai_addr(), bal_addr()];
2169 let max = *MAX_BALANCE / U256::from(100);
2170
2171 let baseline = pool_state
2172 .get_overwrites(tokens.clone(), max, None)
2173 .unwrap();
2174
2175 let empty = OverrideSnapshot::default();
2177 assert_eq!(
2178 pool_state
2179 .get_overwrites(tokens.clone(), max, Some(&empty))
2180 .unwrap(),
2181 baseline
2182 );
2183
2184 let (conflict_addr, conflict_slot, baseline_val) = {
2186 let (addr, slots) = baseline
2187 .iter()
2188 .next()
2189 .expect("baseline has overwrites");
2190 let (slot, val) = slots
2191 .iter()
2192 .next()
2193 .expect("address has slots");
2194 (*addr, *slot, *val)
2195 };
2196 let sentinel = baseline_val + U256::from(1);
2197 let fresh = Address::from([0xAB; 20]);
2198 assert!(
2199 !baseline.contains_key(&fresh),
2200 "fresh address must originate from the live snapshot"
2201 );
2202
2203 let live = OverrideSnapshot {
2204 storage: std::sync::Arc::new(HashMap::from([
2205 (fresh, HashMap::from([(U256::from(7), U256::from(123))])),
2206 (conflict_addr, HashMap::from([(conflict_slot, sentinel)])),
2207 ])),
2208 ..Default::default()
2209 };
2210 let with_live = pool_state
2211 .get_overwrites(tokens, max, Some(&live))
2212 .unwrap();
2213
2214 assert_eq!(
2215 with_live
2216 .get(&fresh)
2217 .and_then(|slots| slots.get(&U256::from(7))),
2218 Some(&U256::from(123)),
2219 "live storage for a fresh contract must be merged in"
2220 );
2221 assert_eq!(
2222 with_live
2223 .get(&conflict_addr)
2224 .and_then(|slots| slots.get(&conflict_slot)),
2225 Some(&sentinel),
2226 "live override must win on slot conflict"
2227 );
2228 }
2229
2230 #[tokio::test]
2235 async fn test_get_live_snapshot_drops_expired() {
2236 let mut pool_state = setup_pool_state().await;
2237
2238 assert!(pool_state.get_live_snapshot().is_none());
2240
2241 let never =
2243 OverrideSnapshot { block_number: Some(1), expires_at: None, ..Default::default() };
2244 let (_never_tx, never_rx) = watch::channel(never);
2245 pool_state.set_live_overrides(never_rx);
2246 assert_eq!(
2247 pool_state
2248 .get_live_snapshot()
2249 .and_then(|snapshot| snapshot.block_number),
2250 Some(1)
2251 );
2252
2253 let fresh = OverrideSnapshot {
2255 block_number: Some(42),
2256 expires_at: Some(u64::MAX),
2257 ..Default::default()
2258 };
2259 let (_fresh_tx, fresh_rx) = watch::channel(fresh);
2260 pool_state.set_live_overrides(fresh_rx);
2261 assert_eq!(
2262 pool_state
2263 .get_live_snapshot()
2264 .and_then(|snapshot| snapshot.block_number),
2265 Some(42)
2266 );
2267
2268 let expired =
2270 OverrideSnapshot { block_number: Some(42), expires_at: Some(1), ..Default::default() };
2271 let (_expired_tx, expired_rx) = watch::channel(expired);
2272 pool_state.set_live_overrides(expired_rx);
2273 assert!(pool_state.get_live_snapshot().is_none());
2274 }
2275
2276 #[tokio::test]
2280 async fn test_set_live_overrides_clears_caches() {
2281 let mut pool_state = setup_pool_state().await;
2282 {
2283 let mut caches = pool_state.caches.write();
2284 caches
2285 .spot_prices
2286 .insert((dai_addr(), bal_addr()), 1.0);
2287 caches
2288 .limits
2289 .insert((dai_addr(), bal_addr()), (U256::from(1u64), U256::from(1u64)));
2290 }
2291
2292 let (_tx, rx) = watch::channel(OverrideSnapshot::default());
2293 pool_state.set_live_overrides(rx);
2294
2295 let caches = pool_state.caches.read();
2296 assert!(caches.spot_prices.is_empty());
2297 assert!(caches.limits.is_empty());
2298 }
2299
2300 fn poison_snapshot(failure_policy: FailurePolicy) -> OverrideSnapshot {
2303 let vault: Address = "0xBA12222222228d8Ba445958a75a0704d566BF2C8"
2304 .parse()
2305 .unwrap();
2306 let poisoned_slots = (0u64..10)
2307 .map(|slot| (U256::from(slot), U256::MAX))
2308 .collect();
2309 OverrideSnapshot {
2310 storage: std::sync::Arc::new(HashMap::from([(vault, poisoned_slots)])),
2311 failure_policy,
2312 ..Default::default()
2313 }
2314 }
2315
2316 #[tokio::test]
2319 async fn test_failing_overrides_error_by_default() {
2320 let mut pool_state = setup_pool_state().await;
2321 let poison = poison_snapshot(FailurePolicy::Error);
2322 let (_tx, rx) = watch::channel(poison);
2323 pool_state.set_live_overrides(rx);
2324
2325 assert!(pool_state
2326 .get_amount_out(BigUint::from_str("1000000000000000000").unwrap(), &dai(), &bal())
2327 .is_err());
2328 }
2329
2330 #[tokio::test]
2334 async fn test_failing_overrides_fall_back_to_indexed_state() {
2335 let pool_state = setup_pool_state().await;
2336 let expected = pool_state
2337 .get_amount_out(BigUint::from_str("1000000000000000000").unwrap(), &dai(), &bal())
2338 .unwrap();
2339 let expected_limits = pool_state
2340 .get_limits(dai().address.clone(), bal().address.clone())
2341 .unwrap();
2342
2343 let mut pool_state = pool_state;
2344 let poison = poison_snapshot(FailurePolicy::FallbackToIndexedState);
2345 let (_tx, rx) = watch::channel(poison);
2346 pool_state.set_live_overrides(rx);
2347
2348 let result = pool_state
2349 .get_amount_out(BigUint::from_str("1000000000000000000").unwrap(), &dai(), &bal())
2350 .expect("must fall back to indexed state");
2351 assert_eq!(result.amount, expected.amount);
2352
2353 let limits = pool_state
2354 .get_limits(dai().address.clone(), bal().address.clone())
2355 .expect("limits must fall back to indexed state");
2356 assert_eq!(limits, expected_limits);
2357
2358 let tokens =
2359 HashMap::from([(dai().address.clone(), dai()), (bal().address.clone(), bal())]);
2360 pool_state
2361 .set_spot_prices(&tokens)
2362 .expect("spot prices must fall back to indexed state");
2363 }
2364}