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,
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        // The process-wide `SHARED_TYCHO_DB` holds a single current-block header, so tests
1130        // sharing it race; a fresh database keeps each pool independent.
1131        let db = PreCachedDB::new().expect("failed to create test database");
1132        let engine: SimulationEngine<_> = create_engine(db.clone(), false).unwrap();
1133
1134        let block = BlockHeader {
1135            number: 20463609,
1136            hash: Bytes::from_str(
1137                "0x4315fd1afc25cc2ebc72029c543293f9fd833eeb305e2e30159459c827733b1b",
1138            )
1139            .unwrap(),
1140            timestamp: 1722875891,
1141            ..Default::default()
1142        };
1143
1144        for account in accounts.clone() {
1145            engine
1146                .state
1147                .init_account(
1148                    account.address,
1149                    AccountInfo {
1150                        balance: account.balance.unwrap_or_default(),
1151                        nonce: 0u64,
1152                        code_hash: KECCAK_EMPTY,
1153                        code: account
1154                            .code
1155                            .clone()
1156                            .map(|arg0: Vec<u8>| Bytecode::new_raw(arg0.into())),
1157                    },
1158                    None,
1159                    false,
1160                )
1161                .expect("Failed to initialize account");
1162        }
1163        db.update(accounts, Some(block))
1164            .unwrap();
1165
1166        let tokens = vec![dai().address, bal().address];
1167        for token in &tokens {
1168            engine
1169                .state
1170                .init_account(
1171                    bytes_to_address(token).unwrap(),
1172                    AccountInfo {
1173                        balance: U256::from(0),
1174                        nonce: 0,
1175                        code_hash: KECCAK_EMPTY,
1176                        code: Some(Bytecode::new_raw(ERC20_PROXY_BYTECODE.into())),
1177                    },
1178                    None,
1179                    true,
1180                )
1181                .expect("Failed to initialize account");
1182        }
1183
1184        let block = BlockHeader {
1185            number: 18485417,
1186            hash: Bytes::from_str(
1187                "0x28d41d40f2ac275a4f5f621a636b9016b527d11d37d610a45ac3a821346ebf8c",
1188            )
1189            .expect("Invalid block hash"),
1190            timestamp: 0,
1191            ..Default::default()
1192        };
1193        db.update(vec![], Some(block.clone()))
1194            .unwrap();
1195
1196        let pool_id: String =
1197            "0x4626d81b3a1711beb79f4cecff2413886d461677000200000000000000000011".into();
1198
1199        let stateless_contracts = HashMap::from([(
1200            String::from("0x3de27efa2f1aa663ae5d458857e731c129069f29"),
1201            Some(Vec::new()),
1202        )]);
1203
1204        let balances = HashMap::from([
1205            (dai_addr(), U256::from_str("178754012737301807104").unwrap()),
1206            (bal_addr(), U256::from_str("91082987763369885696").unwrap()),
1207        ]);
1208        let adapter_address =
1209            Address::from_str("0xA2C5C98A892fD6656a7F39A2f63228C0Bc846270").unwrap();
1210
1211        EVMPoolStateBuilder::new(pool_id, tokens, adapter_address)
1212            .balances(balances)
1213            .balance_owner(Address::from_str("0xBA12222222228d8Ba445958a75a0704d566BF2C8").unwrap())
1214            .adapter_contract_bytecode(Bytecode::new_raw(BALANCER_V2.into()))
1215            .stateless_contracts(stateless_contracts)
1216            .build(db)
1217            .await
1218            .expect("Failed to build pool state")
1219    }
1220
1221    #[tokio::test]
1222    async fn test_init() {
1223        let pool_state = setup_pool_state().await;
1224
1225        let expected_capabilities = vec![
1226            Capability::SellSide,
1227            Capability::BuySide,
1228            Capability::PriceFunction,
1229            Capability::HardLimits,
1230        ]
1231        .into_iter()
1232        .collect::<HashSet<_>>();
1233
1234        let capabilities_adapter_contract = pool_state
1235            .adapter_contract
1236            .get_capabilities(
1237                &pool_state.id,
1238                bytes_to_address(&pool_state.tokens[0]).unwrap(),
1239                bytes_to_address(&pool_state.tokens[1]).unwrap(),
1240            )
1241            .unwrap();
1242
1243        assert_eq!(capabilities_adapter_contract, expected_capabilities.clone());
1244
1245        let capabilities_state = pool_state.clone().capabilities;
1246
1247        assert_eq!(capabilities_state, expected_capabilities.clone());
1248
1249        for capability in expected_capabilities.clone() {
1250            assert!(pool_state
1251                .clone()
1252                .ensure_capability(capability)
1253                .is_ok());
1254        }
1255
1256        assert!(pool_state
1257            .clone()
1258            .ensure_capability(Capability::MarginalPrice)
1259            .is_err());
1260
1261        // Verify all tokens are initialized in the engine
1262        let engine_accounts = pool_state
1263            .adapter_contract
1264            .engine
1265            .state
1266            .clone()
1267            .get_account_storage()
1268            .expect("Failed to get account storage");
1269        for token in pool_state.tokens.clone() {
1270            let account = engine_accounts
1271                .get_account_info(&bytes_to_address(&token).unwrap())
1272                .unwrap();
1273            assert_eq!(account.balance, U256::from(0));
1274            assert_eq!(account.nonce, 0u64);
1275            assert_eq!(account.code_hash, KECCAK_EMPTY);
1276            assert!(account.code.is_some());
1277        }
1278
1279        // Verify external account is initialized in the engine
1280        let external_account = engine_accounts
1281            .get_account_info(&EXTERNAL_ACCOUNT)
1282            .unwrap();
1283        assert_eq!(external_account.balance, U256::from(*MAX_BALANCE));
1284        assert_eq!(external_account.nonce, 0u64);
1285        assert_eq!(external_account.code_hash, KECCAK_EMPTY);
1286        assert!(external_account.code.is_none());
1287    }
1288
1289    #[tokio::test]
1290    async fn test_get_amount_out() -> Result<(), Box<dyn std::error::Error>> {
1291        let pool_state = setup_pool_state().await;
1292
1293        let result = pool_state
1294            .get_amount_out(BigUint::from_str("1000000000000000000").unwrap(), &dai(), &bal())
1295            .unwrap();
1296        let new_state = result
1297            .new_state
1298            .as_any()
1299            .downcast_ref::<EVMPoolState<PreCachedDB>>()
1300            .unwrap();
1301        assert_eq!(result.amount, BigUint::from_str("137780051463393923").unwrap());
1302        assert_ne!(new_state.spot_prices, pool_state.spot_prices);
1303        assert!(pool_state
1304            .block_lasting_overwrites
1305            .is_empty());
1306        Ok(())
1307    }
1308
1309    #[tokio::test]
1310    async fn test_sequential_get_amount_outs() {
1311        let pool_state = setup_pool_state().await;
1312
1313        let result = pool_state
1314            .get_amount_out(BigUint::from_str("1000000000000000000").unwrap(), &dai(), &bal())
1315            .unwrap();
1316        let new_state = result
1317            .new_state
1318            .as_any()
1319            .downcast_ref::<EVMPoolState<PreCachedDB>>()
1320            .unwrap();
1321        assert_eq!(result.amount, BigUint::from_str("137780051463393923").unwrap());
1322        assert_ne!(new_state.spot_prices, pool_state.spot_prices);
1323
1324        let new_result = new_state
1325            .get_amount_out(BigUint::from_str("1000000000000000000").unwrap(), &dai(), &bal())
1326            .unwrap();
1327        let new_state_second_swap = new_result
1328            .new_state
1329            .as_any()
1330            .downcast_ref::<EVMPoolState<PreCachedDB>>()
1331            .unwrap();
1332
1333        assert_eq!(new_result.amount, BigUint::from_str("136964651490065626").unwrap());
1334        assert_ne!(new_state_second_swap.spot_prices, new_state.spot_prices);
1335    }
1336
1337    #[tokio::test]
1338    async fn test_get_amount_out_dust() {
1339        let pool_state = setup_pool_state().await;
1340
1341        let result = pool_state
1342            .get_amount_out(BigUint::one(), &dai(), &bal())
1343            .unwrap();
1344
1345        let _ = result
1346            .new_state
1347            .as_any()
1348            .downcast_ref::<EVMPoolState<PreCachedDB>>()
1349            .unwrap();
1350        assert_eq!(result.amount, BigUint::ZERO);
1351    }
1352
1353    #[tokio::test]
1354    async fn test_get_amount_out_sell_limit() {
1355        let pool_state = setup_pool_state().await;
1356
1357        let result = pool_state.get_amount_out(
1358            // sell limit is 100279494253364362835
1359            BigUint::from_str("100379494253364362835").unwrap(),
1360            &dai(),
1361            &bal(),
1362        );
1363
1364        assert!(result.is_err());
1365
1366        match result {
1367            Err(SimulationError::InvalidInput(msg1, amount_out_result)) => {
1368                assert_eq!(msg1, "Sell amount exceeds limit 100279494253364362835");
1369                assert!(amount_out_result.is_some());
1370            }
1371            _ => panic!("Test failed: was expecting an Err(SimulationError::RetryDifferentInput(_, _)) value"),
1372        }
1373    }
1374
1375    #[tokio::test]
1376    async fn test_get_amount_limits() {
1377        let pool_state = setup_pool_state().await;
1378
1379        let overwrites = pool_state
1380            .get_overwrites(
1381                vec![
1382                    bytes_to_address(&pool_state.tokens[0]).unwrap(),
1383                    bytes_to_address(&pool_state.tokens[1]).unwrap(),
1384                ],
1385                *MAX_BALANCE / U256::from(100),
1386                None,
1387            )
1388            .unwrap();
1389        let (dai_limit, _) = pool_state
1390            .get_amount_limits(
1391                vec![dai_addr(), bal_addr()],
1392                Some(overwrites.clone()),
1393                pool_state.block_env(None),
1394            )
1395            .unwrap();
1396        assert_eq!(dai_limit, U256::from_str("100279494253364362835").unwrap());
1397
1398        let (bal_limit, _) = pool_state
1399            .get_amount_limits(
1400                vec![
1401                    bytes_to_address(&pool_state.tokens[1]).unwrap(),
1402                    bytes_to_address(&pool_state.tokens[0]).unwrap(),
1403                ],
1404                Some(overwrites),
1405                pool_state.block_env(None),
1406            )
1407            .unwrap();
1408        assert_eq!(bal_limit, U256::from_str("13997408640689987484").unwrap());
1409    }
1410
1411    #[tokio::test]
1412    async fn test_set_spot_prices() {
1413        let mut pool_state = setup_pool_state().await;
1414
1415        pool_state
1416            .set_spot_prices(
1417                &vec![bal(), dai()]
1418                    .into_iter()
1419                    .map(|t| (t.address.clone(), t))
1420                    .collect(),
1421            )
1422            .unwrap();
1423
1424        let dai_bal_spot_price = pool_state
1425            .spot_prices
1426            .get(&(
1427                bytes_to_address(&pool_state.tokens[0]).unwrap(),
1428                bytes_to_address(&pool_state.tokens[1]).unwrap(),
1429            ))
1430            .unwrap();
1431        let bal_dai_spot_price = pool_state
1432            .spot_prices
1433            .get(&(
1434                bytes_to_address(&pool_state.tokens[1]).unwrap(),
1435                bytes_to_address(&pool_state.tokens[0]).unwrap(),
1436            ))
1437            .unwrap();
1438        assert_eq!(dai_bal_spot_price, &0.137_778_914_319_047_9);
1439        assert_eq!(bal_dai_spot_price, &7.071_503_245_428_246);
1440    }
1441
1442    #[tokio::test]
1443    async fn test_set_spot_prices_without_capability() {
1444        // Tests set Spot Prices functions when the pool doesn't have PriceFunction capability
1445        let mut pool_state = setup_pool_state().await;
1446
1447        pool_state
1448            .capabilities
1449            .remove(&Capability::PriceFunction);
1450
1451        pool_state
1452            .set_spot_prices(
1453                &vec![bal(), dai()]
1454                    .into_iter()
1455                    .map(|t| (t.address.clone(), t))
1456                    .collect(),
1457            )
1458            .unwrap();
1459
1460        let dai_bal_spot_price = pool_state
1461            .spot_prices
1462            .get(&(
1463                bytes_to_address(&pool_state.tokens[0]).unwrap(),
1464                bytes_to_address(&pool_state.tokens[1]).unwrap(),
1465            ))
1466            .unwrap();
1467        let bal_dai_spot_price = pool_state
1468            .spot_prices
1469            .get(&(
1470                bytes_to_address(&pool_state.tokens[1]).unwrap(),
1471                bytes_to_address(&pool_state.tokens[0]).unwrap(),
1472            ))
1473            .unwrap();
1474        assert_eq!(dai_bal_spot_price, &0.13736685496467538);
1475        assert_eq!(bal_dai_spot_price, &7.050354297665408);
1476    }
1477
1478    #[tokio::test]
1479    async fn test_get_balance_overwrites_with_component_balances() {
1480        let pool_state: EVMPoolState<PreCachedDB> = setup_pool_state().await;
1481
1482        let overwrites = pool_state
1483            .get_balance_overwrites()
1484            .unwrap();
1485
1486        let dai_address = dai_addr();
1487        let bal_address = bal_addr();
1488        assert!(overwrites.contains_key(&dai_address));
1489        assert!(overwrites.contains_key(&bal_address));
1490    }
1491
1492    #[tokio::test]
1493    async fn test_get_balance_overwrites_with_contract_balances() {
1494        let mut pool_state: EVMPoolState<PreCachedDB> = setup_pool_state().await;
1495
1496        let contract_address =
1497            Address::from_str("0xBA12222222228d8Ba445958a75a0704d566BF2C8").unwrap();
1498
1499        // Ensure no component balances are used
1500        pool_state.balances.clear();
1501        pool_state.balance_owner = None;
1502
1503        // Set contract balances
1504        let dai_address = dai_addr();
1505        let bal_address = bal_addr();
1506        pool_state.contract_balances = HashMap::from([(
1507            contract_address,
1508            HashMap::from([
1509                (dai_address, U256::from_str("7500000000000000000000").unwrap()), // 7500 DAI
1510                (bal_address, U256::from_str("1500000000000000000000").unwrap()), // 1500 BAL
1511            ]),
1512        )]);
1513
1514        let overwrites = pool_state
1515            .get_balance_overwrites()
1516            .unwrap();
1517
1518        assert!(overwrites.contains_key(&dai_address));
1519        assert!(overwrites.contains_key(&bal_address));
1520    }
1521
1522    #[tokio::test]
1523    async fn test_balance_merging_during_delta_transition() {
1524        use std::str::FromStr;
1525
1526        let mut pool_state = setup_pool_state().await;
1527        let pool_id = pool_state.id.clone();
1528
1529        // Test the balance merging logic more directly
1530        // Setup initial balances including DAI and BAL (which the pool already knows about)
1531        let dai_addr = dai_addr();
1532        let bal_addr = bal_addr();
1533        let new_token = Address::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); // WETH
1534
1535        // Clear and setup clean initial state
1536        pool_state.balances.clear();
1537        pool_state
1538            .balances
1539            .insert(dai_addr, U256::from(1000000000u64));
1540        pool_state
1541            .balances
1542            .insert(bal_addr, U256::from(2000000000u64));
1543        pool_state
1544            .balances
1545            .insert(new_token, U256::from(3000000000u64));
1546
1547        // Create tokens mapping including the existing DAI and BAL
1548        let mut tokens = HashMap::new();
1549        tokens.insert(dai().address.clone(), dai());
1550        tokens.insert(bal().address.clone(), bal());
1551
1552        // Simulate a delta transition with only DAI balance update (missing BAL and new_token)
1553        let mut component_balances = HashMap::new();
1554        let mut delta_balances = HashMap::new();
1555        // Only update DAI balance, leave others unchanged in delta
1556        delta_balances.insert(dai().address.clone(), Bytes::from(vec![0x77, 0x35, 0x94, 0x00])); // 2000000000 (updated value)
1557        component_balances.insert(pool_id.clone(), delta_balances);
1558
1559        let balances = Balances { component_balances, account_balances: HashMap::new() };
1560
1561        // Record initial balance count
1562        let initial_balance_count = pool_state.balances.len();
1563        assert_eq!(initial_balance_count, 3);
1564
1565        // Apply delta transition
1566        pool_state
1567            .update_pool_state(&tokens, &balances)
1568            .unwrap();
1569
1570        // Verify that all 3 balances are preserved (BAL and new_token should still be there)
1571        assert_eq!(
1572            pool_state.balances.len(),
1573            3,
1574            "All balances should be preserved after delta transition"
1575        );
1576        assert!(
1577            pool_state
1578                .balances
1579                .contains_key(&dai_addr),
1580            "DAI balance should be present"
1581        );
1582        assert!(
1583            pool_state
1584                .balances
1585                .contains_key(&bal_addr),
1586            "BAL balance should be present"
1587        );
1588        assert!(
1589            pool_state
1590                .balances
1591                .contains_key(&new_token),
1592            "New token balance should be preserved from before delta"
1593        );
1594
1595        // Verify that updated token (DAI) has new value
1596        assert_eq!(
1597            pool_state.balances[&dai_addr],
1598            U256::from(2000000000u64),
1599            "DAI balance should be updated"
1600        );
1601
1602        // Verify that non-updated tokens retain their original values
1603        assert_eq!(
1604            pool_state.balances[&bal_addr],
1605            U256::from(2000000000u64),
1606            "BAL balance should be unchanged"
1607        );
1608        assert_eq!(
1609            pool_state.balances[&new_token],
1610            U256::from(3000000000u64),
1611            "New token balance should be unchanged"
1612        );
1613    }
1614
1615    #[tokio::test]
1616    async fn test_delta_transition_updates_block_overrides() {
1617        let mut pool_state = setup_pool_state().await;
1618        pool_state.manual_updates = true;
1619        pool_state.block_overrides = None;
1620
1621        let delta = ProtocolStateDelta {
1622            component_id: pool_state.id.clone(),
1623            updated_attributes: HashMap::from([
1624                ("override_block_number".to_string(), Bytes::from(123_u64.to_be_bytes().to_vec())),
1625                (
1626                    "override_block_timestamp".to_string(),
1627                    Bytes::from(456_u64.to_be_bytes().to_vec()),
1628                ),
1629            ]),
1630            deleted_attributes: HashSet::new(),
1631        };
1632
1633        pool_state
1634            .delta_transition(delta, &HashMap::new(), &Balances::default())
1635            .unwrap();
1636
1637        assert_eq!(
1638            pool_state.block_overrides,
1639            Some(BlockEnvOverrides { number: Some(123), timestamp: Some(456) })
1640        );
1641    }
1642
1643    #[tokio::test]
1644    async fn test_delta_transition_updates_partial_block_overrides() {
1645        let mut pool_state = setup_pool_state().await;
1646        pool_state.manual_updates = true;
1647        pool_state.block_overrides =
1648            Some(BlockEnvOverrides { number: Some(123), timestamp: Some(456) });
1649
1650        let delta = ProtocolStateDelta {
1651            component_id: pool_state.id.clone(),
1652            updated_attributes: HashMap::from([(
1653                "override_block_number".to_string(),
1654                Bytes::from(789_u64.to_be_bytes().to_vec()),
1655            )]),
1656            deleted_attributes: HashSet::new(),
1657        };
1658
1659        pool_state
1660            .delta_transition(delta, &HashMap::new(), &Balances::default())
1661            .unwrap();
1662
1663        assert_eq!(
1664            pool_state.block_overrides,
1665            Some(BlockEnvOverrides { number: Some(789), timestamp: Some(456) })
1666        );
1667    }
1668
1669    /// `update_pool_state` must refresh BOTH component balances and tracked contract balances,
1670    /// not treat them as mutually exclusive. Balancer V3 is hybrid (component balances at the
1671    /// vault owner + tracked vault contract balances); freezing contract balances while the
1672    /// vault's indexed reserves advance is what caused the WETH-in `BalanceNotSettled` failures.
1673    #[tokio::test]
1674    async fn test_delta_transition_refreshes_both_balance_maps() {
1675        let mut pool_state = setup_pool_state().await;
1676        let vault = Address::from_str("0xBA12222222228d8Ba445958a75a0704d566BF2C8").unwrap();
1677        // Non-manual pool (how Balancer V3 behaves once `manual_updates` is dropped): a contract
1678        // change routes the pool through delta_transition, which refreshes its balances.
1679        pool_state.manual_updates = false;
1680        pool_state.involved_contracts = HashSet::from([vault]);
1681        pool_state.contract_balances =
1682            HashMap::from([(vault, HashMap::from([(dai_addr(), U256::from(1u64))]))]);
1683
1684        let delta = ProtocolStateDelta {
1685            component_id: pool_state.id.clone(),
1686            updated_attributes: HashMap::new(),
1687            deleted_attributes: HashSet::new(),
1688        };
1689        let balances = Balances {
1690            component_balances: HashMap::new(),
1691            account_balances: HashMap::from([(
1692                Bytes::from(vault.as_slice()),
1693                HashMap::from([(dai().address.clone(), Bytes::from(42u64).lpad(32, 0))]),
1694            )]),
1695        };
1696
1697        // The spot-price refresh inside update_pool_state may fail in offline test
1698        // environments; the balance bookkeeping this test guards happens before it.
1699        let _ = pool_state.delta_transition(delta, &HashMap::new(), &balances);
1700
1701        // Contract balance refreshed even though component balances are non-empty (the old
1702        // mutual-exclusion would have skipped this branch entirely).
1703        assert_eq!(pool_state.contract_balances[&vault][&dai_addr()], U256::from(42u64));
1704        // Component balances stay untouched by a contract-balance-only delta.
1705        assert_eq!(
1706            pool_state.balances[&dai_addr()],
1707            U256::from_str("178754012737301807104").unwrap()
1708        );
1709    }
1710
1711    #[test]
1712    fn should_not_panic_at_typetag_deserialize() {
1713        let deserialized: Result<Box<dyn ProtocolSim>, _> = serde_json::from_str(
1714            r#"{"protocol":"EVMPoolState","state":{"reserve_0":1,"reserve_1":2}}"#,
1715        );
1716
1717        assert!(deserialized.is_err());
1718    }
1719
1720    /// A live snapshot's block number/timestamp take precedence over the static block overrides,
1721    /// field by field, while an absent or empty snapshot leaves them untouched.
1722    #[tokio::test]
1723    async fn test_block_env_prefers_live_overrides() {
1724        let mut pool_state = setup_pool_state().await;
1725        pool_state.block_overrides =
1726            Some(BlockEnvOverrides { number: Some(100), timestamp: Some(1_000) });
1727
1728        // No live snapshot: the statically configured overrides are used unchanged.
1729        assert_eq!(
1730            pool_state.block_env(None),
1731            Some(BlockEnvOverrides { number: Some(100), timestamp: Some(1_000) })
1732        );
1733
1734        // A live snapshot with neither field set does not touch the static overrides.
1735        let empty = OverrideSnapshot::default();
1736        assert_eq!(
1737            pool_state.block_env(Some(&empty)),
1738            Some(BlockEnvOverrides { number: Some(100), timestamp: Some(1_000) })
1739        );
1740
1741        // Present live fields win; unset live fields keep the static value.
1742        let live = OverrideSnapshot {
1743            block_number: Some(200),
1744            block_timestamp: None,
1745            ..Default::default()
1746        };
1747        assert_eq!(
1748            pool_state.block_env(Some(&live)),
1749            Some(BlockEnvOverrides { number: Some(200), timestamp: Some(1_000) })
1750        );
1751
1752        // With no static overrides, the live block environment stands on its own.
1753        pool_state.block_overrides = None;
1754        let live = OverrideSnapshot {
1755            block_number: Some(300),
1756            block_timestamp: Some(3_000),
1757            ..Default::default()
1758        };
1759        assert_eq!(
1760            pool_state.block_env(Some(&live)),
1761            Some(BlockEnvOverrides { number: Some(300), timestamp: Some(3_000) })
1762        );
1763    }
1764
1765    /// Live storage is merged into the computed overwrites: a fresh contract is added, a value on a
1766    /// slot the baseline already sets is overridden (live wins on conflict), and empty live storage
1767    /// is a no-op.
1768    #[tokio::test]
1769    async fn test_get_overwrites_applies_live_storage() {
1770        let pool_state = setup_pool_state().await;
1771        let tokens = vec![dai_addr(), bal_addr()];
1772        let max = *MAX_BALANCE / U256::from(100);
1773
1774        let baseline = pool_state
1775            .get_overwrites(tokens.clone(), max, None)
1776            .unwrap();
1777
1778        // An empty live snapshot leaves the overwrites unchanged.
1779        let empty = OverrideSnapshot::default();
1780        assert_eq!(
1781            pool_state
1782                .get_overwrites(tokens.clone(), max, Some(&empty))
1783                .unwrap(),
1784            baseline
1785        );
1786
1787        // Pick an address/slot the baseline already sets, so we can assert live wins the conflict.
1788        let (conflict_addr, conflict_slot, baseline_val) = {
1789            let (addr, slots) = baseline
1790                .iter()
1791                .next()
1792                .expect("baseline has overwrites");
1793            let (slot, val) = slots
1794                .iter()
1795                .next()
1796                .expect("address has slots");
1797            (*addr, *slot, *val)
1798        };
1799        let sentinel = baseline_val + U256::from(1);
1800        let fresh = Address::from([0xAB; 20]);
1801        assert!(
1802            !baseline.contains_key(&fresh),
1803            "fresh address must originate from the live snapshot"
1804        );
1805
1806        let live = OverrideSnapshot {
1807            storage: std::sync::Arc::new(HashMap::from([
1808                (fresh, HashMap::from([(U256::from(7), U256::from(123))])),
1809                (conflict_addr, HashMap::from([(conflict_slot, sentinel)])),
1810            ])),
1811            ..Default::default()
1812        };
1813        let with_live = pool_state
1814            .get_overwrites(tokens, max, Some(&live))
1815            .unwrap();
1816
1817        assert_eq!(
1818            with_live
1819                .get(&fresh)
1820                .and_then(|slots| slots.get(&U256::from(7))),
1821            Some(&U256::from(123)),
1822            "live storage for a fresh contract must be merged in"
1823        );
1824        assert_eq!(
1825            with_live
1826                .get(&conflict_addr)
1827                .and_then(|slots| slots.get(&conflict_slot)),
1828            Some(&sentinel),
1829            "live override must win on slot conflict"
1830        );
1831    }
1832
1833    /// `get_live_snapshot` returns the attached snapshot only while it is fresh: an expired one is
1834    /// dropped (so the pool reverts to indexed state), and no channel means no snapshot. Expiry is
1835    /// pinned to the extremes (`1` = long past, `u64::MAX` = effectively never) so the assertions
1836    /// are independent of the wall clock.
1837    #[tokio::test]
1838    async fn test_get_live_snapshot_drops_expired() {
1839        let mut pool_state = setup_pool_state().await;
1840
1841        // No channel attached: nothing to read.
1842        assert!(pool_state.get_live_snapshot().is_none());
1843
1844        // A snapshot without an expiry never goes stale.
1845        let never =
1846            OverrideSnapshot { block_number: Some(1), expires_at: None, ..Default::default() };
1847        let (_never_tx, never_rx) = watch::channel(never);
1848        pool_state.set_live_overrides(never_rx);
1849        assert_eq!(
1850            pool_state
1851                .get_live_snapshot()
1852                .and_then(|snapshot| snapshot.block_number),
1853            Some(1)
1854        );
1855
1856        // A snapshot whose expiry is far in the future is returned.
1857        let fresh = OverrideSnapshot {
1858            block_number: Some(42),
1859            expires_at: Some(u64::MAX),
1860            ..Default::default()
1861        };
1862        let (_fresh_tx, fresh_rx) = watch::channel(fresh);
1863        pool_state.set_live_overrides(fresh_rx);
1864        assert_eq!(
1865            pool_state
1866                .get_live_snapshot()
1867                .and_then(|snapshot| snapshot.block_number),
1868            Some(42)
1869        );
1870
1871        // A snapshot whose expiry is in the past is dropped.
1872        let expired =
1873            OverrideSnapshot { block_number: Some(42), expires_at: Some(1), ..Default::default() };
1874        let (_expired_tx, expired_rx) = watch::channel(expired);
1875        pool_state.set_live_overrides(expired_rx);
1876        assert!(pool_state.get_live_snapshot().is_none());
1877    }
1878
1879    /// A live snapshot that corrupts the Balancer Vault's low storage slots (pause / reentrancy
1880    /// state), guaranteeing that any simulation run with it applied reverts.
1881    fn poison_snapshot(failure_policy: FailurePolicy) -> OverrideSnapshot {
1882        let vault: Address = "0xBA12222222228d8Ba445958a75a0704d566BF2C8"
1883            .parse()
1884            .unwrap();
1885        let poisoned_slots = (0u64..10)
1886            .map(|slot| (U256::from(slot), U256::MAX))
1887            .collect();
1888        OverrideSnapshot {
1889            storage: std::sync::Arc::new(HashMap::from([(vault, poisoned_slots)])),
1890            failure_policy,
1891            ..Default::default()
1892        }
1893    }
1894
1895    /// With the default [`FailurePolicy::Error`], a snapshot that breaks the simulation surfaces
1896    /// the failure to the caller.
1897    #[tokio::test]
1898    async fn test_failing_overrides_error_by_default() {
1899        let mut pool_state = setup_pool_state().await;
1900        let poison = poison_snapshot(FailurePolicy::Error);
1901        let (_tx, rx) = watch::channel(poison);
1902        pool_state.set_live_overrides(rx);
1903
1904        assert!(pool_state
1905            .get_amount_out(BigUint::from_str("1000000000000000000").unwrap(), &dai(), &bal())
1906            .is_err());
1907    }
1908
1909    /// With [`FailurePolicy::FallbackToIndexedState`], a snapshot that breaks the simulation is
1910    /// dropped and the operation is retried on the plain indexed state, matching the result the
1911    /// pool produces without any live overrides.
1912    #[tokio::test]
1913    async fn test_failing_overrides_fall_back_to_indexed_state() {
1914        let pool_state = setup_pool_state().await;
1915        let expected = pool_state
1916            .get_amount_out(BigUint::from_str("1000000000000000000").unwrap(), &dai(), &bal())
1917            .unwrap();
1918        let expected_limits = pool_state
1919            .get_limits(dai().address.clone(), bal().address.clone())
1920            .unwrap();
1921
1922        let mut pool_state = pool_state;
1923        let poison = poison_snapshot(FailurePolicy::FallbackToIndexedState);
1924        let (_tx, rx) = watch::channel(poison);
1925        pool_state.set_live_overrides(rx);
1926
1927        let result = pool_state
1928            .get_amount_out(BigUint::from_str("1000000000000000000").unwrap(), &dai(), &bal())
1929            .expect("must fall back to indexed state");
1930        assert_eq!(result.amount, expected.amount);
1931
1932        let limits = pool_state
1933            .get_limits(dai().address.clone(), bal().address.clone())
1934            .expect("limits must fall back to indexed state");
1935        assert_eq!(limits, expected_limits);
1936
1937        let tokens =
1938            HashMap::from([(dai().address.clone(), dai()), (bal().address.clone(), bal())]);
1939        pool_state
1940            .set_spot_prices(&tokens)
1941            .expect("spot prices must fall back to indexed state");
1942    }
1943}