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