Skip to main content

tycho_simulation/evm/protocol/vm/
state.rs

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 tycho_common::{
17    dto::ProtocolStateDelta,
18    models::token::Token,
19    simulation::{
20        errors::{SimulationError, TransitionError},
21        protocol_sim::{Balances, GetAmountOutResult, ProtocolSim},
22    },
23    Bytes,
24};
25
26use super::{
27    constants::{EXTERNAL_ACCOUNT, MAX_BALANCE},
28    erc20_token::{Overwrites, TokenProxyOverwriteFactory},
29    models::Capability,
30    tycho_simulation_contract::TychoSimulationContract,
31};
32use crate::evm::{
33    engine_db::{engine_db_interface::EngineDatabaseInterface, tycho_db::PreCachedDB},
34    override_stream::OverrideSnapshot,
35    protocol::{
36        u256_num::{u256_to_biguint, u256_to_f64},
37        utils::bytes_to_address,
38    },
39    simulation::BlockEnvOverrides,
40};
41
42#[derive(Clone)]
43pub struct EVMPoolState<D: EngineDatabaseInterface + Clone + Debug>
44where
45    <D as DatabaseRef>::Error: Debug,
46    <D as EngineDatabaseInterface>::Error: Debug,
47{
48    /// The pool's identifier
49    id: String,
50    /// The pool's token's addresses
51    pub tokens: Vec<Bytes>,
52    /// The pool's component balances.
53    balances: HashMap<Address, U256>,
54    /// The contract address for where protocol balances are stored (i.e. a vault contract).
55    /// If given, balances will be overwritten here instead of on the pool contract during
56    /// simulations. This has been deprecated in favor of `contract_balances`.
57    #[deprecated(note = "Use contract_balances instead")]
58    balance_owner: Option<Address>,
59    /// Spot prices of the pool by token pair
60    spot_prices: HashMap<(Address, Address), f64>,
61    /// The supported capabilities of this pool
62    capabilities: HashSet<Capability>,
63    /// Storage overwrites that will be applied to all simulations. They will be cleared
64    /// when ``update_pool_state`` is called, i.e. usually at each block. Hence, the name.
65    block_lasting_overwrites: HashMap<Address, Overwrites>,
66    /// A set of all contract addresses involved in the simulation of this pool.
67    involved_contracts: HashSet<Address>,
68    /// A map of contracts to their token balances.
69    contract_balances: HashMap<Address, HashMap<Address, U256>>,
70    /// Indicates if the protocol uses custom update rules and requires update
71    /// triggers to recalculate spot prices ect. Default is to update on all changes on
72    /// the pool.
73    manual_updates: bool,
74    /// The adapter contract. This is used to interact with the protocol when running simulations
75    adapter_contract: TychoSimulationContract<D>,
76    /// Tokens for which balance overwrites should be disabled.
77    disable_overwrite_tokens: HashSet<Address>,
78    /// Tokens whose protocol does not emit token contract storage (e.g. FermiSwap), so they are
79    /// bare `TokenProxy` accounts with no implementation in the shared DB. For these, the
80    /// overwrites keep transfers in the proxy's local bookkeeping — holders get a custom approval
81    /// and the swap recipient a custom balance — so a `transferFrom` never delegates to a real
82    /// implementation another VM protocol (curve, balancer) mounted on the same shared token,
83    /// which would revert with `SafeERC20FailedOperation` (ENG-6161). Rebase/fee tokens in
84    /// `disable_overwrite_tokens` are excluded.
85    self_contained_tokens: HashSet<Address>,
86    /// Block context overrides applied to this pool's adapter simulations.
87    block_overrides: Option<BlockEnvOverrides>,
88    /// Live per-block VM overrides (e.g. Titan pAMM oracle prices) read at simulation time.
89    ///
90    /// When set, the latest [`OverrideSnapshot`] is merged into the pool's storage overwrites and
91    /// block environment on every simulation, so sub-block updates are reflected without a Tycho
92    /// block update. Takes precedence over [`Self::block_lasting_overwrites`] and
93    /// [`Self::block_overrides`] on conflict.
94    live_overrides: Option<watch::Receiver<OverrideSnapshot>>,
95}
96
97impl<D> Debug for EVMPoolState<D>
98where
99    D: EngineDatabaseInterface + Clone + Debug,
100    <D as DatabaseRef>::Error: Debug,
101    <D as EngineDatabaseInterface>::Error: Debug,
102{
103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104        f.debug_struct("EVMPoolState")
105            .field("id", &self.id)
106            .field("tokens", &self.tokens)
107            .field("balances", &self.balances)
108            .field("involved_contracts", &self.involved_contracts)
109            .field("contract_balances", &self.contract_balances)
110            .finish_non_exhaustive()
111    }
112}
113
114impl<D> EVMPoolState<D>
115where
116    D: EngineDatabaseInterface + Clone + Debug + 'static,
117    <D as DatabaseRef>::Error: Debug,
118    <D as EngineDatabaseInterface>::Error: Debug,
119{
120    /// Creates a new instance of `EVMPoolState` with the given attributes, with the ability to
121    /// simulate a protocol-agnostic transaction.
122    ///
123    /// See struct definition of `EVMPoolState` for attribute explanations.
124    #[allow(clippy::too_many_arguments)]
125    pub fn new(
126        id: String,
127        tokens: Vec<Bytes>,
128        component_balances: HashMap<Address, U256>,
129        balance_owner: Option<Address>,
130        contract_balances: HashMap<Address, HashMap<Address, U256>>,
131        spot_prices: HashMap<(Address, Address), f64>,
132        capabilities: HashSet<Capability>,
133        block_lasting_overwrites: HashMap<Address, Overwrites>,
134        involved_contracts: HashSet<Address>,
135        manual_updates: bool,
136        adapter_contract: TychoSimulationContract<D>,
137        disable_overwrite_tokens: HashSet<Address>,
138        self_contained_tokens: HashSet<Address>,
139        block_overrides: Option<BlockEnvOverrides>,
140    ) -> Self {
141        Self {
142            id,
143            tokens,
144            balances: component_balances,
145            balance_owner,
146            spot_prices,
147            capabilities,
148            block_lasting_overwrites,
149            involved_contracts,
150            contract_balances,
151            manual_updates,
152            adapter_contract,
153            disable_overwrite_tokens,
154            self_contained_tokens,
155            block_overrides,
156            live_overrides: None,
157        }
158    }
159
160    /// Attaches a live override channel (e.g. from a Titan pAMM provider).
161    ///
162    /// Once set, the latest snapshot is read on every simulation; see [`Self::live_overrides`].
163    pub fn set_live_overrides(&mut self, receiver: watch::Receiver<OverrideSnapshot>) {
164        self.live_overrides = Some(receiver);
165    }
166
167    /// Reads the latest live override snapshot once, if a channel is attached and still fresh.
168    ///
169    /// The `watch::Ref` guard is released immediately; the returned value is a clone. A single
170    /// simulation resolves both its storage overwrites and its block environment from this one
171    /// snapshot, so it can never mix storage from one snapshot with a block environment from
172    /// another, and never holds the channel's read lock across EVM calls.
173    ///
174    /// Returns `None` once the snapshot has passed its provider-set expiry, so an expired override
175    /// is dropped and the pool transparently reverts to Tycho's indexed state.
176    fn get_live_snapshot(&self) -> Option<OverrideSnapshot> {
177        let snapshot = self
178            .live_overrides
179            .as_ref()
180            .map(|receiver| receiver.borrow().clone())?;
181        let now = SystemTime::now()
182            .duration_since(UNIX_EPOCH)
183            .map(|elapsed| elapsed.as_secs())
184            .unwrap_or(0);
185        if snapshot.is_expired(now) {
186            return None;
187        }
188        Some(snapshot)
189    }
190
191    /// The block environment to apply to adapter simulations, resolved from a single pre-read
192    /// `live` snapshot: its block number/timestamp take precedence over the statically configured
193    /// [`Self::block_overrides`].
194    fn block_env(&self, live: Option<&OverrideSnapshot>) -> Option<BlockEnvOverrides> {
195        let base = self.block_overrides.to_owned();
196        let Some(snapshot) = live else {
197            return base;
198        };
199        if snapshot.block_number.is_none() && snapshot.block_timestamp.is_none() {
200            return base;
201        }
202        let mut overrides = base.unwrap_or_default();
203        if snapshot.block_number.is_some() {
204            overrides.number = snapshot.block_number;
205        }
206        if snapshot.block_timestamp.is_some() {
207            overrides.timestamp = snapshot.block_timestamp;
208        }
209        Some(overrides)
210    }
211
212    /// Ensures the pool supports the given capability
213    ///
214    /// # Arguments
215    ///
216    /// * `capability` - The capability that we would like to check for.
217    ///
218    /// # Returns
219    ///
220    /// * `Result<(), SimulationError>` - Returns `Ok(())` if the capability is supported, or a
221    ///   `SimulationError` otherwise.
222    fn ensure_capability(&self, capability: Capability) -> Result<(), SimulationError> {
223        if !self.capabilities.contains(&capability) {
224            return Err(SimulationError::FatalError(format!(
225                "capability {:?} not supported",
226                capability.to_string()
227            )));
228        }
229        Ok(())
230    }
231    /// Sets the spot prices for a pool for all possible pairs of the given tokens.
232    ///
233    /// # Arguments
234    ///
235    /// * `tokens` - A hashmap of `Token` instances representing the tokens to calculate spot prices
236    ///   for.
237    ///
238    /// # Returns
239    ///
240    /// * `Result<(), SimulationError>` - Returns `Ok(())` if the spot prices are successfully set,
241    ///   or a `SimulationError` if an error occurs during the calculation or processing.
242    ///
243    /// # Behavior
244    ///
245    /// This function performs the following steps:
246    /// 1. Ensures the pool has the required capability to perform price calculations.
247    /// 2. Iterates over all permutations of token pairs (sell token and buy token). For each pair:
248    ///    - Retrieves all possible overwrites, considering the maximum balance limit.
249    ///    - Calculates the sell amount limit, considering the overwrites.
250    ///    - Invokes the adapter contract's `price` function to retrieve the calculated price for
251    ///      the token pair, considering the sell amount limit.
252    ///    - Processes the price based on whether the `ScaledPrice` capability is present:
253    ///       - If `ScaledPrice` is present, uses the price directly from the adapter contract.
254    ///       - If `ScaledPrice` is absent, scales the price by adjusting for token decimals.
255    ///    - Stores the calculated price in the `spot_prices` map with the token addresses as the
256    ///      key.
257    /// 3. Returns `Ok(())` upon successful completion or a `SimulationError` upon failure.
258    ///
259    /// # Usage
260    ///
261    /// Spot prices need to be set before attempting to retrieve prices using `spot_price`.
262    ///
263    /// Tip: Setting spot prices on the pool every time the pool actually changes will result in
264    /// faster price fetching than if prices are only set immediately before attempting to retrieve
265    /// prices.
266    pub fn set_spot_prices(
267        &mut self,
268        tokens: &HashMap<Bytes, Token>,
269    ) -> Result<(), SimulationError> {
270        // Read the live snapshot once and resolve the block environment from it, so every pair
271        // (and both sub-swaps in the no-capability branch) simulates against one consistent
272        // snapshot.
273        let live_snapshot = self.get_live_snapshot();
274        let block_overrides = self.block_env(live_snapshot.as_ref());
275        match self.ensure_capability(Capability::PriceFunction) {
276            Ok(_) => {
277                for [sell_token_address, buy_token_address] in self
278                    .tokens
279                    .iter()
280                    .permutations(2)
281                    .map(|p| [p[0], p[1]])
282                {
283                    let sell_token_address = bytes_to_address(sell_token_address)?;
284                    let buy_token_address = bytes_to_address(buy_token_address)?;
285
286                    let overwrites = Some(self.get_overwrites(
287                        vec![sell_token_address, buy_token_address],
288                        *MAX_BALANCE / U256::from(100),
289                        live_snapshot.as_ref(),
290                    )?);
291
292                    let (sell_amount_limit, _) = self.get_amount_limits(
293                        vec![sell_token_address, buy_token_address],
294                        overwrites.clone(),
295                        block_overrides.clone(),
296                    )?;
297                    let price_result = self.adapter_contract.price(
298                        &self.id,
299                        sell_token_address,
300                        buy_token_address,
301                        vec![sell_amount_limit / U256::from(100)],
302                        overwrites,
303                        block_overrides.clone(),
304                    )?;
305
306                    let price = if self
307                        .capabilities
308                        .contains(&Capability::ScaledPrice)
309                    {
310                        *price_result.first().ok_or_else(|| {
311                            SimulationError::FatalError(
312                                "Calculated price array is empty".to_string(),
313                            )
314                        })?
315                    } else {
316                        let unscaled_price = price_result.first().ok_or_else(|| {
317                            SimulationError::FatalError(
318                                "Calculated price array is empty".to_string(),
319                            )
320                        })?;
321                        let sell_token_decimals = self.get_decimals(tokens, &sell_token_address)?;
322                        let buy_token_decimals = self.get_decimals(tokens, &buy_token_address)?;
323                        *unscaled_price * 10f64.powi(sell_token_decimals as i32) /
324                            10f64.powi(buy_token_decimals as i32)
325                    };
326
327                    self.spot_prices
328                        .insert((sell_token_address, buy_token_address), price);
329                }
330            }
331            Err(SimulationError::FatalError(_)) => {
332                // If the pool does not support price function, we need to calculate spot prices by
333                // swapping two amounts and use the approximation to get the derivative.
334
335                for iter_tokens in self.tokens.iter().permutations(2) {
336                    let t0 = bytes_to_address(iter_tokens[0])?;
337                    let t1 = bytes_to_address(iter_tokens[1])?;
338
339                    let overwrites = Some(self.get_overwrites(
340                        vec![t0, t1],
341                        *MAX_BALANCE / U256::from(100),
342                        live_snapshot.as_ref(),
343                    )?);
344
345                    // Calculate the first sell amount (x1) as 1% of the maximum limit.
346                    let x1 =
347                        self.get_amount_limits(
348                            vec![t0, t1],
349                            overwrites.clone(),
350                            block_overrides.clone(),
351                        )?
352                        .0 / U256::from(100);
353
354                    // Calculate the second sell amount (x2) as x1 + 1% of x1. 1.01% of the max
355                    // limit
356                    let x2 = x1 + (x1 / U256::from(100));
357
358                    // Perform a swap for the first sell amount (x1) and retrieve the received
359                    // amount (y1).
360                    let y1 = self
361                        .adapter_contract
362                        .swap(
363                            &self.id,
364                            t0,
365                            t1,
366                            false,
367                            x1,
368                            overwrites.clone(),
369                            block_overrides.clone(),
370                        )?
371                        .0
372                        .received_amount;
373
374                    // Perform a swap for the second sell amount (x2) and retrieve the received
375                    // amount (y2).
376                    let y2 = self
377                        .adapter_contract
378                        .swap(&self.id, t0, t1, false, x2, overwrites, block_overrides.clone())?
379                        .0
380                        .received_amount;
381
382                    let sell_token_decimals = self.get_decimals(tokens, &t0)?;
383                    let buy_token_decimals = self.get_decimals(tokens, &t1)?;
384
385                    let num = y2 - y1;
386                    let den = x2 - x1;
387
388                    // Calculate the marginal price, adjusting for token decimals.
389                    let token_correction =
390                        10f64.powi(sell_token_decimals as i32 - buy_token_decimals as i32);
391                    let num_f64 = u256_to_f64(num)?;
392                    let den_f64 = u256_to_f64(den)?;
393                    if den_f64 == 0.0 {
394                        return Err(SimulationError::FatalError(
395                            "Failed to compute marginal price: denominator converted to 0".into(),
396                        ));
397                    }
398                    let marginal_price = num_f64 / den_f64 * token_correction;
399
400                    self.spot_prices
401                        .insert((t0, t1), marginal_price);
402                }
403            }
404            Err(e) => return Err(e),
405        }
406
407        Ok(())
408    }
409
410    fn get_decimals(
411        &self,
412        tokens: &HashMap<Bytes, Token>,
413        sell_token_address: &Address,
414    ) -> Result<usize, SimulationError> {
415        tokens
416            .get(&Bytes::from(sell_token_address.as_slice()))
417            .map(|t| t.decimals as usize)
418            .ok_or_else(|| {
419                SimulationError::FatalError(format!(
420                    "Failed to scale spot prices! Pool: {} Token 0x{:x} is not available!",
421                    self.id, sell_token_address
422                ))
423            })
424    }
425
426    /// Retrieves the sell and buy amount limit for a given pair of tokens and the given overwrites.
427    ///
428    /// Attempting to swap an amount of the sell token that exceeds the sell amount limit is not
429    /// advised and in most cases will result in a revert.
430    ///
431    /// # Arguments
432    ///
433    /// * `tokens` - A vec of tokens, where the first token is the sell token and the second is the
434    ///   buy token. The order of tokens in the input vector is significant and determines the
435    ///   direction of the price query.
436    /// * `overwrites` - A hashmap of overwrites to apply to the simulation.
437    ///
438    /// # Returns
439    ///
440    /// * `Result<(U256,U256), SimulationError>` - Returns the sell and buy amount limit as a `U256`
441    ///   if successful, or a `SimulationError` on failure.
442    fn get_amount_limits(
443        &self,
444        tokens: Vec<Address>,
445        overwrites: Option<HashMap<Address, HashMap<U256, U256>>>,
446        block_overrides: Option<BlockEnvOverrides>,
447    ) -> Result<(U256, U256), SimulationError> {
448        let limits = self.adapter_contract.get_limits(
449            &self.id,
450            tokens[0],
451            tokens[1],
452            overwrites,
453            block_overrides,
454        )?;
455
456        Ok(limits)
457    }
458
459    /// Updates the pool state.
460    ///
461    /// It is assumed this is called on a new block. Therefore, first the pool's overwrites cache is
462    /// cleared, then the balances are updated and the spot prices are recalculated.
463    ///
464    /// # Arguments
465    ///
466    /// * `tokens` - A hashmap of token addresses to `Token` instances. This is necessary for
467    ///   calculating new spot prices.
468    /// * `balances` - A `Balances` instance containing all balance updates on the current block.
469    fn update_pool_state(
470        &mut self,
471        tokens: &HashMap<Bytes, Token>,
472        balances: &Balances,
473    ) -> Result<(), SimulationError> {
474        // clear cache
475        self.adapter_contract
476            .engine
477            .clear_temp_storage()
478            .map_err(|err| {
479                SimulationError::FatalError(format!("Failed to clear temporary storage: {err:?}",))
480            })?;
481        self.block_lasting_overwrites.clear();
482
483        // set balances
484        if !self.balances.is_empty() {
485            // Pool uses component balances for overwrites
486            if let Some(bals) = balances
487                .component_balances
488                .get(&self.id)
489            {
490                // Merge delta balances with existing balances instead of replacing them
491                // Prevents errors when delta balance changes do not affect all the pool tokens.
492                for (token, bal) in bals {
493                    let addr = bytes_to_address(token).map_err(|_| {
494                        SimulationError::FatalError(format!(
495                            "Invalid token address in balance update: {token:?}"
496                        ))
497                    })?;
498                    self.balances
499                        .insert(addr, U256::from_be_slice(bal));
500                }
501            }
502        } else {
503            // Pool uses contract balances for overwrites
504            for contract in &self.involved_contracts {
505                if let Some(bals) = balances
506                    .account_balances
507                    .get(&Bytes::from(contract.as_slice()))
508                {
509                    let contract_entry = self
510                        .contract_balances
511                        .entry(*contract)
512                        .or_default();
513                    for (token, bal) in bals {
514                        let addr = bytes_to_address(token).map_err(|_| {
515                            SimulationError::FatalError(format!(
516                                "Invalid token address in balance update: {token:?}"
517                            ))
518                        })?;
519                        contract_entry.insert(addr, U256::from_be_slice(bal));
520                    }
521                }
522            }
523        }
524
525        // reset spot prices
526        self.set_spot_prices(tokens)?;
527        Ok(())
528    }
529
530    fn get_overwrites(
531        &self,
532        tokens: Vec<Address>,
533        max_amount: U256,
534        live: Option<&OverrideSnapshot>,
535    ) -> Result<HashMap<Address, Overwrites>, SimulationError> {
536        let token_overwrites = self.get_token_overwrites(tokens, max_amount)?;
537
538        // Merge `block_lasting_overwrites` with `token_overwrites`
539        let mut merged_overwrites =
540            self.merge(self.block_lasting_overwrites.clone(), token_overwrites);
541
542        // Live overrides (e.g. Titan pAMM oracle state) take precedence on conflict.
543        if let Some(live) = live {
544            if !live.storage.is_empty() {
545                merged_overwrites = self.merge(merged_overwrites, live.storage.as_ref().clone());
546            }
547        }
548
549        Ok(merged_overwrites)
550    }
551
552    fn get_token_overwrites(
553        &self,
554        tokens: Vec<Address>,
555        max_amount: U256,
556    ) -> Result<HashMap<Address, Overwrites>, SimulationError> {
557        let sell_token = &tokens[0].clone(); //TODO: need to make it clearer from the interface
558        let mut res: Vec<HashMap<Address, Overwrites>> = Vec::new();
559        if !self
560            .capabilities
561            .contains(&Capability::TokenBalanceIndependent)
562        {
563            res.push(self.get_balance_overwrites()?);
564        }
565
566        let mut overwrites = TokenProxyOverwriteFactory::new(*sell_token, None);
567
568        overwrites.set_balance(max_amount, Address::from_slice(&*EXTERNAL_ACCOUNT.0));
569
570        // Set allowance for adapter_address to max_amount
571        overwrites.set_allowance(max_amount, self.adapter_contract.address, *EXTERNAL_ACCOUNT);
572
573        res.push(overwrites.get_overwrites());
574
575        // Self-contained tokens (see `self_contained_tokens`): pre-track EXTERNAL_ACCOUNT (the
576        // recipient) for each output token, so it's credited locally instead of bootstrapping its
577        // balance via the implementation.
578        for token in tokens.iter().skip(1) {
579            if self
580                .self_contained_tokens
581                .contains(token) &&
582                !self
583                    .disable_overwrite_tokens
584                    .contains(token)
585            {
586                let mut recipient = TokenProxyOverwriteFactory::new(*token, None);
587                recipient.set_balance(U256::ZERO, *EXTERNAL_ACCOUNT);
588                res.push(recipient.get_overwrites());
589            }
590        }
591
592        // Merge all overwrites into a single HashMap
593        Ok(res
594            .into_iter()
595            .fold(HashMap::new(), |acc, overwrite| self.merge(acc, overwrite)))
596    }
597
598    /// Gets all balance overwrites for the pool's tokens.
599    ///
600    /// If the pool uses component balances, the balances are set for the balance owner (if exists)
601    /// or for the pool itself. If the pool uses contract balances, the balances are set for the
602    /// contracts involved in the pool.
603    ///
604    /// # Returns
605    ///
606    /// * `Result<HashMap<Address, Overwrites>, SimulationError>` - Returns a hashmap of address to
607    ///   `Overwrites` if successful, or a `SimulationError` on failure.
608    fn get_balance_overwrites(&self) -> Result<HashMap<Address, Overwrites>, SimulationError> {
609        let mut balance_overwrites: HashMap<Address, Overwrites> = HashMap::new();
610
611        // Use component balances for overrides
612        let address = match self.balance_owner {
613            Some(owner) => Some(owner),
614            None if !self.contract_balances.is_empty() => None,
615            None => Some(self.id.parse().map_err(|_| {
616                SimulationError::FatalError(
617                    "Failed to get balance overwrites: Pool ID is not an address".into(),
618                )
619            })?),
620        };
621
622        if let Some(address) = address {
623            // Only override balances that are explicitly provided in self.balances
624            // This preserves existing balances for tokens not updated in delta transitions
625            for (token, bal) in &self.balances {
626                let mut overwrites = TokenProxyOverwriteFactory::new(*token, None);
627                overwrites.set_balance(*bal, address);
628                // Self-contained tokens (see `self_contained_tokens`): also grant a custom approval
629                // so `transferFrom` from the holder stays local instead of delegating to the impl.
630                if self
631                    .self_contained_tokens
632                    .contains(token)
633                {
634                    overwrites.set_has_custom_approval(address);
635                }
636                balance_overwrites.extend(overwrites.get_overwrites());
637            }
638        }
639
640        // Use contract balances for overrides (will overwrite component balances if they were set
641        // for a contract we explicitly track balances for)
642        for (contract, balances) in &self.contract_balances {
643            for (token, balance) in balances {
644                let mut overwrites = TokenProxyOverwriteFactory::new(*token, None);
645                overwrites.set_balance(*balance, *contract);
646                // Same as above: keep `transferFrom` from this contract local for self-contained
647                // tokens (see `self_contained_tokens`).
648                if self
649                    .self_contained_tokens
650                    .contains(token)
651                {
652                    overwrites.set_has_custom_approval(*contract);
653                }
654                balance_overwrites.extend(overwrites.get_overwrites());
655            }
656        }
657
658        // Apply disables for tokens that should not have any balance overrides
659        for token in &self.disable_overwrite_tokens {
660            balance_overwrites.remove(token);
661        }
662
663        Ok(balance_overwrites)
664    }
665
666    /// Merges `source` into `target` and returns the result. On a per-slot conflict, `source` wins.
667    fn merge(
668        &self,
669        mut target: HashMap<Address, Overwrites>,
670        source: HashMap<Address, Overwrites>,
671    ) -> HashMap<Address, Overwrites> {
672        for (key, source_inner) in source {
673            target
674                .entry(key)
675                .or_default()
676                .extend(source_inner);
677        }
678
679        target
680    }
681
682    #[cfg(test)]
683    pub fn get_involved_contracts(&self) -> HashSet<Address> {
684        self.involved_contracts.clone()
685    }
686
687    #[cfg(test)]
688    pub fn get_manual_updates(&self) -> bool {
689        self.manual_updates
690    }
691
692    #[cfg(test)]
693    pub fn get_balance_owner(&self) -> Option<Address> {
694        self.balance_owner
695    }
696
697    /// Get the component balances for validation purposes
698    pub fn get_balances(&self) -> &HashMap<Address, U256> {
699        &self.balances
700    }
701
702    #[cfg(test)]
703    pub fn get_block_overrides(&self) -> Option<BlockEnvOverrides> {
704        let live_snapshot = self.get_live_snapshot();
705        self.block_env(live_snapshot.as_ref())
706    }
707}
708
709impl<D> Serialize for EVMPoolState<D>
710where
711    D: EngineDatabaseInterface + Clone + Debug,
712    <D as DatabaseRef>::Error: Debug,
713    <D as EngineDatabaseInterface>::Error: Debug,
714{
715    fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
716    where
717        S: serde::Serializer,
718    {
719        Err(serde::ser::Error::custom("not supported due vm state deps"))
720    }
721}
722
723impl<'de, D> Deserialize<'de> for EVMPoolState<D>
724where
725    D: EngineDatabaseInterface + Clone + Debug,
726    <D as DatabaseRef>::Error: Debug,
727    <D as EngineDatabaseInterface>::Error: Debug,
728{
729    fn deserialize<De>(_deserializer: De) -> Result<Self, De::Error>
730    where
731        De: serde::Deserializer<'de>,
732    {
733        Err(serde::de::Error::custom("not supported due vm state deps"))
734    }
735}
736
737#[typetag::serialize]
738impl<D> ProtocolSim for EVMPoolState<D>
739where
740    D: EngineDatabaseInterface + Clone + Debug + 'static,
741    <D as DatabaseRef>::Error: Debug,
742    <D as EngineDatabaseInterface>::Error: Debug,
743{
744    fn fee(&self) -> f64 {
745        todo!()
746    }
747
748    fn spot_price(&self, base: &Token, quote: &Token) -> Result<f64, SimulationError> {
749        let base_address = bytes_to_address(&base.address)?;
750        let quote_address = bytes_to_address(&quote.address)?;
751        self.spot_prices
752            .get(&(base_address, quote_address))
753            .cloned()
754            .ok_or(SimulationError::FatalError(format!(
755                "Spot price not found for base token {base_address} and quote token {quote_address}"
756            )))
757    }
758
759    fn get_amount_out(
760        &self,
761        amount_in: BigUint,
762        token_in: &Token,
763        token_out: &Token,
764    ) -> Result<GetAmountOutResult, SimulationError> {
765        let sell_token_address = bytes_to_address(&token_in.address)?;
766        let buy_token_address = bytes_to_address(&token_out.address)?;
767        let sell_amount = U256::from_be_slice(&amount_in.to_bytes_be());
768        // Read the live snapshot once so overwrites, limits and the swap use one snapshot.
769        let live_snapshot = self.get_live_snapshot();
770        let block_overrides = self.block_env(live_snapshot.as_ref());
771        let overwrites = self.get_overwrites(
772            vec![sell_token_address, buy_token_address],
773            *MAX_BALANCE / U256::from(100),
774            live_snapshot.as_ref(),
775        )?;
776        let (sell_amount_limit, _) = self.get_amount_limits(
777            vec![sell_token_address, buy_token_address],
778            Some(overwrites.clone()),
779            block_overrides.clone(),
780        )?;
781        let (sell_amount_respecting_limit, sell_amount_exceeds_limit) = if self
782            .capabilities
783            .contains(&Capability::HardLimits) &&
784            sell_amount_limit < sell_amount
785        {
786            (sell_amount_limit, true)
787        } else {
788            (sell_amount, false)
789        };
790
791        let overwrites_with_sell_limit = self.get_overwrites(
792            vec![sell_token_address, buy_token_address],
793            sell_amount_limit,
794            live_snapshot.as_ref(),
795        )?;
796        let complete_overwrites = self.merge(overwrites, overwrites_with_sell_limit);
797
798        let (trade, state_changes) = self.adapter_contract.swap(
799            &self.id,
800            sell_token_address,
801            buy_token_address,
802            false,
803            sell_amount_respecting_limit,
804            Some(complete_overwrites),
805            block_overrides,
806        )?;
807
808        let mut new_state = self.clone();
809
810        // Apply state changes to the new state
811        for (address, state_update) in state_changes {
812            if let Some(storage) = state_update.storage {
813                let block_overwrites = new_state
814                    .block_lasting_overwrites
815                    .entry(address)
816                    .or_default();
817                for (slot, value) in storage {
818                    let slot = U256::from_str(&slot.to_string()).map_err(|_| {
819                        SimulationError::FatalError("Failed to decode slot index".to_string())
820                    })?;
821                    let value = U256::from_str(&value.to_string()).map_err(|_| {
822                        SimulationError::FatalError("Failed to decode slot overwrite".to_string())
823                    })?;
824                    block_overwrites.insert(slot, value);
825                }
826            }
827        }
828
829        // Update spot prices
830        let tokens = HashMap::from([
831            (token_in.address.clone(), token_in.clone()),
832            (token_out.address.clone(), token_out.clone()),
833        ]);
834        let _ = new_state.set_spot_prices(&tokens);
835
836        let buy_amount = trade.received_amount;
837
838        if sell_amount_exceeds_limit {
839            return Err(SimulationError::InvalidInput(
840                format!("Sell amount exceeds limit {sell_amount_limit}"),
841                Some(GetAmountOutResult::new(
842                    u256_to_biguint(buy_amount),
843                    u256_to_biguint(trade.gas_used),
844                    Box::new(new_state.clone()),
845                )),
846            ));
847        }
848        Ok(GetAmountOutResult::new(
849            u256_to_biguint(buy_amount),
850            u256_to_biguint(trade.gas_used),
851            Box::new(new_state.clone()),
852        ))
853    }
854
855    fn get_limits(
856        &self,
857        sell_token: Bytes,
858        buy_token: Bytes,
859    ) -> Result<(BigUint, BigUint), SimulationError> {
860        let sell_token = bytes_to_address(&sell_token)?;
861        let buy_token = bytes_to_address(&buy_token)?;
862        let live_snapshot = self.get_live_snapshot();
863        let overwrites = self.get_overwrites(
864            vec![sell_token, buy_token],
865            *MAX_BALANCE / U256::from(100),
866            live_snapshot.as_ref(),
867        )?;
868        let limits = self.get_amount_limits(
869            vec![sell_token, buy_token],
870            Some(overwrites),
871            self.block_env(live_snapshot.as_ref()),
872        )?;
873        Ok((u256_to_biguint(limits.0), u256_to_biguint(limits.1)))
874    }
875
876    fn delta_transition(
877        &mut self,
878        delta: ProtocolStateDelta,
879        tokens: &HashMap<Bytes, Token>,
880        balances: &Balances,
881    ) -> Result<(), TransitionError> {
882        if let Some(block_number) = delta
883            .updated_attributes
884            .get("override_block_number")
885        {
886            let number = <[u8; 8]>::try_from(block_number.as_ref())
887                .map(u64::from_be_bytes)
888                .map_err(|_| {
889                    TransitionError::DecodeError(
890                        "override_block_number attribute must be an 8-byte big-endian u64"
891                            .to_string(),
892                    )
893                })?;
894            self.block_overrides
895                .get_or_insert_with(BlockEnvOverrides::default)
896                .number = Some(number);
897        }
898
899        if let Some(block_timestamp) = delta
900            .updated_attributes
901            .get("override_block_timestamp")
902        {
903            let timestamp = <[u8; 8]>::try_from(block_timestamp.as_ref())
904                .map(u64::from_be_bytes)
905                .map_err(|_| {
906                    TransitionError::DecodeError(
907                        "override_block_timestamp attribute must be an 8-byte big-endian u64"
908                            .to_string(),
909                    )
910                })?;
911            self.block_overrides
912                .get_or_insert_with(BlockEnvOverrides::default)
913                .timestamp = Some(timestamp);
914        }
915
916        if self.manual_updates {
917            // Directly check for "update_marker" in `updated_attributes`
918            if let Some(marker) = delta
919                .updated_attributes
920                .get("update_marker")
921            {
922                // Assuming `marker` is of type `Bytes`, check its value for "truthiness"
923                if !marker.is_empty() && marker[0] != 0 {
924                    self.update_pool_state(tokens, balances)?;
925                }
926            }
927        } else {
928            self.update_pool_state(tokens, balances)?;
929        }
930
931        Ok(())
932    }
933
934    fn query_pool_swap(
935        &self,
936        params: &tycho_common::simulation::protocol_sim::QueryPoolSwapParams,
937    ) -> Result<tycho_common::simulation::protocol_sim::PoolSwap, SimulationError> {
938        crate::evm::query_pool_swap::query_pool_swap(self, params)
939    }
940
941    fn clone_box(&self) -> Box<dyn ProtocolSim> {
942        Box::new(self.clone())
943    }
944
945    fn as_any(&self) -> &dyn Any {
946        self
947    }
948
949    fn as_any_mut(&mut self) -> &mut dyn Any {
950        self
951    }
952
953    fn eq(&self, other: &dyn ProtocolSim) -> bool {
954        if let Some(other_state) = other
955            .as_any()
956            .downcast_ref::<EVMPoolState<PreCachedDB>>()
957        {
958            self.id == other_state.id
959        } else {
960            false
961        }
962    }
963
964    /// Implemented manually because `typetag` macro not supports generics
965    fn typetag_deserialize(&self) {
966        // https://github.com/dtolnay/typetag/blob/21ae0d40c9f73443a20204ab4a134441355b52f7/impl/src/tagged_trait.rs#L140
967        unreachable!("Only to catch missing typetag attribute on impl blocks. Not called.")
968    }
969}
970
971#[cfg(test)]
972mod tests {
973    use std::default::Default;
974
975    use num_traits::One;
976    use revm::{
977        primitives::KECCAK_EMPTY,
978        state::{AccountInfo, Bytecode},
979    };
980    use serde_json::Value;
981    use tycho_client::feed::BlockHeader;
982    use tycho_common::models::Chain;
983
984    use super::*;
985    use crate::evm::{
986        engine_db::{create_engine, SHARED_TYCHO_DB},
987        protocol::vm::{
988            constants::{BALANCER_V2, ERC20_PROXY_BYTECODE},
989            state_builder::EVMPoolStateBuilder,
990        },
991        simulation::SimulationEngine,
992        tycho_models::AccountUpdate,
993    };
994
995    fn dai() -> Token {
996        Token::new(
997            &Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(),
998            "DAI",
999            18,
1000            0,
1001            &[Some(10_000)],
1002            Chain::Ethereum,
1003            100,
1004        )
1005    }
1006
1007    fn bal() -> Token {
1008        Token::new(
1009            &Bytes::from_str("0xba100000625a3754423978a60c9317c58a424e3d").unwrap(),
1010            "BAL",
1011            18,
1012            0,
1013            &[Some(10_000)],
1014            Chain::Ethereum,
1015            100,
1016        )
1017    }
1018
1019    fn dai_addr() -> Address {
1020        bytes_to_address(&dai().address).unwrap()
1021    }
1022
1023    fn bal_addr() -> Address {
1024        bytes_to_address(&bal().address).unwrap()
1025    }
1026
1027    async fn setup_pool_state() -> EVMPoolState<PreCachedDB> {
1028        let data_str = include_str!("assets/balancer_contract_storage_block_20463609.json");
1029        let data: Value = serde_json::from_str(data_str).expect("Failed to parse JSON");
1030
1031        let accounts: Vec<AccountUpdate> = serde_json::from_value(data["accounts"].clone())
1032            .expect("Expected accounts to match AccountUpdate structure");
1033
1034        let db = SHARED_TYCHO_DB.clone();
1035        let engine: SimulationEngine<_> = create_engine(db.clone(), false).unwrap();
1036
1037        let block = BlockHeader {
1038            number: 20463609,
1039            hash: Bytes::from_str(
1040                "0x4315fd1afc25cc2ebc72029c543293f9fd833eeb305e2e30159459c827733b1b",
1041            )
1042            .unwrap(),
1043            timestamp: 1722875891,
1044            ..Default::default()
1045        };
1046
1047        for account in accounts.clone() {
1048            engine
1049                .state
1050                .init_account(
1051                    account.address,
1052                    AccountInfo {
1053                        balance: account.balance.unwrap_or_default(),
1054                        nonce: 0u64,
1055                        code_hash: KECCAK_EMPTY,
1056                        code: account
1057                            .code
1058                            .clone()
1059                            .map(|arg0: Vec<u8>| Bytecode::new_raw(arg0.into())),
1060                    },
1061                    None,
1062                    false,
1063                )
1064                .expect("Failed to initialize account");
1065        }
1066        db.update(accounts, Some(block))
1067            .unwrap();
1068
1069        let tokens = vec![dai().address, bal().address];
1070        for token in &tokens {
1071            engine
1072                .state
1073                .init_account(
1074                    bytes_to_address(token).unwrap(),
1075                    AccountInfo {
1076                        balance: U256::from(0),
1077                        nonce: 0,
1078                        code_hash: KECCAK_EMPTY,
1079                        code: Some(Bytecode::new_raw(ERC20_PROXY_BYTECODE.into())),
1080                    },
1081                    None,
1082                    true,
1083                )
1084                .expect("Failed to initialize account");
1085        }
1086
1087        let block = BlockHeader {
1088            number: 18485417,
1089            hash: Bytes::from_str(
1090                "0x28d41d40f2ac275a4f5f621a636b9016b527d11d37d610a45ac3a821346ebf8c",
1091            )
1092            .expect("Invalid block hash"),
1093            timestamp: 0,
1094            ..Default::default()
1095        };
1096        db.update(vec![], Some(block.clone()))
1097            .unwrap();
1098
1099        let pool_id: String =
1100            "0x4626d81b3a1711beb79f4cecff2413886d461677000200000000000000000011".into();
1101
1102        let stateless_contracts = HashMap::from([(
1103            String::from("0x3de27efa2f1aa663ae5d458857e731c129069f29"),
1104            Some(Vec::new()),
1105        )]);
1106
1107        let balances = HashMap::from([
1108            (dai_addr(), U256::from_str("178754012737301807104").unwrap()),
1109            (bal_addr(), U256::from_str("91082987763369885696").unwrap()),
1110        ]);
1111        let adapter_address =
1112            Address::from_str("0xA2C5C98A892fD6656a7F39A2f63228C0Bc846270").unwrap();
1113
1114        EVMPoolStateBuilder::new(pool_id, tokens, adapter_address)
1115            .balances(balances)
1116            .balance_owner(Address::from_str("0xBA12222222228d8Ba445958a75a0704d566BF2C8").unwrap())
1117            .adapter_contract_bytecode(Bytecode::new_raw(BALANCER_V2.into()))
1118            .stateless_contracts(stateless_contracts)
1119            .build(SHARED_TYCHO_DB.clone())
1120            .await
1121            .expect("Failed to build pool state")
1122    }
1123
1124    #[tokio::test]
1125    async fn test_init() {
1126        // Clear DB from this test to prevent interference from other tests
1127        SHARED_TYCHO_DB
1128            .clear()
1129            .expect("Failed to cleared SHARED TX");
1130        let pool_state = setup_pool_state().await;
1131
1132        let expected_capabilities = vec![
1133            Capability::SellSide,
1134            Capability::BuySide,
1135            Capability::PriceFunction,
1136            Capability::HardLimits,
1137        ]
1138        .into_iter()
1139        .collect::<HashSet<_>>();
1140
1141        let capabilities_adapter_contract = pool_state
1142            .adapter_contract
1143            .get_capabilities(
1144                &pool_state.id,
1145                bytes_to_address(&pool_state.tokens[0]).unwrap(),
1146                bytes_to_address(&pool_state.tokens[1]).unwrap(),
1147            )
1148            .unwrap();
1149
1150        assert_eq!(capabilities_adapter_contract, expected_capabilities.clone());
1151
1152        let capabilities_state = pool_state.clone().capabilities;
1153
1154        assert_eq!(capabilities_state, expected_capabilities.clone());
1155
1156        for capability in expected_capabilities.clone() {
1157            assert!(pool_state
1158                .clone()
1159                .ensure_capability(capability)
1160                .is_ok());
1161        }
1162
1163        assert!(pool_state
1164            .clone()
1165            .ensure_capability(Capability::MarginalPrice)
1166            .is_err());
1167
1168        // Verify all tokens are initialized in the engine
1169        let engine_accounts = pool_state
1170            .adapter_contract
1171            .engine
1172            .state
1173            .clone()
1174            .get_account_storage()
1175            .expect("Failed to get account storage");
1176        for token in pool_state.tokens.clone() {
1177            let account = engine_accounts
1178                .get_account_info(&bytes_to_address(&token).unwrap())
1179                .unwrap();
1180            assert_eq!(account.balance, U256::from(0));
1181            assert_eq!(account.nonce, 0u64);
1182            assert_eq!(account.code_hash, KECCAK_EMPTY);
1183            assert!(account.code.is_some());
1184        }
1185
1186        // Verify external account is initialized in the engine
1187        let external_account = engine_accounts
1188            .get_account_info(&EXTERNAL_ACCOUNT)
1189            .unwrap();
1190        assert_eq!(external_account.balance, U256::from(*MAX_BALANCE));
1191        assert_eq!(external_account.nonce, 0u64);
1192        assert_eq!(external_account.code_hash, KECCAK_EMPTY);
1193        assert!(external_account.code.is_none());
1194    }
1195
1196    #[tokio::test]
1197    async fn test_get_amount_out() -> Result<(), Box<dyn std::error::Error>> {
1198        let pool_state = setup_pool_state().await;
1199
1200        let result = pool_state
1201            .get_amount_out(BigUint::from_str("1000000000000000000").unwrap(), &dai(), &bal())
1202            .unwrap();
1203        let new_state = result
1204            .new_state
1205            .as_any()
1206            .downcast_ref::<EVMPoolState<PreCachedDB>>()
1207            .unwrap();
1208        assert_eq!(result.amount, BigUint::from_str("137780051463393923").unwrap());
1209        assert_ne!(new_state.spot_prices, pool_state.spot_prices);
1210        assert!(pool_state
1211            .block_lasting_overwrites
1212            .is_empty());
1213        Ok(())
1214    }
1215
1216    #[tokio::test]
1217    async fn test_sequential_get_amount_outs() {
1218        let pool_state = setup_pool_state().await;
1219
1220        let result = pool_state
1221            .get_amount_out(BigUint::from_str("1000000000000000000").unwrap(), &dai(), &bal())
1222            .unwrap();
1223        let new_state = result
1224            .new_state
1225            .as_any()
1226            .downcast_ref::<EVMPoolState<PreCachedDB>>()
1227            .unwrap();
1228        assert_eq!(result.amount, BigUint::from_str("137780051463393923").unwrap());
1229        assert_ne!(new_state.spot_prices, pool_state.spot_prices);
1230
1231        let new_result = new_state
1232            .get_amount_out(BigUint::from_str("1000000000000000000").unwrap(), &dai(), &bal())
1233            .unwrap();
1234        let new_state_second_swap = new_result
1235            .new_state
1236            .as_any()
1237            .downcast_ref::<EVMPoolState<PreCachedDB>>()
1238            .unwrap();
1239
1240        assert_eq!(new_result.amount, BigUint::from_str("136964651490065626").unwrap());
1241        assert_ne!(new_state_second_swap.spot_prices, new_state.spot_prices);
1242    }
1243
1244    #[tokio::test]
1245    async fn test_get_amount_out_dust() {
1246        let pool_state = setup_pool_state().await;
1247
1248        let result = pool_state
1249            .get_amount_out(BigUint::one(), &dai(), &bal())
1250            .unwrap();
1251
1252        let _ = result
1253            .new_state
1254            .as_any()
1255            .downcast_ref::<EVMPoolState<PreCachedDB>>()
1256            .unwrap();
1257        assert_eq!(result.amount, BigUint::ZERO);
1258    }
1259
1260    #[tokio::test]
1261    async fn test_get_amount_out_sell_limit() {
1262        let pool_state = setup_pool_state().await;
1263
1264        let result = pool_state.get_amount_out(
1265            // sell limit is 100279494253364362835
1266            BigUint::from_str("100379494253364362835").unwrap(),
1267            &dai(),
1268            &bal(),
1269        );
1270
1271        assert!(result.is_err());
1272
1273        match result {
1274            Err(SimulationError::InvalidInput(msg1, amount_out_result)) => {
1275                assert_eq!(msg1, "Sell amount exceeds limit 100279494253364362835");
1276                assert!(amount_out_result.is_some());
1277            }
1278            _ => panic!("Test failed: was expecting an Err(SimulationError::RetryDifferentInput(_, _)) value"),
1279        }
1280    }
1281
1282    #[tokio::test]
1283    async fn test_get_amount_limits() {
1284        let pool_state = setup_pool_state().await;
1285
1286        let overwrites = pool_state
1287            .get_overwrites(
1288                vec![
1289                    bytes_to_address(&pool_state.tokens[0]).unwrap(),
1290                    bytes_to_address(&pool_state.tokens[1]).unwrap(),
1291                ],
1292                *MAX_BALANCE / U256::from(100),
1293                None,
1294            )
1295            .unwrap();
1296        let (dai_limit, _) = pool_state
1297            .get_amount_limits(
1298                vec![dai_addr(), bal_addr()],
1299                Some(overwrites.clone()),
1300                pool_state.block_env(None),
1301            )
1302            .unwrap();
1303        assert_eq!(dai_limit, U256::from_str("100279494253364362835").unwrap());
1304
1305        let (bal_limit, _) = pool_state
1306            .get_amount_limits(
1307                vec![
1308                    bytes_to_address(&pool_state.tokens[1]).unwrap(),
1309                    bytes_to_address(&pool_state.tokens[0]).unwrap(),
1310                ],
1311                Some(overwrites),
1312                pool_state.block_env(None),
1313            )
1314            .unwrap();
1315        assert_eq!(bal_limit, U256::from_str("13997408640689987484").unwrap());
1316    }
1317
1318    #[tokio::test]
1319    async fn test_set_spot_prices() {
1320        let mut pool_state = setup_pool_state().await;
1321
1322        pool_state
1323            .set_spot_prices(
1324                &vec![bal(), dai()]
1325                    .into_iter()
1326                    .map(|t| (t.address.clone(), t))
1327                    .collect(),
1328            )
1329            .unwrap();
1330
1331        let dai_bal_spot_price = pool_state
1332            .spot_prices
1333            .get(&(
1334                bytes_to_address(&pool_state.tokens[0]).unwrap(),
1335                bytes_to_address(&pool_state.tokens[1]).unwrap(),
1336            ))
1337            .unwrap();
1338        let bal_dai_spot_price = pool_state
1339            .spot_prices
1340            .get(&(
1341                bytes_to_address(&pool_state.tokens[1]).unwrap(),
1342                bytes_to_address(&pool_state.tokens[0]).unwrap(),
1343            ))
1344            .unwrap();
1345        assert_eq!(dai_bal_spot_price, &0.137_778_914_319_047_9);
1346        assert_eq!(bal_dai_spot_price, &7.071_503_245_428_246);
1347    }
1348
1349    #[tokio::test]
1350    async fn test_set_spot_prices_without_capability() {
1351        // Tests set Spot Prices functions when the pool doesn't have PriceFunction capability
1352        let mut pool_state = setup_pool_state().await;
1353
1354        pool_state
1355            .capabilities
1356            .remove(&Capability::PriceFunction);
1357
1358        pool_state
1359            .set_spot_prices(
1360                &vec![bal(), dai()]
1361                    .into_iter()
1362                    .map(|t| (t.address.clone(), t))
1363                    .collect(),
1364            )
1365            .unwrap();
1366
1367        let dai_bal_spot_price = pool_state
1368            .spot_prices
1369            .get(&(
1370                bytes_to_address(&pool_state.tokens[0]).unwrap(),
1371                bytes_to_address(&pool_state.tokens[1]).unwrap(),
1372            ))
1373            .unwrap();
1374        let bal_dai_spot_price = pool_state
1375            .spot_prices
1376            .get(&(
1377                bytes_to_address(&pool_state.tokens[1]).unwrap(),
1378                bytes_to_address(&pool_state.tokens[0]).unwrap(),
1379            ))
1380            .unwrap();
1381        assert_eq!(dai_bal_spot_price, &0.13736685496467538);
1382        assert_eq!(bal_dai_spot_price, &7.050354297665408);
1383    }
1384
1385    #[tokio::test]
1386    async fn test_get_balance_overwrites_with_component_balances() {
1387        let pool_state: EVMPoolState<PreCachedDB> = setup_pool_state().await;
1388
1389        let overwrites = pool_state
1390            .get_balance_overwrites()
1391            .unwrap();
1392
1393        let dai_address = dai_addr();
1394        let bal_address = bal_addr();
1395        assert!(overwrites.contains_key(&dai_address));
1396        assert!(overwrites.contains_key(&bal_address));
1397    }
1398
1399    #[tokio::test]
1400    async fn test_get_balance_overwrites_with_contract_balances() {
1401        let mut pool_state: EVMPoolState<PreCachedDB> = setup_pool_state().await;
1402
1403        let contract_address =
1404            Address::from_str("0xBA12222222228d8Ba445958a75a0704d566BF2C8").unwrap();
1405
1406        // Ensure no component balances are used
1407        pool_state.balances.clear();
1408        pool_state.balance_owner = None;
1409
1410        // Set contract balances
1411        let dai_address = dai_addr();
1412        let bal_address = bal_addr();
1413        pool_state.contract_balances = HashMap::from([(
1414            contract_address,
1415            HashMap::from([
1416                (dai_address, U256::from_str("7500000000000000000000").unwrap()), // 7500 DAI
1417                (bal_address, U256::from_str("1500000000000000000000").unwrap()), // 1500 BAL
1418            ]),
1419        )]);
1420
1421        let overwrites = pool_state
1422            .get_balance_overwrites()
1423            .unwrap();
1424
1425        assert!(overwrites.contains_key(&dai_address));
1426        assert!(overwrites.contains_key(&bal_address));
1427    }
1428
1429    #[tokio::test]
1430    async fn test_balance_merging_during_delta_transition() {
1431        use std::str::FromStr;
1432
1433        let mut pool_state = setup_pool_state().await;
1434        let pool_id = pool_state.id.clone();
1435
1436        // Test the balance merging logic more directly
1437        // Setup initial balances including DAI and BAL (which the pool already knows about)
1438        let dai_addr = dai_addr();
1439        let bal_addr = bal_addr();
1440        let new_token = Address::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); // WETH
1441
1442        // Clear and setup clean initial state
1443        pool_state.balances.clear();
1444        pool_state
1445            .balances
1446            .insert(dai_addr, U256::from(1000000000u64));
1447        pool_state
1448            .balances
1449            .insert(bal_addr, U256::from(2000000000u64));
1450        pool_state
1451            .balances
1452            .insert(new_token, U256::from(3000000000u64));
1453
1454        // Create tokens mapping including the existing DAI and BAL
1455        let mut tokens = HashMap::new();
1456        tokens.insert(dai().address.clone(), dai());
1457        tokens.insert(bal().address.clone(), bal());
1458
1459        // Simulate a delta transition with only DAI balance update (missing BAL and new_token)
1460        let mut component_balances = HashMap::new();
1461        let mut delta_balances = HashMap::new();
1462        // Only update DAI balance, leave others unchanged in delta
1463        delta_balances.insert(dai().address.clone(), Bytes::from(vec![0x77, 0x35, 0x94, 0x00])); // 2000000000 (updated value)
1464        component_balances.insert(pool_id.clone(), delta_balances);
1465
1466        let balances = Balances { component_balances, account_balances: HashMap::new() };
1467
1468        // Record initial balance count
1469        let initial_balance_count = pool_state.balances.len();
1470        assert_eq!(initial_balance_count, 3);
1471
1472        // Apply delta transition
1473        pool_state
1474            .update_pool_state(&tokens, &balances)
1475            .unwrap();
1476
1477        // Verify that all 3 balances are preserved (BAL and new_token should still be there)
1478        assert_eq!(
1479            pool_state.balances.len(),
1480            3,
1481            "All balances should be preserved after delta transition"
1482        );
1483        assert!(
1484            pool_state
1485                .balances
1486                .contains_key(&dai_addr),
1487            "DAI balance should be present"
1488        );
1489        assert!(
1490            pool_state
1491                .balances
1492                .contains_key(&bal_addr),
1493            "BAL balance should be present"
1494        );
1495        assert!(
1496            pool_state
1497                .balances
1498                .contains_key(&new_token),
1499            "New token balance should be preserved from before delta"
1500        );
1501
1502        // Verify that updated token (DAI) has new value
1503        assert_eq!(
1504            pool_state.balances[&dai_addr],
1505            U256::from(2000000000u64),
1506            "DAI balance should be updated"
1507        );
1508
1509        // Verify that non-updated tokens retain their original values
1510        assert_eq!(
1511            pool_state.balances[&bal_addr],
1512            U256::from(2000000000u64),
1513            "BAL balance should be unchanged"
1514        );
1515        assert_eq!(
1516            pool_state.balances[&new_token],
1517            U256::from(3000000000u64),
1518            "New token balance should be unchanged"
1519        );
1520    }
1521
1522    #[tokio::test]
1523    async fn test_delta_transition_updates_block_overrides() {
1524        let mut pool_state = setup_pool_state().await;
1525        pool_state.manual_updates = true;
1526        pool_state.block_overrides = None;
1527
1528        let delta = ProtocolStateDelta {
1529            component_id: pool_state.id.clone(),
1530            updated_attributes: HashMap::from([
1531                ("override_block_number".to_string(), Bytes::from(123_u64.to_be_bytes().to_vec())),
1532                (
1533                    "override_block_timestamp".to_string(),
1534                    Bytes::from(456_u64.to_be_bytes().to_vec()),
1535                ),
1536            ]),
1537            deleted_attributes: HashSet::new(),
1538        };
1539
1540        pool_state
1541            .delta_transition(delta, &HashMap::new(), &Balances::default())
1542            .unwrap();
1543
1544        assert_eq!(
1545            pool_state.block_overrides,
1546            Some(BlockEnvOverrides { number: Some(123), timestamp: Some(456) })
1547        );
1548    }
1549
1550    #[tokio::test]
1551    async fn test_delta_transition_updates_partial_block_overrides() {
1552        let mut pool_state = setup_pool_state().await;
1553        pool_state.manual_updates = true;
1554        pool_state.block_overrides =
1555            Some(BlockEnvOverrides { number: Some(123), timestamp: Some(456) });
1556
1557        let delta = ProtocolStateDelta {
1558            component_id: pool_state.id.clone(),
1559            updated_attributes: HashMap::from([(
1560                "override_block_number".to_string(),
1561                Bytes::from(789_u64.to_be_bytes().to_vec()),
1562            )]),
1563            deleted_attributes: HashSet::new(),
1564        };
1565
1566        pool_state
1567            .delta_transition(delta, &HashMap::new(), &Balances::default())
1568            .unwrap();
1569
1570        assert_eq!(
1571            pool_state.block_overrides,
1572            Some(BlockEnvOverrides { number: Some(789), timestamp: Some(456) })
1573        );
1574    }
1575
1576    #[test]
1577    fn should_not_panic_at_typetag_deserialize() {
1578        let deserialized: Result<Box<dyn ProtocolSim>, _> = serde_json::from_str(
1579            r#"{"protocol":"EVMPoolState","state":{"reserve_0":1,"reserve_1":2}}"#,
1580        );
1581
1582        assert!(deserialized.is_err());
1583    }
1584
1585    /// A live snapshot's block number/timestamp take precedence over the static block overrides,
1586    /// field by field, while an absent or empty snapshot leaves them untouched.
1587    #[tokio::test]
1588    async fn test_block_env_prefers_live_overrides() {
1589        let mut pool_state = setup_pool_state().await;
1590        pool_state.block_overrides =
1591            Some(BlockEnvOverrides { number: Some(100), timestamp: Some(1_000) });
1592
1593        // No live snapshot: the statically configured overrides are used unchanged.
1594        assert_eq!(
1595            pool_state.block_env(None),
1596            Some(BlockEnvOverrides { number: Some(100), timestamp: Some(1_000) })
1597        );
1598
1599        // A live snapshot with neither field set does not touch the static overrides.
1600        let empty = OverrideSnapshot::default();
1601        assert_eq!(
1602            pool_state.block_env(Some(&empty)),
1603            Some(BlockEnvOverrides { number: Some(100), timestamp: Some(1_000) })
1604        );
1605
1606        // Present live fields win; unset live fields keep the static value.
1607        let live = OverrideSnapshot {
1608            block_number: Some(200),
1609            block_timestamp: None,
1610            ..Default::default()
1611        };
1612        assert_eq!(
1613            pool_state.block_env(Some(&live)),
1614            Some(BlockEnvOverrides { number: Some(200), timestamp: Some(1_000) })
1615        );
1616
1617        // With no static overrides, the live block environment stands on its own.
1618        pool_state.block_overrides = None;
1619        let live = OverrideSnapshot {
1620            block_number: Some(300),
1621            block_timestamp: Some(3_000),
1622            ..Default::default()
1623        };
1624        assert_eq!(
1625            pool_state.block_env(Some(&live)),
1626            Some(BlockEnvOverrides { number: Some(300), timestamp: Some(3_000) })
1627        );
1628    }
1629
1630    /// Live storage is merged into the computed overwrites: a fresh contract is added, a value on a
1631    /// slot the baseline already sets is overridden (live wins on conflict), and empty live storage
1632    /// is a no-op.
1633    #[tokio::test]
1634    async fn test_get_overwrites_applies_live_storage() {
1635        let pool_state = setup_pool_state().await;
1636        let tokens = vec![dai_addr(), bal_addr()];
1637        let max = *MAX_BALANCE / U256::from(100);
1638
1639        let baseline = pool_state
1640            .get_overwrites(tokens.clone(), max, None)
1641            .unwrap();
1642
1643        // An empty live snapshot leaves the overwrites unchanged.
1644        let empty = OverrideSnapshot::default();
1645        assert_eq!(
1646            pool_state
1647                .get_overwrites(tokens.clone(), max, Some(&empty))
1648                .unwrap(),
1649            baseline
1650        );
1651
1652        // Pick an address/slot the baseline already sets, so we can assert live wins the conflict.
1653        let (conflict_addr, conflict_slot, baseline_val) = {
1654            let (addr, slots) = baseline
1655                .iter()
1656                .next()
1657                .expect("baseline has overwrites");
1658            let (slot, val) = slots
1659                .iter()
1660                .next()
1661                .expect("address has slots");
1662            (*addr, *slot, *val)
1663        };
1664        let sentinel = baseline_val + U256::from(1);
1665        let fresh = Address::from([0xAB; 20]);
1666        assert!(
1667            !baseline.contains_key(&fresh),
1668            "fresh address must originate from the live snapshot"
1669        );
1670
1671        let live = OverrideSnapshot {
1672            storage: std::sync::Arc::new(HashMap::from([
1673                (fresh, HashMap::from([(U256::from(7), U256::from(123))])),
1674                (conflict_addr, HashMap::from([(conflict_slot, sentinel)])),
1675            ])),
1676            ..Default::default()
1677        };
1678        let with_live = pool_state
1679            .get_overwrites(tokens, max, Some(&live))
1680            .unwrap();
1681
1682        assert_eq!(
1683            with_live
1684                .get(&fresh)
1685                .and_then(|slots| slots.get(&U256::from(7))),
1686            Some(&U256::from(123)),
1687            "live storage for a fresh contract must be merged in"
1688        );
1689        assert_eq!(
1690            with_live
1691                .get(&conflict_addr)
1692                .and_then(|slots| slots.get(&conflict_slot)),
1693            Some(&sentinel),
1694            "live override must win on slot conflict"
1695        );
1696    }
1697
1698    /// `get_live_snapshot` returns the attached snapshot only while it is fresh: an expired one is
1699    /// dropped (so the pool reverts to indexed state), and no channel means no snapshot. Expiry is
1700    /// pinned to the extremes (`1` = long past, `u64::MAX` = effectively never) so the assertions
1701    /// are independent of the wall clock.
1702    #[tokio::test]
1703    async fn test_get_live_snapshot_drops_expired() {
1704        let mut pool_state = setup_pool_state().await;
1705
1706        // No channel attached: nothing to read.
1707        assert!(pool_state.get_live_snapshot().is_none());
1708
1709        // A snapshot without an expiry never goes stale.
1710        let never =
1711            OverrideSnapshot { block_number: Some(1), expires_at: None, ..Default::default() };
1712        let (_never_tx, never_rx) = watch::channel(never);
1713        pool_state.set_live_overrides(never_rx);
1714        assert_eq!(
1715            pool_state
1716                .get_live_snapshot()
1717                .and_then(|snapshot| snapshot.block_number),
1718            Some(1)
1719        );
1720
1721        // A snapshot whose expiry is far in the future is returned.
1722        let fresh = OverrideSnapshot {
1723            block_number: Some(42),
1724            expires_at: Some(u64::MAX),
1725            ..Default::default()
1726        };
1727        let (_fresh_tx, fresh_rx) = watch::channel(fresh);
1728        pool_state.set_live_overrides(fresh_rx);
1729        assert_eq!(
1730            pool_state
1731                .get_live_snapshot()
1732                .and_then(|snapshot| snapshot.block_number),
1733            Some(42)
1734        );
1735
1736        // A snapshot whose expiry is in the past is dropped.
1737        let expired =
1738            OverrideSnapshot { block_number: Some(42), expires_at: Some(1), ..Default::default() };
1739        let (_expired_tx, expired_rx) = watch::channel(expired);
1740        pool_state.set_live_overrides(expired_rx);
1741        assert!(pool_state.get_live_snapshot().is_none());
1742    }
1743}