Skip to main content

tycho_simulation/evm/protocol/vm/
state.rs

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