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