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