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    sync::{PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard},
8    time::{SystemTime, UNIX_EPOCH},
9};
10
11use alloy::primitives::{Address, U256};
12use itertools::Itertools;
13use num_bigint::BigUint;
14use revm::DatabaseRef;
15use rustc_hash::FxHashMap;
16use serde::{Deserialize, Serialize};
17use tokio::sync::watch;
18use tracing::{debug, warn};
19use tycho_common::{
20    dto::ProtocolStateDelta,
21    models::token::Token,
22    simulation::{
23        errors::{SimulationError, TransitionError},
24        protocol_sim::{Balances, GetAmountOutResult, ProtocolSim},
25    },
26    Bytes,
27};
28
29use super::{
30    constants::{EXTERNAL_ACCOUNT, MAX_BALANCE},
31    erc20_token::{Overwrites, TokenProxyOverwriteFactory},
32    models::Capability,
33    tycho_simulation_contract::TychoSimulationContract,
34};
35use crate::evm::{
36    engine_db::{engine_db_interface::EngineDatabaseInterface, tycho_db::PreCachedDB},
37    override_stream::{FailurePolicy, OverrideSnapshot},
38    protocol::{
39        u256_num::{u256_to_biguint, u256_to_f64},
40        utils::bytes_to_address,
41    },
42    simulation::BlockEnvOverrides,
43};
44
45/// Cached derived values (spot prices, swap limits) for one pool-state version.
46///
47/// Both maps live behind a single lock so the invalidation policy has one home:
48/// - [`Self::invalidate`] drops everything; call it whenever the pool's own state changes (the
49///   per-block update, a post-swap child state, attaching live overrides).
50/// - Cached limits additionally carry the [`LimitContext`] they were computed under; a context
51///   mismatch at read time is a miss, so limits self-correct when the engine's current block or the
52///   pool's block-env overrides move without any pool-level event.
53///
54/// Spot prices deliberately carry no block context: they are eagerly re-warmed each block by
55/// `update_pool_state` and are allowed to go stale on paths that skip it (the documented
56/// `manual_updates` contract).
57///
58/// Lock poisoning is recovered from rather than propagated: the critical sections only perform
59/// map reads/inserts/clears, so a poisoned guard cannot hold a torn value, while propagating
60/// would turn one panicked thread into a panic in every worker sharing the pool.
61#[derive(Default)]
62struct DerivedCaches {
63    inner: RwLock<CacheState>,
64}
65
66#[derive(Clone, Default)]
67struct CacheState {
68    /// Keyed by `(sell, buy)` — equivalently `(base, quote)` as used by
69    /// [`ProtocolSim::spot_price`].
70    spot_prices: FxHashMap<(Address, Address), f64>,
71    /// `(sell_limit, buy_limit)` keyed by `(sell, buy)`, valid only under `limit_context`.
72    limits: FxHashMap<(Address, Address), (U256, U256)>,
73    /// Context the cached `limits` were computed under; `None` while `limits` is empty.
74    limit_context: Option<LimitContext>,
75}
76
77/// Simulation context a cached limit depends on besides the pool's own state: the engine's
78/// current block and the pool's resolved block-env overrides.
79#[derive(Clone, Debug, PartialEq)]
80struct LimitContext {
81    /// `(number, timestamp, partial_block_index)` of the engine's current block.
82    block: Option<(u64, u64, Option<u32>)>,
83    block_env: Option<BlockEnvOverrides>,
84}
85
86impl DerivedCaches {
87    fn read(&self) -> RwLockReadGuard<'_, CacheState> {
88        self.inner
89            .read()
90            .unwrap_or_else(PoisonError::into_inner)
91    }
92
93    fn write(&self) -> RwLockWriteGuard<'_, CacheState> {
94        self.inner
95            .write()
96            .unwrap_or_else(PoisonError::into_inner)
97    }
98
99    fn get_spot_price(&self, key: (Address, Address)) -> Option<f64> {
100        self.read()
101            .spot_prices
102            .get(&key)
103            .copied()
104    }
105
106    fn insert_spot_price(&self, key: (Address, Address), price: f64) {
107        self.write()
108            .spot_prices
109            .insert(key, price);
110    }
111
112    fn get_limits(&self, key: (Address, Address), context: &LimitContext) -> Option<(U256, U256)> {
113        let state = self.read();
114        if state.limit_context.as_ref() != Some(context) {
115            return None;
116        }
117        state.limits.get(&key).copied()
118    }
119
120    fn insert_limits(&self, key: (Address, Address), limits: (U256, U256), context: LimitContext) {
121        let mut state = self.write();
122        if state.limit_context.as_ref() != Some(&context) {
123            state.limits.clear();
124            state.limit_context = Some(context);
125        }
126        state.limits.insert(key, limits);
127    }
128
129    /// Drops all cached values. The pool's own state changed, so nothing derived from the
130    /// previous version may be served again.
131    fn invalidate(&self) {
132        let mut state = self.write();
133        state.spot_prices.clear();
134        state.limits.clear();
135        state.limit_context = None;
136    }
137}
138
139impl Clone for DerivedCaches {
140    /// Deep copy: a clone owns an independent cache, never a shared lock.
141    fn clone(&self) -> Self {
142        Self { inner: RwLock::new(self.read().clone()) }
143    }
144}
145
146#[derive(Clone)]
147pub struct EVMPoolState<D: EngineDatabaseInterface + Clone + Debug>
148where
149    <D as DatabaseRef>::Error: Debug,
150    <D as EngineDatabaseInterface>::Error: Debug,
151{
152    /// The pool's identifier
153    id: String,
154    /// The pool's token's addresses
155    pub tokens: Vec<Bytes>,
156    /// The pool's component balances.
157    balances: HashMap<Address, U256>,
158    /// The contract address for where protocol balances are stored (i.e. a vault contract).
159    /// If given, balances will be overwritten here instead of on the pool contract during
160    /// simulations. This has been deprecated in favor of `contract_balances`.
161    #[deprecated(note = "Use contract_balances instead")]
162    balance_owner: Option<Address>,
163    /// Derived values cached for the current pool-state version; see [`DerivedCaches`].
164    ///
165    /// Regular pools read through both caches: `spot_price` lazily populates on a miss and
166    /// `update_pool_state` eagerly re-warms each block. Pools with live overrides only ever
167    /// populate spot prices eagerly (their override-derived prices change sub-block, so
168    /// `spot_price` errors on a miss instead of computing against a possibly newer snapshot)
169    /// and bypass the limit cache entirely.
170    caches: DerivedCaches,
171    /// The supported capabilities of this pool
172    capabilities: HashSet<Capability>,
173    /// Storage overwrites that will be applied to all simulations. They will be cleared
174    /// when ``update_pool_state`` is called, i.e. usually at each block. Hence, the name.
175    block_lasting_overwrites: HashMap<Address, Overwrites>,
176    /// A set of all contract addresses involved in the simulation of this pool.
177    involved_contracts: HashSet<Address>,
178    /// A map of contracts to their token balances.
179    contract_balances: HashMap<Address, HashMap<Address, U256>>,
180    /// Indicates if the protocol uses custom update rules and requires update
181    /// triggers to recalculate spot prices ect. Default is to update on all changes on
182    /// the pool.
183    manual_updates: bool,
184    /// Caller (`tx.origin`) for the adapter's `price()` query; `None` defaults to
185    /// `EXTERNAL_ACCOUNT`. Set per protocol in the decoder (see `spot_price_caller`).
186    spot_price_caller: Option<Address>,
187    /// The adapter contract. This is used to interact with the protocol when running simulations
188    adapter_contract: TychoSimulationContract<D>,
189    /// Tokens for which balance overwrites should be disabled.
190    disable_overwrite_tokens: HashSet<Address>,
191    /// Tokens whose protocol does not emit token contract storage (e.g. FermiSwap), so they are
192    /// bare `TokenProxy` accounts with no implementation in the shared DB. For these, the
193    /// overwrites keep transfers in the proxy's local bookkeeping — holders get a custom approval
194    /// and the swap recipient a custom balance — so a `transferFrom` never delegates to a real
195    /// implementation another VM protocol (curve, balancer) mounted on the same shared token,
196    /// which would revert with `SafeERC20FailedOperation` (ENG-6161). Rebase/fee tokens in
197    /// `disable_overwrite_tokens` are excluded.
198    self_contained_tokens: HashSet<Address>,
199    /// Block context overrides applied to this pool's adapter simulations.
200    block_overrides: Option<BlockEnvOverrides>,
201    /// Live per-block VM overrides (e.g. Titan pAMM oracle prices) read at simulation time.
202    ///
203    /// When set, the latest [`OverrideSnapshot`] is merged into the pool's storage overwrites and
204    /// block environment on every simulation, so sub-block updates are reflected without a Tycho
205    /// block update. Takes precedence over [`Self::block_lasting_overwrites`] and
206    /// [`Self::block_overrides`] on conflict.
207    live_overrides: Option<watch::Receiver<OverrideSnapshot>>,
208}
209
210impl<D> Debug for EVMPoolState<D>
211where
212    D: EngineDatabaseInterface + Clone + Debug,
213    <D as DatabaseRef>::Error: Debug,
214    <D as EngineDatabaseInterface>::Error: Debug,
215{
216    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217        f.debug_struct("EVMPoolState")
218            .field("id", &self.id)
219            .field("tokens", &self.tokens)
220            .field("balances", &self.balances)
221            .field("involved_contracts", &self.involved_contracts)
222            .field("contract_balances", &self.contract_balances)
223            .finish_non_exhaustive()
224    }
225}
226
227impl<D> EVMPoolState<D>
228where
229    D: EngineDatabaseInterface + Clone + Debug + 'static,
230    <D as DatabaseRef>::Error: Debug,
231    <D as EngineDatabaseInterface>::Error: Debug,
232{
233    /// Creates a new instance of `EVMPoolState` with the given attributes, with the ability to
234    /// simulate a protocol-agnostic transaction.
235    ///
236    /// See struct definition of `EVMPoolState` for attribute explanations.
237    #[allow(clippy::too_many_arguments)]
238    pub fn new(
239        id: String,
240        tokens: Vec<Bytes>,
241        component_balances: HashMap<Address, U256>,
242        balance_owner: Option<Address>,
243        contract_balances: HashMap<Address, HashMap<Address, U256>>,
244        spot_prices: HashMap<(Address, Address), f64>,
245        capabilities: HashSet<Capability>,
246        block_lasting_overwrites: HashMap<Address, Overwrites>,
247        involved_contracts: HashSet<Address>,
248        manual_updates: bool,
249        adapter_contract: TychoSimulationContract<D>,
250        disable_overwrite_tokens: HashSet<Address>,
251        self_contained_tokens: HashSet<Address>,
252        block_overrides: Option<BlockEnvOverrides>,
253        spot_price_caller: Option<Address>,
254    ) -> Self {
255        let caches = DerivedCaches::default();
256        caches.write().spot_prices = spot_prices.into_iter().collect();
257        Self {
258            id,
259            tokens,
260            balances: component_balances,
261            balance_owner,
262            caches,
263            capabilities,
264            block_lasting_overwrites,
265            involved_contracts,
266            contract_balances,
267            manual_updates,
268            adapter_contract,
269            disable_overwrite_tokens,
270            self_contained_tokens,
271            block_overrides,
272            spot_price_caller,
273            live_overrides: None,
274        }
275    }
276
277    /// Attaches a live override channel (e.g. from a Titan pAMM provider).
278    ///
279    /// Once set, the latest snapshot is read on every simulation; see [`Self::live_overrides`].
280    pub fn set_live_overrides(&mut self, receiver: watch::Receiver<OverrideSnapshot>) {
281        self.live_overrides = Some(receiver);
282        // Values cached before the channel was attached were computed without overrides and
283        // would otherwise be served by the cache-read fast paths.
284        self.caches.invalidate();
285    }
286
287    /// A copy of this pool representing a new state version: identical inputs, fresh caches.
288    ///
289    /// Use this instead of `clone()` when the copy's state is about to diverge (e.g. the
290    /// post-swap state returned by `get_amount_out`), so the previous version's derived values
291    /// are never copied only to be discarded.
292    fn clone_invalidated(&self) -> Self {
293        Self {
294            id: self.id.clone(),
295            tokens: self.tokens.clone(),
296            balances: self.balances.clone(),
297            balance_owner: self.balance_owner,
298            caches: DerivedCaches::default(),
299            capabilities: self.capabilities.clone(),
300            block_lasting_overwrites: self.block_lasting_overwrites.clone(),
301            involved_contracts: self.involved_contracts.clone(),
302            contract_balances: self.contract_balances.clone(),
303            manual_updates: self.manual_updates,
304            adapter_contract: self.adapter_contract.clone(),
305            disable_overwrite_tokens: self.disable_overwrite_tokens.clone(),
306            self_contained_tokens: self.self_contained_tokens.clone(),
307            block_overrides: self.block_overrides.clone(),
308            spot_price_caller: self.spot_price_caller,
309            live_overrides: self.live_overrides.clone(),
310        }
311    }
312
313    /// `(number, timestamp, partial_block_index)` of the engine DB's current block, used to
314    /// stamp cached limits with the block they were computed under.
315    fn current_engine_block(&self) -> Option<(u64, u64, Option<u32>)> {
316        self.adapter_contract
317            .engine
318            .state
319            .get_current_block()
320            .map(|block| (block.number, block.timestamp, block.partial_block_index))
321    }
322
323    /// Reads the latest live override snapshot once, if a channel is attached and still fresh.
324    ///
325    /// The `watch::Ref` guard is released immediately; the returned value is a clone. A single
326    /// simulation resolves both its storage overwrites and its block environment from this one
327    /// snapshot, so it can never mix storage from one snapshot with a block environment from
328    /// another, and never holds the channel's read lock across EVM calls.
329    ///
330    /// Returns `None` once the snapshot has passed its provider-set expiry, so an expired override
331    /// is dropped and the pool transparently reverts to Tycho's indexed state.
332    fn get_live_snapshot(&self) -> Option<OverrideSnapshot> {
333        let snapshot = self
334            .live_overrides
335            .as_ref()
336            .map(|receiver| receiver.borrow().clone())?;
337        let now = SystemTime::now()
338            .duration_since(UNIX_EPOCH)
339            .map(|elapsed| elapsed.as_secs())
340            .unwrap_or(0);
341        if snapshot.is_expired(now) {
342            return None;
343        }
344        Some(snapshot)
345    }
346
347    /// Runs `simulate` against `live_snapshot` and, when the snapshot's provider opted into
348    /// [`FailurePolicy::FallbackToIndexedState`], retries a failure once on the plain indexed
349    /// state.
350    ///
351    /// `InvalidInput` failures are never retried: the simulation itself succeeded and the input
352    /// was merely clamped to the pool's limit. Note that under overrides the limit is itself
353    /// override-derived (it reflects the size the live maker quote covers), and that clamp is
354    /// treated as authoritative rather than retried against the indexed pool's larger limit.
355    fn run_with_indexed_fallback<T>(
356        pool_id: &str,
357        operation: &str,
358        live_snapshot: Option<&OverrideSnapshot>,
359        mut simulate: impl FnMut(Option<&OverrideSnapshot>) -> Result<T, SimulationError>,
360    ) -> Result<T, SimulationError> {
361        let attempt = simulate(live_snapshot);
362        let Err(error) = &attempt else { return attempt };
363        if matches!(error, SimulationError::InvalidInput(..)) {
364            return attempt;
365        }
366        let Some(snapshot) = live_snapshot
367            .filter(|snapshot| snapshot.failure_policy == FailurePolicy::FallbackToIndexedState)
368        else {
369            return attempt;
370        };
371        debug!(
372            pool = %pool_id,
373            %error,
374            snapshot_block = ?snapshot.block_number,
375            snapshot_ts = ?snapshot.block_timestamp,
376            expires_at = ?snapshot.expires_at,
377            override_accounts = snapshot.storage.len(),
378            "{operation} failed with live overrides; retrying on indexed state"
379        );
380        let retry = simulate(None);
381        if let Err(retry_error) = &retry {
382            warn!(pool = %pool_id, %retry_error, "{operation} retry on indexed state also failed");
383        }
384        retry
385    }
386
387    /// The block environment to apply to adapter simulations, resolved from a single pre-read
388    /// `live` snapshot: its block number/timestamp take precedence over the statically configured
389    /// [`Self::block_overrides`].
390    fn block_env(&self, live: Option<&OverrideSnapshot>) -> Option<BlockEnvOverrides> {
391        let base = self.block_overrides.to_owned();
392        let Some(snapshot) = live else {
393            return base;
394        };
395        if snapshot.block_number.is_none() && snapshot.block_timestamp.is_none() {
396            return base;
397        }
398        let mut overrides = base.unwrap_or_default();
399        if snapshot.block_number.is_some() {
400            overrides.number = snapshot.block_number;
401        }
402        if snapshot.block_timestamp.is_some() {
403            overrides.timestamp = snapshot.block_timestamp;
404        }
405        Some(overrides)
406    }
407
408    /// Sets the spot prices for a pool for all possible pairs of the given tokens.
409    ///
410    /// # Arguments
411    ///
412    /// * `tokens` - A hashmap of `Token` instances representing the tokens to calculate spot prices
413    ///   for.
414    ///
415    /// # Returns
416    ///
417    /// * `Result<(), SimulationError>` - Returns `Ok(())` if the spot prices are successfully set,
418    ///   or a `SimulationError` if an error occurs during the calculation or processing.
419    ///
420    /// # Behavior
421    ///
422    /// This function performs the following steps:
423    /// 1. Ensures the pool has the required capability to perform price calculations.
424    /// 2. Iterates over all permutations of token pairs (sell token and buy token). For each pair:
425    ///    - Retrieves all possible overwrites, considering the maximum balance limit.
426    ///    - Calculates the sell amount limit, considering the overwrites.
427    ///    - Invokes the adapter contract's `price` function to retrieve the calculated price for
428    ///      the token pair, considering the sell amount limit.
429    ///    - Processes the price based on whether the `ScaledPrice` capability is present:
430    ///       - If `ScaledPrice` is present, uses the price directly from the adapter contract.
431    ///       - If `ScaledPrice` is absent, scales the price by adjusting for token decimals.
432    ///    - Stores the calculated price in the `spot_prices` map with the token addresses as the
433    ///      key.
434    /// 3. Returns `Ok(())` upon successful completion or a `SimulationError` upon failure.
435    ///
436    /// # Usage
437    ///
438    /// Spot prices need to be set before attempting to retrieve prices using `spot_price`.
439    ///
440    /// Tip: Setting spot prices on the pool every time the pool actually changes will result in
441    /// faster price fetching than if prices are only set immediately before attempting to retrieve
442    /// prices.
443    pub fn set_spot_prices(&self, tokens: &HashMap<Bytes, Token>) -> Result<(), SimulationError> {
444        // Read the live snapshot once, so every pair (and both sub-swaps in the no-capability
445        // branch) simulates against one consistent snapshot.
446        let live_snapshot = self.get_live_snapshot();
447        Self::run_with_indexed_fallback(
448            &self.id,
449            "Spot prices",
450            live_snapshot.as_ref(),
451            |snapshot| self.set_spot_prices_with(tokens, snapshot),
452        )
453    }
454
455    /// Computes the spot price for a single `(sell, buy)` pair against `live_snapshot`'s overrides
456    /// (or the plain indexed state when `None`): via the adapter's `price` function when the pool
457    /// has the `PriceFunction` capability, otherwise approximated as a two-swap finite difference.
458    /// Does not touch the cache.
459    fn compute_spot_price(
460        &self,
461        tokens: &HashMap<Bytes, Token>,
462        sell_token_address: Address,
463        buy_token_address: Address,
464        live_snapshot: Option<&OverrideSnapshot>,
465    ) -> Result<f64, SimulationError> {
466        let block_overrides = self.block_env(live_snapshot);
467        if self
468            .capabilities
469            .contains(&Capability::PriceFunction)
470        {
471            let overwrites = Some(self.get_overwrites(
472                vec![sell_token_address, buy_token_address],
473                *MAX_BALANCE / U256::from(100),
474                live_snapshot,
475            )?);
476
477            let (sell_amount_limit, _) = self.get_amount_limits(
478                vec![sell_token_address, buy_token_address],
479                overwrites.clone(),
480                block_overrides.clone(),
481            )?;
482            let price_result = self.adapter_contract.price(
483                &self.id,
484                sell_token_address,
485                buy_token_address,
486                vec![sell_amount_limit / U256::from(100)],
487                overwrites,
488                self.spot_price_caller,
489                block_overrides.clone(),
490            )?;
491
492            if self
493                .capabilities
494                .contains(&Capability::ScaledPrice)
495            {
496                Ok(*price_result.first().ok_or_else(|| {
497                    SimulationError::FatalError("Calculated price array is empty".to_string())
498                })?)
499            } else {
500                let unscaled_price = price_result.first().ok_or_else(|| {
501                    SimulationError::FatalError("Calculated price array is empty".to_string())
502                })?;
503                let sell_token_decimals = self.get_decimals(tokens, &sell_token_address)?;
504                let buy_token_decimals = self.get_decimals(tokens, &buy_token_address)?;
505                Ok(*unscaled_price * 10f64.powi(sell_token_decimals as i32) /
506                    10f64.powi(buy_token_decimals as i32))
507            }
508        } else {
509            // If the pool does not support price function, we need to calculate spot prices by
510            // swapping two amounts and use the approximation to get the derivative.
511            let overwrites = Some(self.get_overwrites(
512                vec![sell_token_address, buy_token_address],
513                *MAX_BALANCE / U256::from(100),
514                live_snapshot,
515            )?);
516
517            // Calculate the first sell amount (x1) as 1% of the maximum limit.
518            let x1 =
519                self.get_amount_limits(
520                    vec![sell_token_address, buy_token_address],
521                    overwrites.clone(),
522                    block_overrides.clone(),
523                )?
524                .0 / U256::from(100);
525
526            // Calculate the second sell amount (x2) as x1 + 1% of x1. 1.01% of the max limit
527            let x2 = x1 + (x1 / U256::from(100));
528
529            // Perform a swap for the first sell amount (x1) and retrieve the received amount
530            // (y1).
531            let y1 = self
532                .adapter_contract
533                .swap(
534                    &self.id,
535                    sell_token_address,
536                    buy_token_address,
537                    false,
538                    x1,
539                    overwrites.clone(),
540                    block_overrides.clone(),
541                )?
542                .0
543                .received_amount;
544
545            // Perform a swap for the second sell amount (x2) and retrieve the received amount
546            // (y2).
547            let y2 = self
548                .adapter_contract
549                .swap(
550                    &self.id,
551                    sell_token_address,
552                    buy_token_address,
553                    false,
554                    x2,
555                    overwrites,
556                    block_overrides.clone(),
557                )?
558                .0
559                .received_amount;
560
561            let sell_token_decimals = self.get_decimals(tokens, &sell_token_address)?;
562            let buy_token_decimals = self.get_decimals(tokens, &buy_token_address)?;
563
564            let num = y2 - y1;
565            let den = x2 - x1;
566
567            // Calculate the marginal price, adjusting for token decimals.
568            let token_correction =
569                10f64.powi(sell_token_decimals as i32 - buy_token_decimals as i32);
570            let num_f64 = u256_to_f64(num)?;
571            let den_f64 = u256_to_f64(den)?;
572            if den_f64 == 0.0 {
573                return Err(SimulationError::FatalError(
574                    "Failed to compute marginal price: denominator converted to 0".into(),
575                ));
576            }
577            Ok(num_f64 / den_f64 * token_correction)
578        }
579    }
580
581    /// Computes and stores spot prices against `live_snapshot`'s overrides (or the plain indexed
582    /// state when `None`).
583    fn set_spot_prices_with(
584        &self,
585        tokens: &HashMap<Bytes, Token>,
586        live_snapshot: Option<&OverrideSnapshot>,
587    ) -> Result<(), SimulationError> {
588        for [sell_token_address, buy_token_address] in self
589            .tokens
590            .iter()
591            .permutations(2)
592            .map(|p| [p[0], p[1]])
593        {
594            let sell_token_address = bytes_to_address(sell_token_address)?;
595            let buy_token_address = bytes_to_address(buy_token_address)?;
596
597            let price = self.compute_spot_price(
598                tokens,
599                sell_token_address,
600                buy_token_address,
601                live_snapshot,
602            )?;
603
604            self.caches
605                .insert_spot_price((sell_token_address, buy_token_address), price);
606        }
607
608        Ok(())
609    }
610
611    fn get_decimals(
612        &self,
613        tokens: &HashMap<Bytes, Token>,
614        sell_token_address: &Address,
615    ) -> Result<usize, SimulationError> {
616        tokens
617            .get(&Bytes::from(sell_token_address.as_slice()))
618            .map(|t| t.decimals as usize)
619            .ok_or_else(|| {
620                SimulationError::FatalError(format!(
621                    "Failed to scale spot prices! Pool: {} Token 0x{:x} is not available!",
622                    self.id, sell_token_address
623                ))
624            })
625    }
626
627    /// Retrieves the sell and buy amount limit for a given pair of tokens and the given overwrites.
628    ///
629    /// Attempting to swap an amount of the sell token that exceeds the sell amount limit is not
630    /// advised and in most cases will result in a revert.
631    ///
632    /// Cached per `(sell, buy)` pair, stamped with the engine block and block-env overrides the
633    /// value was computed under; a stamp mismatch is a miss, so limits that depend on time (e.g.
634    /// an amplification ramp) self-correct each block even if the pool receives no deltas. The
635    /// cached value is assumed independent of the caller's overwrite amounts (guarded by
636    /// `test_get_limits_cached_matches_fresh`). Pools with live overrides derive limits from a
637    /// sub-block snapshot, so caching is bypassed and the adapter is called directly; see
638    /// [`Self::live_overrides`].
639    ///
640    /// # Arguments
641    ///
642    /// * `tokens` - A vec of tokens, where the first token is the sell token and the second is the
643    ///   buy token. The order of tokens in the input vector is significant and determines the
644    ///   direction of the price query.
645    /// * `overwrites` - A hashmap of overwrites to apply to the simulation.
646    ///
647    /// # Returns
648    ///
649    /// * `Result<(U256,U256), SimulationError>` - Returns the sell and buy amount limit as a `U256`
650    ///   if successful, or a `SimulationError` on failure.
651    fn get_amount_limits(
652        &self,
653        tokens: Vec<Address>,
654        overwrites: Option<HashMap<Address, HashMap<U256, U256>>>,
655        block_overrides: Option<BlockEnvOverrides>,
656    ) -> Result<(U256, U256), SimulationError> {
657        if self.live_overrides.is_some() {
658            return self.adapter_contract.get_limits(
659                &self.id,
660                tokens[0],
661                tokens[1],
662                overwrites,
663                block_overrides,
664            );
665        }
666
667        let key = (tokens[0], tokens[1]);
668        let context =
669            LimitContext { block: self.current_engine_block(), block_env: block_overrides.clone() };
670        if let Some(limits) = self.caches.get_limits(key, &context) {
671            return Ok(limits);
672        }
673
674        let limits = self.adapter_contract.get_limits(
675            &self.id,
676            tokens[0],
677            tokens[1],
678            overwrites,
679            block_overrides,
680        )?;
681        self.caches
682            .insert_limits(key, limits, context);
683
684        Ok(limits)
685    }
686
687    /// Updates the pool state.
688    ///
689    /// It is assumed this is called on a new block. Therefore, first the pool's overwrites cache is
690    /// cleared, then the balances are updated and the spot prices are recalculated.
691    ///
692    /// # Arguments
693    ///
694    /// * `tokens` - A hashmap of token addresses to `Token` instances. This is necessary for
695    ///   calculating new spot prices.
696    /// * `balances` - A `Balances` instance containing all balance updates on the current block.
697    fn update_pool_state(
698        &mut self,
699        tokens: &HashMap<Bytes, Token>,
700        balances: &Balances,
701    ) -> Result<(), SimulationError> {
702        // clear cache
703        self.adapter_contract
704            .engine
705            .clear_temp_storage()
706            .map_err(|err| {
707                SimulationError::FatalError(format!("Failed to clear temporary storage: {err:?}",))
708            })?;
709        self.block_lasting_overwrites.clear();
710        self.caches.invalidate();
711
712        // Set balances. Component balances and contract balances are refreshed independently:
713        // hybrid pools (e.g. Balancer V3) carry both, and `get_balance_overwrites` layers
714        // contract balances over component balances. Skipping the contract-balance refresh
715        // whenever component balances exist would freeze contract balances at their snapshot
716        // values while the contract's indexed storage keeps advancing, which breaks any
717        // simulation that compares `balanceOf` against stored reserves (e.g. Balancer V3
718        // `settle` reverting with `BalanceNotSettled`).
719        if let Some(bals) = balances
720            .component_balances
721            .get(&self.id)
722        {
723            // Merge delta balances with existing balances instead of replacing them
724            // Prevents errors when delta balance changes do not affect all the pool tokens.
725            for (token, bal) in bals {
726                let addr = bytes_to_address(token).map_err(|_| {
727                    SimulationError::FatalError(format!(
728                        "Invalid token address in balance update: {token:?}"
729                    ))
730                })?;
731                self.balances
732                    .insert(addr, U256::from_be_slice(bal));
733            }
734        }
735        for contract in &self.involved_contracts {
736            if let Some(bals) = balances
737                .account_balances
738                .get(&Bytes::from(contract.as_slice()))
739            {
740                let contract_entry = self
741                    .contract_balances
742                    .entry(*contract)
743                    .or_default();
744                for (token, bal) in bals {
745                    let addr = bytes_to_address(token).map_err(|_| {
746                        SimulationError::FatalError(format!(
747                            "Invalid token address in balance update: {token:?}"
748                        ))
749                    })?;
750                    contract_entry.insert(addr, U256::from_be_slice(bal));
751                }
752            }
753        }
754
755        // reset spot prices
756        self.set_spot_prices(tokens)?;
757        Ok(())
758    }
759
760    fn get_overwrites(
761        &self,
762        tokens: Vec<Address>,
763        max_amount: U256,
764        live: Option<&OverrideSnapshot>,
765    ) -> Result<HashMap<Address, Overwrites>, SimulationError> {
766        let token_overwrites = self.get_token_overwrites(tokens, max_amount)?;
767
768        // Merge `block_lasting_overwrites` with `token_overwrites`
769        let mut merged_overwrites =
770            self.merge(self.block_lasting_overwrites.clone(), token_overwrites);
771
772        // Live overrides (e.g. Titan pAMM oracle state) take precedence on conflict.
773        if let Some(live) = live {
774            if !live.storage.is_empty() {
775                merged_overwrites = self.merge(merged_overwrites, live.storage.as_ref().clone());
776            }
777        }
778
779        Ok(merged_overwrites)
780    }
781
782    fn get_token_overwrites(
783        &self,
784        tokens: Vec<Address>,
785        max_amount: U256,
786    ) -> Result<HashMap<Address, Overwrites>, SimulationError> {
787        let sell_token = &tokens[0].clone(); //TODO: need to make it clearer from the interface
788        let mut res: Vec<HashMap<Address, Overwrites>> = Vec::new();
789        if !self
790            .capabilities
791            .contains(&Capability::TokenBalanceIndependent)
792        {
793            res.push(self.get_balance_overwrites()?);
794        }
795
796        let mut overwrites = TokenProxyOverwriteFactory::new(*sell_token, None);
797
798        overwrites.set_balance(max_amount, Address::from_slice(&*EXTERNAL_ACCOUNT.0));
799
800        // Set allowance for adapter_address to max_amount
801        overwrites.set_allowance(max_amount, self.adapter_contract.address, *EXTERNAL_ACCOUNT);
802
803        res.push(overwrites.get_overwrites());
804
805        // Self-contained tokens (see `self_contained_tokens`): pre-track EXTERNAL_ACCOUNT (the
806        // recipient) for each output token, so it's credited locally instead of bootstrapping its
807        // balance via the implementation.
808        for token in tokens.iter().skip(1) {
809            if self
810                .self_contained_tokens
811                .contains(token) &&
812                !self
813                    .disable_overwrite_tokens
814                    .contains(token)
815            {
816                let mut recipient = TokenProxyOverwriteFactory::new(*token, None);
817                recipient.set_balance(U256::ZERO, *EXTERNAL_ACCOUNT);
818                res.push(recipient.get_overwrites());
819            }
820        }
821
822        // Merge all overwrites into a single HashMap
823        Ok(res
824            .into_iter()
825            .fold(HashMap::new(), |acc, overwrite| self.merge(acc, overwrite)))
826    }
827
828    /// Gets all balance overwrites for the pool's tokens.
829    ///
830    /// If the pool uses component balances, the balances are set for the balance owner (if exists)
831    /// or for the pool itself. If the pool uses contract balances, the balances are set for the
832    /// contracts involved in the pool.
833    ///
834    /// # Returns
835    ///
836    /// * `Result<HashMap<Address, Overwrites>, SimulationError>` - Returns a hashmap of address to
837    ///   `Overwrites` if successful, or a `SimulationError` on failure.
838    fn get_balance_overwrites(&self) -> Result<HashMap<Address, Overwrites>, SimulationError> {
839        let mut balance_overwrites: HashMap<Address, Overwrites> = HashMap::new();
840
841        // Use component balances for overrides
842        let address = match self.balance_owner {
843            Some(owner) => Some(owner),
844            None if !self.contract_balances.is_empty() => None,
845            None => Some(self.id.parse().map_err(|_| {
846                SimulationError::FatalError(
847                    "Failed to get balance overwrites: Pool ID is not an address".into(),
848                )
849            })?),
850        };
851
852        if let Some(address) = address {
853            // Only override balances that are explicitly provided in self.balances
854            // This preserves existing balances for tokens not updated in delta transitions
855            for (token, bal) in &self.balances {
856                let mut overwrites = TokenProxyOverwriteFactory::new(*token, None);
857                overwrites.set_balance(*bal, address);
858                // Self-contained tokens (see `self_contained_tokens`): also grant a custom approval
859                // so `transferFrom` from the holder stays local instead of delegating to the impl.
860                if self
861                    .self_contained_tokens
862                    .contains(token)
863                {
864                    overwrites.set_has_custom_approval(address);
865                }
866                balance_overwrites.extend(overwrites.get_overwrites());
867            }
868        }
869
870        // Use contract balances for overrides (will overwrite component balances if they were set
871        // for a contract we explicitly track balances for)
872        for (contract, balances) in &self.contract_balances {
873            for (token, balance) in balances {
874                let mut overwrites = TokenProxyOverwriteFactory::new(*token, None);
875                overwrites.set_balance(*balance, *contract);
876                // Same as above: keep `transferFrom` from this contract local for self-contained
877                // tokens (see `self_contained_tokens`).
878                if self
879                    .self_contained_tokens
880                    .contains(token)
881                {
882                    overwrites.set_has_custom_approval(*contract);
883                }
884                balance_overwrites.extend(overwrites.get_overwrites());
885            }
886        }
887
888        // Apply disables for tokens that should not have any balance overrides
889        for token in &self.disable_overwrite_tokens {
890            balance_overwrites.remove(token);
891        }
892
893        Ok(balance_overwrites)
894    }
895
896    /// Merges `source` into `target` and returns the result. On a per-slot conflict, `source` wins.
897    fn merge(
898        &self,
899        mut target: HashMap<Address, Overwrites>,
900        source: HashMap<Address, Overwrites>,
901    ) -> HashMap<Address, Overwrites> {
902        for (key, source_inner) in source {
903            target
904                .entry(key)
905                .or_default()
906                .extend(source_inner);
907        }
908
909        target
910    }
911
912    #[cfg(test)]
913    pub fn get_involved_contracts(&self) -> HashSet<Address> {
914        self.involved_contracts.clone()
915    }
916
917    #[cfg(test)]
918    pub fn get_manual_updates(&self) -> bool {
919        self.manual_updates
920    }
921
922    #[cfg(test)]
923    pub fn get_spot_price_caller(&self) -> Option<Address> {
924        self.spot_price_caller
925    }
926
927    /// Simulates a sell of `amount_in` against `live_snapshot`'s overrides (or the plain indexed
928    /// state when `None`); see [`ProtocolSim::get_amount_out`] for the caller-facing contract.
929    fn get_amount_out_with(
930        &self,
931        amount_in: &BigUint,
932        token_in: &Token,
933        token_out: &Token,
934        live_snapshot: Option<&OverrideSnapshot>,
935    ) -> Result<GetAmountOutResult, SimulationError> {
936        let sell_token_address = bytes_to_address(&token_in.address)?;
937        let buy_token_address = bytes_to_address(&token_out.address)?;
938        let sell_amount = U256::from_be_slice(&amount_in.to_bytes_be());
939        let block_overrides = self.block_env(live_snapshot);
940        let overwrites = self.get_overwrites(
941            vec![sell_token_address, buy_token_address],
942            *MAX_BALANCE / U256::from(100),
943            live_snapshot,
944        )?;
945        let (sell_amount_limit, _) = self.get_amount_limits(
946            vec![sell_token_address, buy_token_address],
947            Some(overwrites.clone()),
948            block_overrides.clone(),
949        )?;
950        let (sell_amount_respecting_limit, sell_amount_exceeds_limit) = if self
951            .capabilities
952            .contains(&Capability::HardLimits) &&
953            sell_amount_limit < sell_amount
954        {
955            (sell_amount_limit, true)
956        } else {
957            (sell_amount, false)
958        };
959
960        let overwrites_with_sell_limit = self.get_overwrites(
961            vec![sell_token_address, buy_token_address],
962            sell_amount_limit,
963            live_snapshot,
964        )?;
965        let complete_overwrites = self.merge(overwrites, overwrites_with_sell_limit);
966
967        let (trade, state_changes) = self.adapter_contract.swap(
968            &self.id,
969            sell_token_address,
970            buy_token_address,
971            false,
972            sell_amount_respecting_limit,
973            Some(complete_overwrites),
974            block_overrides,
975        )?;
976
977        let mut new_state = self.clone_invalidated();
978
979        // Apply state changes to the new state
980        for (address, state_update) in state_changes {
981            if let Some(storage) = state_update.storage {
982                let block_overwrites = new_state
983                    .block_lasting_overwrites
984                    .entry(address)
985                    .or_default();
986                for (slot, value) in storage {
987                    let slot = U256::from_str(&slot.to_string()).map_err(|_| {
988                        SimulationError::FatalError("Failed to decode slot index".to_string())
989                    })?;
990                    let value = U256::from_str(&value.to_string()).map_err(|_| {
991                        SimulationError::FatalError("Failed to decode slot overwrite".to_string())
992                    })?;
993                    block_overwrites.insert(slot, value);
994                }
995            }
996        }
997
998        if new_state.live_overrides.is_some() {
999            // Override pools serve only eagerly-warmed spot prices (`spot_price` errors on a
1000            // miss rather than computing against a possibly newer snapshot), so the returned
1001            // state must be warmed here; failures fall back to the indexed state inside
1002            // `set_spot_prices`.
1003            let tokens = HashMap::from([
1004                (token_in.address.clone(), token_in.clone()),
1005                (token_out.address.clone(), token_out.clone()),
1006            ]);
1007            let _ = new_state.set_spot_prices(&tokens);
1008        }
1009
1010        let buy_amount = trade.received_amount;
1011
1012        if sell_amount_exceeds_limit {
1013            return Err(SimulationError::InvalidInput(
1014                format!("Sell amount exceeds limit {sell_amount_limit}"),
1015                Some(GetAmountOutResult::new(
1016                    u256_to_biguint(buy_amount),
1017                    u256_to_biguint(trade.gas_used),
1018                    Box::new(new_state),
1019                )),
1020            ));
1021        }
1022        Ok(GetAmountOutResult::new(
1023            u256_to_biguint(buy_amount),
1024            u256_to_biguint(trade.gas_used),
1025            Box::new(new_state),
1026        ))
1027    }
1028
1029    /// Computes trade limits against `live_snapshot`'s overrides (or the plain indexed state when
1030    /// `None`); see [`ProtocolSim::get_limits`] for the caller-facing contract.
1031    fn get_limits_with(
1032        &self,
1033        sell_token: &Bytes,
1034        buy_token: &Bytes,
1035        live_snapshot: Option<&OverrideSnapshot>,
1036    ) -> Result<(BigUint, BigUint), SimulationError> {
1037        let sell_token = bytes_to_address(sell_token)?;
1038        let buy_token = bytes_to_address(buy_token)?;
1039        let overwrites = self.get_overwrites(
1040            vec![sell_token, buy_token],
1041            *MAX_BALANCE / U256::from(100),
1042            live_snapshot,
1043        )?;
1044        let limits = self.get_amount_limits(
1045            vec![sell_token, buy_token],
1046            Some(overwrites),
1047            self.block_env(live_snapshot),
1048        )?;
1049        Ok((u256_to_biguint(limits.0), u256_to_biguint(limits.1)))
1050    }
1051
1052    #[cfg(test)]
1053    pub fn get_balance_owner(&self) -> Option<Address> {
1054        self.balance_owner
1055    }
1056
1057    /// Get the component balances for validation purposes
1058    pub fn get_balances(&self) -> &HashMap<Address, U256> {
1059        &self.balances
1060    }
1061
1062    #[cfg(test)]
1063    pub fn get_block_overrides(&self) -> Option<BlockEnvOverrides> {
1064        let live_snapshot = self.get_live_snapshot();
1065        self.block_env(live_snapshot.as_ref())
1066    }
1067}
1068
1069impl<D> Serialize for EVMPoolState<D>
1070where
1071    D: EngineDatabaseInterface + Clone + Debug,
1072    <D as DatabaseRef>::Error: Debug,
1073    <D as EngineDatabaseInterface>::Error: Debug,
1074{
1075    fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
1076    where
1077        S: serde::Serializer,
1078    {
1079        Err(serde::ser::Error::custom("not supported due vm state deps"))
1080    }
1081}
1082
1083impl<'de, D> Deserialize<'de> for EVMPoolState<D>
1084where
1085    D: EngineDatabaseInterface + Clone + Debug,
1086    <D as DatabaseRef>::Error: Debug,
1087    <D as EngineDatabaseInterface>::Error: Debug,
1088{
1089    fn deserialize<De>(_deserializer: De) -> Result<Self, De::Error>
1090    where
1091        De: serde::Deserializer<'de>,
1092    {
1093        Err(serde::de::Error::custom("not supported due vm state deps"))
1094    }
1095}
1096
1097#[typetag::serialize]
1098impl<D> ProtocolSim for EVMPoolState<D>
1099where
1100    D: EngineDatabaseInterface + Clone + Debug + 'static,
1101    <D as DatabaseRef>::Error: Debug,
1102    <D as EngineDatabaseInterface>::Error: Debug,
1103{
1104    fn fee(&self) -> f64 {
1105        todo!()
1106    }
1107
1108    fn spot_price(&self, base: &Token, quote: &Token) -> Result<f64, SimulationError> {
1109        let base_address = bytes_to_address(&base.address)?;
1110        let quote_address = bytes_to_address(&quote.address)?;
1111
1112        if let Some(price) = self
1113            .caches
1114            .get_spot_price((base_address, quote_address))
1115        {
1116            return Ok(price);
1117        }
1118
1119        // A pair the pool does not hold can never have a price; failing here keeps the error
1120        // actionable and spares the adapter one or two EVM simulations per bogus pair.
1121        if !self.tokens.contains(&base.address) || !self.tokens.contains(&quote.address) {
1122            return Err(SimulationError::FatalError(format!(
1123                "Spot price not found for base token {base_address} and quote token {quote_address}"
1124            )));
1125        }
1126
1127        // Pools with live overrides derive spot prices from a sub-block snapshot that changes
1128        // intra-block, so a lazily computed value could mix snapshots with the eagerly-warmed
1129        // pairs. Only the eagerly-warmed value is authoritative; a genuine miss is an error.
1130        if self.live_overrides.is_some() {
1131            return Err(SimulationError::FatalError(format!(
1132                "Spot price not found for base token {base_address} and quote token {quote_address}"
1133            )));
1134        }
1135
1136        // Compute this single pair on demand and cache it.
1137        let tokens = HashMap::from([
1138            (base.address.clone(), base.clone()),
1139            (quote.address.clone(), quote.clone()),
1140        ]);
1141        let price = self.compute_spot_price(&tokens, base_address, quote_address, None)?;
1142        self.caches
1143            .insert_spot_price((base_address, quote_address), price);
1144        Ok(price)
1145    }
1146
1147    fn get_amount_out(
1148        &self,
1149        amount_in: BigUint,
1150        token_in: &Token,
1151        token_out: &Token,
1152    ) -> Result<GetAmountOutResult, SimulationError> {
1153        // Read the live snapshot once so overwrites, limits and the swap use one snapshot.
1154        let live_snapshot = self.get_live_snapshot();
1155        Self::run_with_indexed_fallback(&self.id, "Swap", live_snapshot.as_ref(), |snapshot| {
1156            self.get_amount_out_with(&amount_in, token_in, token_out, snapshot)
1157        })
1158    }
1159
1160    fn get_limits(
1161        &self,
1162        sell_token: Bytes,
1163        buy_token: Bytes,
1164    ) -> Result<(BigUint, BigUint), SimulationError> {
1165        let live_snapshot = self.get_live_snapshot();
1166        Self::run_with_indexed_fallback(&self.id, "Limits", live_snapshot.as_ref(), |snapshot| {
1167            self.get_limits_with(&sell_token, &buy_token, snapshot)
1168        })
1169    }
1170
1171    fn delta_transition(
1172        &mut self,
1173        delta: ProtocolStateDelta,
1174        tokens: &HashMap<Bytes, Token>,
1175        balances: &Balances,
1176    ) -> Result<(), TransitionError> {
1177        if let Some(block_number) = delta
1178            .updated_attributes
1179            .get("override_block_number")
1180        {
1181            let number = <[u8; 8]>::try_from(block_number.as_ref())
1182                .map(u64::from_be_bytes)
1183                .map_err(|_| {
1184                    TransitionError::DecodeError(
1185                        "override_block_number attribute must be an 8-byte big-endian u64"
1186                            .to_string(),
1187                    )
1188                })?;
1189            self.block_overrides
1190                .get_or_insert_with(BlockEnvOverrides::default)
1191                .number = Some(number);
1192        }
1193
1194        if let Some(block_timestamp) = delta
1195            .updated_attributes
1196            .get("override_block_timestamp")
1197        {
1198            let timestamp = <[u8; 8]>::try_from(block_timestamp.as_ref())
1199                .map(u64::from_be_bytes)
1200                .map_err(|_| {
1201                    TransitionError::DecodeError(
1202                        "override_block_timestamp attribute must be an 8-byte big-endian u64"
1203                            .to_string(),
1204                    )
1205                })?;
1206            self.block_overrides
1207                .get_or_insert_with(BlockEnvOverrides::default)
1208                .timestamp = Some(timestamp);
1209        }
1210
1211        // No explicit cache handling is needed for the block-env updates above: cached limits
1212        // are stamped with the block env they were computed under (see `LimitContext`), so a
1213        // change here turns into a miss on the next read. Spot prices are deliberately allowed
1214        // to go stale when `update_pool_state` is skipped below — the `manual_updates` contract.
1215
1216        if self.manual_updates {
1217            // Directly check for "update_marker" in `updated_attributes`
1218            if let Some(marker) = delta
1219                .updated_attributes
1220                .get("update_marker")
1221            {
1222                // Assuming `marker` is of type `Bytes`, check its value for "truthiness"
1223                if !marker.is_empty() && marker[0] != 0 {
1224                    self.update_pool_state(tokens, balances)?;
1225                }
1226            }
1227        } else {
1228            self.update_pool_state(tokens, balances)?;
1229        }
1230
1231        Ok(())
1232    }
1233
1234    fn query_pool_swap(
1235        &self,
1236        params: &tycho_common::simulation::protocol_sim::QueryPoolSwapParams,
1237    ) -> Result<tycho_common::simulation::protocol_sim::PoolSwap, SimulationError> {
1238        crate::evm::query_pool_swap::query_pool_swap(self, params)
1239    }
1240
1241    fn clone_box(&self) -> Box<dyn ProtocolSim> {
1242        Box::new(self.clone())
1243    }
1244
1245    fn as_any(&self) -> &dyn Any {
1246        self
1247    }
1248
1249    fn as_any_mut(&mut self) -> &mut dyn Any {
1250        self
1251    }
1252
1253    fn eq(&self, other: &dyn ProtocolSim) -> bool {
1254        if let Some(other_state) = other
1255            .as_any()
1256            .downcast_ref::<EVMPoolState<PreCachedDB>>()
1257        {
1258            self.id == other_state.id
1259        } else {
1260            false
1261        }
1262    }
1263
1264    /// Implemented manually because `typetag` macro not supports generics
1265    fn typetag_deserialize(&self) {
1266        // https://github.com/dtolnay/typetag/blob/21ae0d40c9f73443a20204ab4a134441355b52f7/impl/src/tagged_trait.rs#L140
1267        unreachable!("Only to catch missing typetag attribute on impl blocks. Not called.")
1268    }
1269}
1270
1271#[cfg(test)]
1272mod tests {
1273    use std::default::Default;
1274
1275    use num_traits::One;
1276    use revm::{
1277        primitives::KECCAK_EMPTY,
1278        state::{AccountInfo, Bytecode},
1279    };
1280    use serde_json::Value;
1281    use tycho_client::feed::BlockHeader;
1282    use tycho_common::models::Chain;
1283
1284    use super::*;
1285    use crate::evm::{
1286        engine_db::{create_engine, SHARED_TYCHO_DB},
1287        protocol::vm::{
1288            constants::{BALANCER_V2, ERC20_PROXY_BYTECODE},
1289            state_builder::EVMPoolStateBuilder,
1290        },
1291        simulation::SimulationEngine,
1292        tycho_models::AccountUpdate,
1293    };
1294
1295    fn dai() -> Token {
1296        Token::new(
1297            &Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(),
1298            "DAI",
1299            18,
1300            0,
1301            &[Some(10_000)],
1302            Chain::Ethereum,
1303            100,
1304        )
1305    }
1306
1307    fn bal() -> Token {
1308        Token::new(
1309            &Bytes::from_str("0xba100000625a3754423978a60c9317c58a424e3d").unwrap(),
1310            "BAL",
1311            18,
1312            0,
1313            &[Some(10_000)],
1314            Chain::Ethereum,
1315            100,
1316        )
1317    }
1318
1319    fn dai_addr() -> Address {
1320        bytes_to_address(&dai().address).unwrap()
1321    }
1322
1323    fn bal_addr() -> Address {
1324        bytes_to_address(&bal().address).unwrap()
1325    }
1326
1327    async fn setup_pool_state() -> EVMPoolState<PreCachedDB> {
1328        let data_str = include_str!("assets/balancer_contract_storage_block_20463609.json");
1329        let data: Value = serde_json::from_str(data_str).expect("Failed to parse JSON");
1330
1331        let accounts: Vec<AccountUpdate> = serde_json::from_value(data["accounts"].clone())
1332            .expect("Expected accounts to match AccountUpdate structure");
1333
1334        // The process-wide `SHARED_TYCHO_DB` holds a single current-block header, so tests
1335        // sharing it race; a fresh database keeps each pool independent.
1336        let db = PreCachedDB::new().expect("failed to create test database");
1337        let engine: SimulationEngine<_> = create_engine(db.clone(), false).unwrap();
1338
1339        let block = BlockHeader {
1340            number: 20463609,
1341            hash: Bytes::from_str(
1342                "0x4315fd1afc25cc2ebc72029c543293f9fd833eeb305e2e30159459c827733b1b",
1343            )
1344            .unwrap(),
1345            timestamp: 1722875891,
1346            ..Default::default()
1347        };
1348
1349        for account in accounts.clone() {
1350            engine
1351                .state
1352                .init_account(
1353                    account.address,
1354                    AccountInfo {
1355                        balance: account.balance.unwrap_or_default(),
1356                        nonce: 0u64,
1357                        code_hash: KECCAK_EMPTY,
1358                        code: account
1359                            .code
1360                            .clone()
1361                            .map(|arg0: Vec<u8>| Bytecode::new_raw(arg0.into())),
1362                    },
1363                    None,
1364                    false,
1365                )
1366                .expect("Failed to initialize account");
1367        }
1368        db.update(accounts, Some(block))
1369            .unwrap();
1370
1371        let tokens = vec![dai().address, bal().address];
1372        for token in &tokens {
1373            engine
1374                .state
1375                .init_account(
1376                    bytes_to_address(token).unwrap(),
1377                    AccountInfo {
1378                        balance: U256::from(0),
1379                        nonce: 0,
1380                        code_hash: KECCAK_EMPTY,
1381                        code: Some(Bytecode::new_raw(ERC20_PROXY_BYTECODE.into())),
1382                    },
1383                    None,
1384                    true,
1385                )
1386                .expect("Failed to initialize account");
1387        }
1388
1389        let block = BlockHeader {
1390            number: 18485417,
1391            hash: Bytes::from_str(
1392                "0x28d41d40f2ac275a4f5f621a636b9016b527d11d37d610a45ac3a821346ebf8c",
1393            )
1394            .expect("Invalid block hash"),
1395            timestamp: 0,
1396            ..Default::default()
1397        };
1398        db.update(vec![], Some(block.clone()))
1399            .unwrap();
1400
1401        let pool_id: String =
1402            "0x4626d81b3a1711beb79f4cecff2413886d461677000200000000000000000011".into();
1403
1404        let stateless_contracts = HashMap::from([(
1405            String::from("0x3de27efa2f1aa663ae5d458857e731c129069f29"),
1406            Some(Vec::new()),
1407        )]);
1408
1409        let balances = HashMap::from([
1410            (dai_addr(), U256::from_str("178754012737301807104").unwrap()),
1411            (bal_addr(), U256::from_str("91082987763369885696").unwrap()),
1412        ]);
1413        let adapter_address =
1414            Address::from_str("0xA2C5C98A892fD6656a7F39A2f63228C0Bc846270").unwrap();
1415
1416        EVMPoolStateBuilder::new(pool_id, tokens, adapter_address)
1417            .balances(balances)
1418            .balance_owner(Address::from_str("0xBA12222222228d8Ba445958a75a0704d566BF2C8").unwrap())
1419            .adapter_contract_bytecode(Bytecode::new_raw(BALANCER_V2.into()))
1420            .stateless_contracts(stateless_contracts)
1421            .build(db)
1422            .await
1423            .expect("Failed to build pool state")
1424    }
1425
1426    #[tokio::test]
1427    async fn test_init() {
1428        let pool_state = setup_pool_state().await;
1429
1430        let expected_capabilities = vec![
1431            Capability::SellSide,
1432            Capability::BuySide,
1433            Capability::PriceFunction,
1434            Capability::HardLimits,
1435        ]
1436        .into_iter()
1437        .collect::<HashSet<_>>();
1438
1439        let capabilities_adapter_contract = pool_state
1440            .adapter_contract
1441            .get_capabilities(
1442                &pool_state.id,
1443                bytes_to_address(&pool_state.tokens[0]).unwrap(),
1444                bytes_to_address(&pool_state.tokens[1]).unwrap(),
1445            )
1446            .unwrap();
1447
1448        assert_eq!(capabilities_adapter_contract, expected_capabilities.clone());
1449
1450        let capabilities_state = pool_state.clone().capabilities;
1451
1452        assert_eq!(capabilities_state, expected_capabilities.clone());
1453
1454        // Verify all tokens are initialized in the engine
1455        let engine_accounts = pool_state
1456            .adapter_contract
1457            .engine
1458            .state
1459            .clone()
1460            .get_account_storage()
1461            .expect("Failed to get account storage");
1462        for token in pool_state.tokens.clone() {
1463            let account = engine_accounts
1464                .get_account_info(&bytes_to_address(&token).unwrap())
1465                .unwrap();
1466            assert_eq!(account.balance, U256::from(0));
1467            assert_eq!(account.nonce, 0u64);
1468            assert_eq!(account.code_hash, KECCAK_EMPTY);
1469            assert!(account.code.is_some());
1470        }
1471
1472        // Verify external account is initialized in the engine
1473        let external_account = engine_accounts
1474            .get_account_info(&EXTERNAL_ACCOUNT)
1475            .unwrap();
1476        assert_eq!(external_account.balance, U256::from(*MAX_BALANCE));
1477        assert_eq!(external_account.nonce, 0u64);
1478        assert_eq!(external_account.code_hash, KECCAK_EMPTY);
1479        assert!(external_account.code.is_none());
1480    }
1481
1482    #[tokio::test]
1483    async fn test_get_amount_out() -> Result<(), Box<dyn std::error::Error>> {
1484        let pool_state = setup_pool_state().await;
1485
1486        let result = pool_state
1487            .get_amount_out(BigUint::from_str("1000000000000000000").unwrap(), &dai(), &bal())
1488            .unwrap();
1489        let new_state = result
1490            .new_state
1491            .as_any()
1492            .downcast_ref::<EVMPoolState<PreCachedDB>>()
1493            .unwrap();
1494        assert_eq!(result.amount, BigUint::from_str("137780051463393923").unwrap());
1495        // The returned state starts with fresh caches; reading recomputes the post-swap price
1496        // lazily, which differs from the pre-swap price because the swap moved the pool.
1497        let pre = pool_state
1498            .spot_price(&dai(), &bal())
1499            .unwrap();
1500        let post = new_state
1501            .spot_price(&dai(), &bal())
1502            .unwrap();
1503        assert_ne!(pre, post);
1504        assert!(pool_state
1505            .block_lasting_overwrites
1506            .is_empty());
1507        Ok(())
1508    }
1509
1510    #[tokio::test]
1511    async fn test_sequential_get_amount_outs() {
1512        let pool_state = setup_pool_state().await;
1513
1514        let result = pool_state
1515            .get_amount_out(BigUint::from_str("1000000000000000000").unwrap(), &dai(), &bal())
1516            .unwrap();
1517        let new_state = result
1518            .new_state
1519            .as_any()
1520            .downcast_ref::<EVMPoolState<PreCachedDB>>()
1521            .unwrap();
1522        assert_eq!(result.amount, BigUint::from_str("137780051463393923").unwrap());
1523        // The returned state starts with fresh caches; a lazily-computed price against the
1524        // post-swap overwrites differs from the pre-swap price.
1525        let price_before_first_swap = pool_state
1526            .spot_price(&dai(), &bal())
1527            .unwrap();
1528        let price_after_first_swap = new_state
1529            .spot_price(&dai(), &bal())
1530            .unwrap();
1531        assert_ne!(price_before_first_swap, price_after_first_swap);
1532
1533        let new_result = new_state
1534            .get_amount_out(BigUint::from_str("1000000000000000000").unwrap(), &dai(), &bal())
1535            .unwrap();
1536        let new_state_second_swap = new_result
1537            .new_state
1538            .as_any()
1539            .downcast_ref::<EVMPoolState<PreCachedDB>>()
1540            .unwrap();
1541
1542        assert_eq!(new_result.amount, BigUint::from_str("136964651490065626").unwrap());
1543        // Same after the second swap: the lazily recomputed price has moved again relative to
1544        // the state right after the first swap.
1545        let price_after_second_swap = new_state_second_swap
1546            .spot_price(&dai(), &bal())
1547            .unwrap();
1548        assert_ne!(price_after_first_swap, price_after_second_swap);
1549    }
1550
1551    #[tokio::test]
1552    async fn test_get_amount_out_dust() {
1553        let pool_state = setup_pool_state().await;
1554
1555        let result = pool_state
1556            .get_amount_out(BigUint::one(), &dai(), &bal())
1557            .unwrap();
1558
1559        let _ = result
1560            .new_state
1561            .as_any()
1562            .downcast_ref::<EVMPoolState<PreCachedDB>>()
1563            .unwrap();
1564        assert_eq!(result.amount, BigUint::ZERO);
1565    }
1566
1567    #[tokio::test]
1568    async fn test_get_amount_out_sell_limit() {
1569        let pool_state = setup_pool_state().await;
1570
1571        let result = pool_state.get_amount_out(
1572            // sell limit is 100279494253364362835
1573            BigUint::from_str("100379494253364362835").unwrap(),
1574            &dai(),
1575            &bal(),
1576        );
1577
1578        assert!(result.is_err());
1579
1580        match result {
1581            Err(SimulationError::InvalidInput(msg1, amount_out_result)) => {
1582                assert_eq!(msg1, "Sell amount exceeds limit 100279494253364362835");
1583                assert!(amount_out_result.is_some());
1584            }
1585            _ => panic!("Test failed: was expecting an Err(SimulationError::RetryDifferentInput(_, _)) value"),
1586        }
1587    }
1588
1589    #[tokio::test]
1590    async fn test_get_amount_limits() {
1591        let pool_state = setup_pool_state().await;
1592
1593        let overwrites = pool_state
1594            .get_overwrites(
1595                vec![
1596                    bytes_to_address(&pool_state.tokens[0]).unwrap(),
1597                    bytes_to_address(&pool_state.tokens[1]).unwrap(),
1598                ],
1599                *MAX_BALANCE / U256::from(100),
1600                None,
1601            )
1602            .unwrap();
1603        let (dai_limit, _) = pool_state
1604            .get_amount_limits(
1605                vec![dai_addr(), bal_addr()],
1606                Some(overwrites.clone()),
1607                pool_state.block_env(None),
1608            )
1609            .unwrap();
1610        assert_eq!(dai_limit, U256::from_str("100279494253364362835").unwrap());
1611
1612        let (bal_limit, _) = pool_state
1613            .get_amount_limits(
1614                vec![
1615                    bytes_to_address(&pool_state.tokens[1]).unwrap(),
1616                    bytes_to_address(&pool_state.tokens[0]).unwrap(),
1617                ],
1618                Some(overwrites),
1619                pool_state.block_env(None),
1620            )
1621            .unwrap();
1622        assert_eq!(bal_limit, U256::from_str("13997408640689987484").unwrap());
1623    }
1624
1625    #[tokio::test]
1626    async fn test_get_limits_cached_matches_fresh() {
1627        let pool_state = setup_pool_state().await;
1628        let overwrites = pool_state
1629            .get_overwrites(vec![dai_addr(), bal_addr()], *MAX_BALANCE / U256::from(100), None)
1630            .unwrap();
1631
1632        // First call populates the cache.
1633        let fresh = pool_state
1634            .get_amount_limits(vec![dai_addr(), bal_addr()], Some(overwrites.clone()), None)
1635            .unwrap();
1636        assert_eq!(
1637            pool_state
1638                .caches
1639                .read()
1640                .limits
1641                .get(&(dai_addr(), bal_addr()))
1642                .copied(),
1643            Some(fresh)
1644        );
1645
1646        // Second call returns the cached value, identical to the first.
1647        let cached = pool_state
1648            .get_amount_limits(vec![dai_addr(), bal_addr()], Some(overwrites), None)
1649            .unwrap();
1650        assert_eq!(fresh, cached);
1651
1652        // The cache key deliberately excludes the overwrites: the limit must be independent of
1653        // the caller's overwrite amounts. Guard that by computing fresh, on a cold state, with
1654        // overwrites derived from a different amount, and comparing byte-for-byte.
1655        let cold = setup_pool_state().await;
1656        let different_overwrites = cold
1657            .get_overwrites(vec![dai_addr(), bal_addr()], *MAX_BALANCE / U256::from(50), None)
1658            .unwrap();
1659        let independent = cold
1660            .get_amount_limits(vec![dai_addr(), bal_addr()], Some(different_overwrites), None)
1661            .unwrap();
1662        assert_eq!(independent, fresh);
1663    }
1664
1665    /// A cached limit must not survive an engine-block advance, even when the pool receives no
1666    /// delta: the adapter computes limits against the engine's current block, so time-dependent
1667    /// limits (e.g. an amplification ramp) change with zero pool-level events.
1668    #[tokio::test]
1669    async fn test_engine_block_advance_invalidates_cached_limits() {
1670        let pool_state = setup_pool_state().await;
1671        let overwrites = pool_state
1672            .get_overwrites(vec![dai_addr(), bal_addr()], *MAX_BALANCE / U256::from(100), None)
1673            .unwrap();
1674        let real = pool_state
1675            .get_amount_limits(vec![dai_addr(), bal_addr()], Some(overwrites.clone()), None)
1676            .unwrap();
1677        let sentinel = (U256::from(1u64), U256::from(1u64));
1678        assert_ne!(real, sentinel);
1679        pool_state
1680            .caches
1681            .write()
1682            .limits
1683            .insert((dai_addr(), bal_addr()), sentinel);
1684
1685        // Sanity: the sentinel is served while the engine block is unchanged.
1686        let served = pool_state
1687            .get_amount_limits(vec![dai_addr(), bal_addr()], Some(overwrites.clone()), None)
1688            .unwrap();
1689        assert_eq!(served, sentinel);
1690
1691        // Advance the engine's current block without any pool-level event.
1692        let mut block = SHARED_TYCHO_DB
1693            .get_current_block()
1694            .expect("fixture sets an engine block");
1695        block.number += 1;
1696        block.timestamp += 12;
1697        SHARED_TYCHO_DB
1698            .update(vec![], Some(block))
1699            .expect("engine block update");
1700
1701        // The stamp mismatch turns the read into a miss: the sentinel is dead.
1702        let after = pool_state
1703            .get_amount_limits(vec![dai_addr(), bal_addr()], Some(overwrites), None)
1704            .unwrap();
1705        assert_ne!(after, sentinel);
1706        assert_eq!(after, real);
1707    }
1708
1709    #[tokio::test]
1710    async fn test_set_spot_prices() {
1711        let pool_state = setup_pool_state().await;
1712
1713        pool_state
1714            .set_spot_prices(
1715                &vec![bal(), dai()]
1716                    .into_iter()
1717                    .map(|t| (t.address.clone(), t))
1718                    .collect(),
1719            )
1720            .unwrap();
1721
1722        let dai_bal_spot_price = pool_state
1723            .caches
1724            .get_spot_price((
1725                bytes_to_address(&pool_state.tokens[0]).unwrap(),
1726                bytes_to_address(&pool_state.tokens[1]).unwrap(),
1727            ))
1728            .unwrap();
1729        let bal_dai_spot_price = pool_state
1730            .caches
1731            .get_spot_price((
1732                bytes_to_address(&pool_state.tokens[1]).unwrap(),
1733                bytes_to_address(&pool_state.tokens[0]).unwrap(),
1734            ))
1735            .unwrap();
1736        assert_eq!(dai_bal_spot_price, 0.137_778_914_319_047_9);
1737        assert_eq!(bal_dai_spot_price, 7.071_503_245_428_246);
1738    }
1739
1740    #[tokio::test]
1741    async fn test_set_spot_prices_without_capability() {
1742        // Tests set Spot Prices functions when the pool doesn't have PriceFunction capability
1743        let mut pool_state = setup_pool_state().await;
1744
1745        pool_state
1746            .capabilities
1747            .remove(&Capability::PriceFunction);
1748
1749        pool_state
1750            .set_spot_prices(
1751                &vec![bal(), dai()]
1752                    .into_iter()
1753                    .map(|t| (t.address.clone(), t))
1754                    .collect(),
1755            )
1756            .unwrap();
1757
1758        let dai_bal_spot_price = pool_state
1759            .caches
1760            .get_spot_price((
1761                bytes_to_address(&pool_state.tokens[0]).unwrap(),
1762                bytes_to_address(&pool_state.tokens[1]).unwrap(),
1763            ))
1764            .unwrap();
1765        let bal_dai_spot_price = pool_state
1766            .caches
1767            .get_spot_price((
1768                bytes_to_address(&pool_state.tokens[1]).unwrap(),
1769                bytes_to_address(&pool_state.tokens[0]).unwrap(),
1770            ))
1771            .unwrap();
1772        assert_eq!(dai_bal_spot_price, 0.13736685496467538);
1773        assert_eq!(bal_dai_spot_price, 7.050354297665408);
1774    }
1775
1776    #[tokio::test]
1777    async fn test_spot_price_lazy_computes_on_miss() {
1778        let pool_state = setup_pool_state().await;
1779        // Non-override pool with a cold cache: the builder does not warm spot prices.
1780        assert!(pool_state
1781            .caches
1782            .read()
1783            .spot_prices
1784            .is_empty());
1785
1786        // Reading a pair computes it on demand, byte-identical to the eagerly-warmed values
1787        // pinned in `test_set_spot_prices`, and caches it.
1788        let dai_bal = pool_state
1789            .spot_price(&dai(), &bal())
1790            .unwrap();
1791        assert_eq!(dai_bal, 0.137_778_914_319_047_9);
1792        let bal_dai = pool_state
1793            .spot_price(&bal(), &dai())
1794            .unwrap();
1795        assert_eq!(bal_dai, 7.071_503_245_428_246);
1796        assert_eq!(
1797            pool_state
1798                .caches
1799                .get_spot_price((dai_addr(), bal_addr())),
1800            Some(dai_bal)
1801        );
1802    }
1803
1804    #[tokio::test]
1805    async fn test_get_balance_overwrites_with_component_balances() {
1806        let pool_state: EVMPoolState<PreCachedDB> = setup_pool_state().await;
1807
1808        let overwrites = pool_state
1809            .get_balance_overwrites()
1810            .unwrap();
1811
1812        let dai_address = dai_addr();
1813        let bal_address = bal_addr();
1814        assert!(overwrites.contains_key(&dai_address));
1815        assert!(overwrites.contains_key(&bal_address));
1816    }
1817
1818    #[tokio::test]
1819    async fn test_get_balance_overwrites_with_contract_balances() {
1820        let mut pool_state: EVMPoolState<PreCachedDB> = setup_pool_state().await;
1821
1822        let contract_address =
1823            Address::from_str("0xBA12222222228d8Ba445958a75a0704d566BF2C8").unwrap();
1824
1825        // Ensure no component balances are used
1826        pool_state.balances.clear();
1827        pool_state.balance_owner = None;
1828
1829        // Set contract balances
1830        let dai_address = dai_addr();
1831        let bal_address = bal_addr();
1832        pool_state.contract_balances = HashMap::from([(
1833            contract_address,
1834            HashMap::from([
1835                (dai_address, U256::from_str("7500000000000000000000").unwrap()), // 7500 DAI
1836                (bal_address, U256::from_str("1500000000000000000000").unwrap()), // 1500 BAL
1837            ]),
1838        )]);
1839
1840        let overwrites = pool_state
1841            .get_balance_overwrites()
1842            .unwrap();
1843
1844        assert!(overwrites.contains_key(&dai_address));
1845        assert!(overwrites.contains_key(&bal_address));
1846    }
1847
1848    #[tokio::test]
1849    async fn test_balance_merging_during_delta_transition() {
1850        use std::str::FromStr;
1851
1852        let mut pool_state = setup_pool_state().await;
1853        let pool_id = pool_state.id.clone();
1854
1855        // Test the balance merging logic more directly
1856        // Setup initial balances including DAI and BAL (which the pool already knows about)
1857        let dai_addr = dai_addr();
1858        let bal_addr = bal_addr();
1859        let new_token = Address::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); // WETH
1860
1861        // Clear and setup clean initial state
1862        pool_state.balances.clear();
1863        pool_state
1864            .balances
1865            .insert(dai_addr, U256::from(1000000000u64));
1866        pool_state
1867            .balances
1868            .insert(bal_addr, U256::from(2000000000u64));
1869        pool_state
1870            .balances
1871            .insert(new_token, U256::from(3000000000u64));
1872
1873        // Create tokens mapping including the existing DAI and BAL
1874        let mut tokens = HashMap::new();
1875        tokens.insert(dai().address.clone(), dai());
1876        tokens.insert(bal().address.clone(), bal());
1877
1878        // Simulate a delta transition with only DAI balance update (missing BAL and new_token)
1879        let mut component_balances = HashMap::new();
1880        let mut delta_balances = HashMap::new();
1881        // Only update DAI balance, leave others unchanged in delta
1882        delta_balances.insert(dai().address.clone(), Bytes::from(vec![0x77, 0x35, 0x94, 0x00])); // 2000000000 (updated value)
1883        component_balances.insert(pool_id.clone(), delta_balances);
1884
1885        let balances = Balances { component_balances, account_balances: HashMap::new() };
1886
1887        // Record initial balance count
1888        let initial_balance_count = pool_state.balances.len();
1889        assert_eq!(initial_balance_count, 3);
1890
1891        // Apply delta transition
1892        pool_state
1893            .update_pool_state(&tokens, &balances)
1894            .unwrap();
1895
1896        // Verify that all 3 balances are preserved (BAL and new_token should still be there)
1897        assert_eq!(
1898            pool_state.balances.len(),
1899            3,
1900            "All balances should be preserved after delta transition"
1901        );
1902        assert!(
1903            pool_state
1904                .balances
1905                .contains_key(&dai_addr),
1906            "DAI balance should be present"
1907        );
1908        assert!(
1909            pool_state
1910                .balances
1911                .contains_key(&bal_addr),
1912            "BAL balance should be present"
1913        );
1914        assert!(
1915            pool_state
1916                .balances
1917                .contains_key(&new_token),
1918            "New token balance should be preserved from before delta"
1919        );
1920
1921        // Verify that updated token (DAI) has new value
1922        assert_eq!(
1923            pool_state.balances[&dai_addr],
1924            U256::from(2000000000u64),
1925            "DAI balance should be updated"
1926        );
1927
1928        // Verify that non-updated tokens retain their original values
1929        assert_eq!(
1930            pool_state.balances[&bal_addr],
1931            U256::from(2000000000u64),
1932            "BAL balance should be unchanged"
1933        );
1934        assert_eq!(
1935            pool_state.balances[&new_token],
1936            U256::from(3000000000u64),
1937            "New token balance should be unchanged"
1938        );
1939    }
1940
1941    #[tokio::test]
1942    async fn test_update_pool_state_warms_caches() {
1943        let mut pool_state = setup_pool_state().await;
1944        let tokens =
1945            HashMap::from([(dai().address.clone(), dai()), (bal().address.clone(), bal())]);
1946        let balances =
1947            Balances { component_balances: HashMap::new(), account_balances: HashMap::new() };
1948
1949        pool_state
1950            .update_pool_state(&tokens, &balances)
1951            .unwrap();
1952
1953        let caches = pool_state.caches.read();
1954        assert!(!caches.spot_prices.is_empty());
1955        assert!(!caches.limits.is_empty());
1956    }
1957
1958    #[tokio::test]
1959    async fn test_delta_transition_updates_block_overrides() {
1960        let mut pool_state = setup_pool_state().await;
1961        pool_state.manual_updates = true;
1962        pool_state.block_overrides = None;
1963
1964        let delta = ProtocolStateDelta {
1965            component_id: pool_state.id.clone(),
1966            updated_attributes: HashMap::from([
1967                ("override_block_number".to_string(), Bytes::from(123_u64.to_be_bytes().to_vec())),
1968                (
1969                    "override_block_timestamp".to_string(),
1970                    Bytes::from(456_u64.to_be_bytes().to_vec()),
1971                ),
1972            ]),
1973            deleted_attributes: HashSet::new(),
1974        };
1975
1976        pool_state
1977            .delta_transition(delta, &HashMap::new(), &Balances::default())
1978            .unwrap();
1979
1980        assert_eq!(
1981            pool_state.block_overrides,
1982            Some(BlockEnvOverrides { number: Some(123), timestamp: Some(456) })
1983        );
1984    }
1985
1986    #[tokio::test]
1987    async fn test_delta_transition_updates_partial_block_overrides() {
1988        let mut pool_state = setup_pool_state().await;
1989        pool_state.manual_updates = true;
1990        pool_state.block_overrides =
1991            Some(BlockEnvOverrides { number: Some(123), timestamp: Some(456) });
1992
1993        let delta = ProtocolStateDelta {
1994            component_id: pool_state.id.clone(),
1995            updated_attributes: HashMap::from([(
1996                "override_block_number".to_string(),
1997                Bytes::from(789_u64.to_be_bytes().to_vec()),
1998            )]),
1999            deleted_attributes: HashSet::new(),
2000        };
2001
2002        pool_state
2003            .delta_transition(delta, &HashMap::new(), &Balances::default())
2004            .unwrap();
2005
2006        assert_eq!(
2007            pool_state.block_overrides,
2008            Some(BlockEnvOverrides { number: Some(789), timestamp: Some(456) })
2009        );
2010    }
2011
2012    /// `update_pool_state` must refresh BOTH component balances and tracked contract balances,
2013    /// not treat them as mutually exclusive. Balancer V3 is hybrid (component balances at the
2014    /// vault owner + tracked vault contract balances); freezing contract balances while the
2015    /// vault's indexed reserves advance is what caused the WETH-in `BalanceNotSettled` failures.
2016    #[tokio::test]
2017    async fn test_delta_transition_refreshes_both_balance_maps() {
2018        let mut pool_state = setup_pool_state().await;
2019        let vault = Address::from_str("0xBA12222222228d8Ba445958a75a0704d566BF2C8").unwrap();
2020        // Non-manual pool (how Balancer V3 behaves once `manual_updates` is dropped): a contract
2021        // change routes the pool through delta_transition, which refreshes its balances.
2022        pool_state.manual_updates = false;
2023        pool_state.involved_contracts = HashSet::from([vault]);
2024        pool_state.contract_balances =
2025            HashMap::from([(vault, HashMap::from([(dai_addr(), U256::from(1u64))]))]);
2026
2027        let delta = ProtocolStateDelta {
2028            component_id: pool_state.id.clone(),
2029            updated_attributes: HashMap::new(),
2030            deleted_attributes: HashSet::new(),
2031        };
2032        let balances = Balances {
2033            component_balances: HashMap::new(),
2034            account_balances: HashMap::from([(
2035                Bytes::from(vault.as_slice()),
2036                HashMap::from([(dai().address.clone(), Bytes::from(42u64).lpad(32, 0))]),
2037            )]),
2038        };
2039
2040        // The spot-price refresh inside update_pool_state may fail in offline test
2041        // environments; the balance bookkeeping this test guards happens before it.
2042        let _ = pool_state.delta_transition(delta, &HashMap::new(), &balances);
2043
2044        // Contract balance refreshed even though component balances are non-empty (the old
2045        // mutual-exclusion would have skipped this branch entirely).
2046        assert_eq!(pool_state.contract_balances[&vault][&dai_addr()], U256::from(42u64));
2047        // Component balances stay untouched by a contract-balance-only delta.
2048        assert_eq!(
2049            pool_state.balances[&dai_addr()],
2050            U256::from_str("178754012737301807104").unwrap()
2051        );
2052    }
2053
2054    /// A block-environment-only delta (no `update_marker`) on a `manual_updates` pool skips
2055    /// `update_pool_state`, but cached limits must not survive it: they are stamped with the
2056    /// block env they were computed under, so the changed env turns the next read into a miss.
2057    /// Spot prices are deliberately allowed to go stale in this skip path (the `manual_updates`
2058    /// contract).
2059    #[tokio::test]
2060    async fn test_block_override_delta_invalidates_cached_limits() {
2061        let mut pool_state = setup_pool_state().await;
2062        pool_state.manual_updates = true;
2063
2064        // Populate the limit cache, then poison the entry with a sentinel so a served cache hit
2065        // is distinguishable from a fresh computation.
2066        let overwrites = pool_state
2067            .get_overwrites(vec![dai_addr(), bal_addr()], *MAX_BALANCE / U256::from(100), None)
2068            .unwrap();
2069        let real = pool_state
2070            .get_amount_limits(
2071                vec![dai_addr(), bal_addr()],
2072                Some(overwrites.clone()),
2073                pool_state.block_env(None),
2074            )
2075            .unwrap();
2076        let sentinel = (U256::from(1u64), U256::from(1u64));
2077        assert_ne!(real, sentinel);
2078        pool_state
2079            .caches
2080            .write()
2081            .limits
2082            .insert((dai_addr(), bal_addr()), sentinel);
2083
2084        // A delta that only changes the block env, with no `update_marker`.
2085        let delta = ProtocolStateDelta {
2086            component_id: pool_state.id.clone(),
2087            updated_attributes: HashMap::from([(
2088                "override_block_number".to_string(),
2089                Bytes::from(1234_u64.to_be_bytes().to_vec()),
2090            )]),
2091            deleted_attributes: HashSet::new(),
2092        };
2093        pool_state
2094            .delta_transition(delta, &HashMap::new(), &Balances::default())
2095            .unwrap();
2096
2097        // The changed block env must invalidate the sentinel: the read recomputes fresh.
2098        let after = pool_state
2099            .get_amount_limits(
2100                vec![dai_addr(), bal_addr()],
2101                Some(overwrites),
2102                pool_state.block_env(None),
2103            )
2104            .unwrap();
2105        assert_ne!(after, sentinel);
2106    }
2107
2108    #[test]
2109    fn should_not_panic_at_typetag_deserialize() {
2110        let deserialized: Result<Box<dyn ProtocolSim>, _> = serde_json::from_str(
2111            r#"{"protocol":"EVMPoolState","state":{"reserve_0":1,"reserve_1":2}}"#,
2112        );
2113
2114        assert!(deserialized.is_err());
2115    }
2116
2117    /// A live snapshot's block number/timestamp take precedence over the static block overrides,
2118    /// field by field, while an absent or empty snapshot leaves them untouched.
2119    #[tokio::test]
2120    async fn test_block_env_prefers_live_overrides() {
2121        let mut pool_state = setup_pool_state().await;
2122        pool_state.block_overrides =
2123            Some(BlockEnvOverrides { number: Some(100), timestamp: Some(1_000) });
2124
2125        // No live snapshot: the statically configured overrides are used unchanged.
2126        assert_eq!(
2127            pool_state.block_env(None),
2128            Some(BlockEnvOverrides { number: Some(100), timestamp: Some(1_000) })
2129        );
2130
2131        // A live snapshot with neither field set does not touch the static overrides.
2132        let empty = OverrideSnapshot::default();
2133        assert_eq!(
2134            pool_state.block_env(Some(&empty)),
2135            Some(BlockEnvOverrides { number: Some(100), timestamp: Some(1_000) })
2136        );
2137
2138        // Present live fields win; unset live fields keep the static value.
2139        let live = OverrideSnapshot {
2140            block_number: Some(200),
2141            block_timestamp: None,
2142            ..Default::default()
2143        };
2144        assert_eq!(
2145            pool_state.block_env(Some(&live)),
2146            Some(BlockEnvOverrides { number: Some(200), timestamp: Some(1_000) })
2147        );
2148
2149        // With no static overrides, the live block environment stands on its own.
2150        pool_state.block_overrides = None;
2151        let live = OverrideSnapshot {
2152            block_number: Some(300),
2153            block_timestamp: Some(3_000),
2154            ..Default::default()
2155        };
2156        assert_eq!(
2157            pool_state.block_env(Some(&live)),
2158            Some(BlockEnvOverrides { number: Some(300), timestamp: Some(3_000) })
2159        );
2160    }
2161
2162    /// Live storage is merged into the computed overwrites: a fresh contract is added, a value on a
2163    /// slot the baseline already sets is overridden (live wins on conflict), and empty live storage
2164    /// is a no-op.
2165    #[tokio::test]
2166    async fn test_get_overwrites_applies_live_storage() {
2167        let pool_state = setup_pool_state().await;
2168        let tokens = vec![dai_addr(), bal_addr()];
2169        let max = *MAX_BALANCE / U256::from(100);
2170
2171        let baseline = pool_state
2172            .get_overwrites(tokens.clone(), max, None)
2173            .unwrap();
2174
2175        // An empty live snapshot leaves the overwrites unchanged.
2176        let empty = OverrideSnapshot::default();
2177        assert_eq!(
2178            pool_state
2179                .get_overwrites(tokens.clone(), max, Some(&empty))
2180                .unwrap(),
2181            baseline
2182        );
2183
2184        // Pick an address/slot the baseline already sets, so we can assert live wins the conflict.
2185        let (conflict_addr, conflict_slot, baseline_val) = {
2186            let (addr, slots) = baseline
2187                .iter()
2188                .next()
2189                .expect("baseline has overwrites");
2190            let (slot, val) = slots
2191                .iter()
2192                .next()
2193                .expect("address has slots");
2194            (*addr, *slot, *val)
2195        };
2196        let sentinel = baseline_val + U256::from(1);
2197        let fresh = Address::from([0xAB; 20]);
2198        assert!(
2199            !baseline.contains_key(&fresh),
2200            "fresh address must originate from the live snapshot"
2201        );
2202
2203        let live = OverrideSnapshot {
2204            storage: std::sync::Arc::new(HashMap::from([
2205                (fresh, HashMap::from([(U256::from(7), U256::from(123))])),
2206                (conflict_addr, HashMap::from([(conflict_slot, sentinel)])),
2207            ])),
2208            ..Default::default()
2209        };
2210        let with_live = pool_state
2211            .get_overwrites(tokens, max, Some(&live))
2212            .unwrap();
2213
2214        assert_eq!(
2215            with_live
2216                .get(&fresh)
2217                .and_then(|slots| slots.get(&U256::from(7))),
2218            Some(&U256::from(123)),
2219            "live storage for a fresh contract must be merged in"
2220        );
2221        assert_eq!(
2222            with_live
2223                .get(&conflict_addr)
2224                .and_then(|slots| slots.get(&conflict_slot)),
2225            Some(&sentinel),
2226            "live override must win on slot conflict"
2227        );
2228    }
2229
2230    /// `get_live_snapshot` returns the attached snapshot only while it is fresh: an expired one is
2231    /// dropped (so the pool reverts to indexed state), and no channel means no snapshot. Expiry is
2232    /// pinned to the extremes (`1` = long past, `u64::MAX` = effectively never) so the assertions
2233    /// are independent of the wall clock.
2234    #[tokio::test]
2235    async fn test_get_live_snapshot_drops_expired() {
2236        let mut pool_state = setup_pool_state().await;
2237
2238        // No channel attached: nothing to read.
2239        assert!(pool_state.get_live_snapshot().is_none());
2240
2241        // A snapshot without an expiry never goes stale.
2242        let never =
2243            OverrideSnapshot { block_number: Some(1), expires_at: None, ..Default::default() };
2244        let (_never_tx, never_rx) = watch::channel(never);
2245        pool_state.set_live_overrides(never_rx);
2246        assert_eq!(
2247            pool_state
2248                .get_live_snapshot()
2249                .and_then(|snapshot| snapshot.block_number),
2250            Some(1)
2251        );
2252
2253        // A snapshot whose expiry is far in the future is returned.
2254        let fresh = OverrideSnapshot {
2255            block_number: Some(42),
2256            expires_at: Some(u64::MAX),
2257            ..Default::default()
2258        };
2259        let (_fresh_tx, fresh_rx) = watch::channel(fresh);
2260        pool_state.set_live_overrides(fresh_rx);
2261        assert_eq!(
2262            pool_state
2263                .get_live_snapshot()
2264                .and_then(|snapshot| snapshot.block_number),
2265            Some(42)
2266        );
2267
2268        // A snapshot whose expiry is in the past is dropped.
2269        let expired =
2270            OverrideSnapshot { block_number: Some(42), expires_at: Some(1), ..Default::default() };
2271        let (_expired_tx, expired_rx) = watch::channel(expired);
2272        pool_state.set_live_overrides(expired_rx);
2273        assert!(pool_state.get_live_snapshot().is_none());
2274    }
2275
2276    /// Attaching a live override channel must drop any values cached while the pool had no
2277    /// overrides: they were computed against plain indexed state, and the cache-read fast paths
2278    /// would keep serving them.
2279    #[tokio::test]
2280    async fn test_set_live_overrides_clears_caches() {
2281        let mut pool_state = setup_pool_state().await;
2282        {
2283            let mut caches = pool_state.caches.write();
2284            caches
2285                .spot_prices
2286                .insert((dai_addr(), bal_addr()), 1.0);
2287            caches
2288                .limits
2289                .insert((dai_addr(), bal_addr()), (U256::from(1u64), U256::from(1u64)));
2290        }
2291
2292        let (_tx, rx) = watch::channel(OverrideSnapshot::default());
2293        pool_state.set_live_overrides(rx);
2294
2295        let caches = pool_state.caches.read();
2296        assert!(caches.spot_prices.is_empty());
2297        assert!(caches.limits.is_empty());
2298    }
2299
2300    /// A live snapshot that corrupts the Balancer Vault's low storage slots (pause / reentrancy
2301    /// state), guaranteeing that any simulation run with it applied reverts.
2302    fn poison_snapshot(failure_policy: FailurePolicy) -> OverrideSnapshot {
2303        let vault: Address = "0xBA12222222228d8Ba445958a75a0704d566BF2C8"
2304            .parse()
2305            .unwrap();
2306        let poisoned_slots = (0u64..10)
2307            .map(|slot| (U256::from(slot), U256::MAX))
2308            .collect();
2309        OverrideSnapshot {
2310            storage: std::sync::Arc::new(HashMap::from([(vault, poisoned_slots)])),
2311            failure_policy,
2312            ..Default::default()
2313        }
2314    }
2315
2316    /// With the default [`FailurePolicy::Error`], a snapshot that breaks the simulation surfaces
2317    /// the failure to the caller.
2318    #[tokio::test]
2319    async fn test_failing_overrides_error_by_default() {
2320        let mut pool_state = setup_pool_state().await;
2321        let poison = poison_snapshot(FailurePolicy::Error);
2322        let (_tx, rx) = watch::channel(poison);
2323        pool_state.set_live_overrides(rx);
2324
2325        assert!(pool_state
2326            .get_amount_out(BigUint::from_str("1000000000000000000").unwrap(), &dai(), &bal())
2327            .is_err());
2328    }
2329
2330    /// With [`FailurePolicy::FallbackToIndexedState`], a snapshot that breaks the simulation is
2331    /// dropped and the operation is retried on the plain indexed state, matching the result the
2332    /// pool produces without any live overrides.
2333    #[tokio::test]
2334    async fn test_failing_overrides_fall_back_to_indexed_state() {
2335        let pool_state = setup_pool_state().await;
2336        let expected = pool_state
2337            .get_amount_out(BigUint::from_str("1000000000000000000").unwrap(), &dai(), &bal())
2338            .unwrap();
2339        let expected_limits = pool_state
2340            .get_limits(dai().address.clone(), bal().address.clone())
2341            .unwrap();
2342
2343        let mut pool_state = pool_state;
2344        let poison = poison_snapshot(FailurePolicy::FallbackToIndexedState);
2345        let (_tx, rx) = watch::channel(poison);
2346        pool_state.set_live_overrides(rx);
2347
2348        let result = pool_state
2349            .get_amount_out(BigUint::from_str("1000000000000000000").unwrap(), &dai(), &bal())
2350            .expect("must fall back to indexed state");
2351        assert_eq!(result.amount, expected.amount);
2352
2353        let limits = pool_state
2354            .get_limits(dai().address.clone(), bal().address.clone())
2355            .expect("limits must fall back to indexed state");
2356        assert_eq!(limits, expected_limits);
2357
2358        let tokens =
2359            HashMap::from([(dai().address.clone(), dai()), (bal().address.clone(), bal())]);
2360        pool_state
2361            .set_spot_prices(&tokens)
2362            .expect("spot prices must fall back to indexed state");
2363    }
2364}