Skip to main content

tycho_simulation/evm/engine_db/
simulation_db.rs

1use std::{
2    collections::HashMap,
3    fmt::Debug,
4    sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard},
5};
6
7use alloy::{
8    primitives::{Address, Bytes as AlloyBytes, StorageValue, B256, U256},
9    providers::{
10        fillers::{BlobGasFiller, ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller},
11        Provider, RootProvider,
12    },
13    transports::{RpcError, TransportErrorKind},
14};
15use revm::{
16    context::DBErrorMarker,
17    state::{AccountInfo, Bytecode},
18    DatabaseRef,
19};
20use thiserror::Error;
21use tracing::{debug, info};
22use tycho_client::feed::BlockHeader;
23
24use super::{
25    super::account_storage::{AccountStorage, StateUpdate},
26    engine_db_interface::EngineDatabaseInterface,
27};
28
29/// A wrapper over an actual SimulationDB that allows overriding specific storage slots
30/// and native balances
31pub struct OverriddenSimulationDB<'a, DB: DatabaseRef> {
32    /// Wrapped database. Will be queried if a requested item is not found in the overrides.
33    pub inner_db: &'a DB,
34    /// A mapping from account address to storage.
35    /// Storage is a mapping from slot index to slot value.
36    pub overrides: &'a HashMap<Address, HashMap<U256, U256>>,
37    /// A mapping from account address to its overridden native balance.
38    /// An override for an account absent from the inner database materializes it as an
39    /// empty account holding that balance.
40    pub native_balance_overrides: &'a HashMap<Address, U256>,
41}
42
43impl<'a, DB: DatabaseRef> OverriddenSimulationDB<'a, DB> {
44    /// Creates a new OverriddenSimulationDB.
45    pub fn new(
46        inner_db: &'a DB,
47        overrides: &'a HashMap<Address, HashMap<U256, U256>>,
48        native_balance_overrides: &'a HashMap<Address, U256>,
49    ) -> Self {
50        OverriddenSimulationDB { inner_db, overrides, native_balance_overrides }
51    }
52}
53
54impl<DB: DatabaseRef> DatabaseRef for OverriddenSimulationDB<'_, DB> {
55    type Error = DB::Error;
56
57    fn basic_ref(&self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
58        let Some(balance) = self
59            .native_balance_overrides
60            .get(&address)
61        else {
62            return self.inner_db.basic_ref(address);
63        };
64        let info = self
65            .inner_db
66            .basic_ref(address)
67            .ok()
68            .flatten();
69        Ok(Some(AccountInfo { balance: *balance, ..info.unwrap_or_default() }))
70    }
71
72    fn code_by_hash_ref(&self, code_hash: B256) -> Result<Bytecode, Self::Error> {
73        self.inner_db
74            .code_by_hash_ref(code_hash)
75    }
76
77    fn storage_ref(&self, address: Address, index: U256) -> Result<U256, Self::Error> {
78        match self.overrides.get(&address) {
79            None => self
80                .inner_db
81                .storage_ref(address, index),
82            Some(slot_overrides) => match slot_overrides.get(&index) {
83                Some(value) => {
84                    debug!(%address, %index, %value, "Requested storage of account {:x?} slot {}", address, index);
85                    Ok(*value)
86                }
87                None => self
88                    .inner_db
89                    .storage_ref(address, index),
90            },
91        }
92    }
93
94    fn block_hash_ref(&self, number: u64) -> Result<B256, Self::Error> {
95        self.inner_db.block_hash_ref(number)
96    }
97}
98
99/// A wrapper over an Alloy Provider with local storage cache and overrides.
100#[derive(Clone, Debug)]
101pub struct SimulationDB<P: Provider + Debug> {
102    /// Client to connect to the RPC
103    client: Arc<P>,
104    /// Cached data
105    account_storage: Arc<RwLock<AccountStorage>>,
106    /// Current block
107    block: Option<BlockHeader>,
108    /// Tokio runtime to execute async code
109    pub runtime: Option<Arc<tokio::runtime::Runtime>>,
110}
111
112pub type EVMProvider = FillProvider<
113    JoinFill<
114        alloy::providers::Identity,
115        JoinFill<GasFiller, JoinFill<BlobGasFiller, JoinFill<NonceFiller, ChainIdFiller>>>,
116    >,
117    RootProvider,
118>;
119
120impl<P: Provider + Debug + 'static> SimulationDB<P> {
121    pub fn new(
122        client: Arc<P>,
123        runtime: Option<Arc<tokio::runtime::Runtime>>,
124        block: Option<BlockHeader>,
125    ) -> Self {
126        Self {
127            client,
128            account_storage: Arc::new(RwLock::new(AccountStorage::new())),
129            block,
130            runtime,
131        }
132    }
133
134    /// Set the block that will be used when querying a node
135    pub fn set_block(&mut self, block: Option<BlockHeader>) {
136        self.block = block;
137    }
138
139    /// Update the simulation state.
140    ///
141    /// Updates the underlying smart contract storage. Any previously missed account,
142    /// which was queried and whose state now is in the account_storage will be cleared.
143    ///
144    /// # Arguments
145    ///
146    /// * `updates` - Values for the updates that should be applied to the accounts
147    /// * `block` - The newest block
148    ///
149    /// Returns a state update struct to revert this update.
150    pub fn update_state(
151        &mut self,
152        updates: &HashMap<Address, StateUpdate>,
153        block: BlockHeader,
154    ) -> Result<HashMap<Address, StateUpdate>, SimulationDBError> {
155        info!("Received account state update.");
156        let mut revert_updates = HashMap::new();
157        self.block = Some(block);
158        for (address, update_info) in updates.iter() {
159            let mut revert_entry = StateUpdate::default();
160            if let Some(current_account) = self
161                .read_account_storage()?
162                .get_account_info(address)
163            {
164                revert_entry.balance = Some(current_account.balance);
165            }
166            if let Some(storage_updates) = update_info.storage.as_ref() {
167                let mut revert_storage = HashMap::default();
168                for index in storage_updates.keys() {
169                    if let Some(s) = self
170                        .read_account_storage()?
171                        .get_permanent_storage(address, index)
172                    {
173                        revert_storage.insert(*index, s);
174                    }
175                }
176                revert_entry.storage = Some(revert_storage);
177            }
178            revert_updates.insert(*address, revert_entry);
179
180            self.write_account_storage()?
181                .update_account(address, update_info);
182        }
183        Ok(revert_updates)
184    }
185
186    /// Query information about an Ethereum account.
187    /// Gets account information not including storage.
188    ///
189    /// # Arguments
190    ///
191    /// * `address` - The Ethereum address to query.
192    ///
193    /// # Returns
194    ///
195    /// Returns a `Result` containing either an `AccountInfo` object with balance, nonce, and code
196    /// information, or an error of type `SimulationDB<M>::Error` if the query fails.
197    fn query_account_info(
198        &self,
199        address: Address,
200    ) -> Result<AccountInfo, <SimulationDB<P> as DatabaseRef>::Error> {
201        debug!("Querying account info of {:x?} at block {:?}", address, self.block);
202
203        let (balance, nonce, code) = self.block_on(async {
204            let mut balance_request = self.client.get_balance(address);
205            let mut nonce_request = self
206                .client
207                .get_transaction_count(address);
208            let mut code_request = self.client.get_code_at(address);
209
210            if let Some(block) = &self.block {
211                balance_request = balance_request.number(block.number);
212                nonce_request = nonce_request.number(block.number);
213                code_request = code_request.number(block.number);
214            }
215
216            tokio::join!(balance_request, nonce_request, code_request,)
217        });
218        let code = Bytecode::new_raw(AlloyBytes::copy_from_slice(&code?));
219
220        Ok(AccountInfo::new(balance?, nonce?, code.hash_slow(), code))
221    }
222
223    /// Queries a value from storage at the specified index for a given Ethereum account.
224    ///
225    /// # Arguments
226    ///
227    /// * `address` - The Ethereum address of the account.
228    /// * `index` - The index of the storage value to query.
229    ///
230    /// # Returns
231    ///
232    /// Returns a `Result` containing the value from storage at the specified index as an `U256`,
233    /// or an error of type `SimulationDB<M>::Error` if the query fails.
234    pub fn query_storage(
235        &self,
236        address: Address,
237        index: U256,
238    ) -> Result<StorageValue, <SimulationDB<P> as DatabaseRef>::Error> {
239        let mut request = self
240            .client
241            .get_storage_at(address, index);
242        if let Some(block) = &self.block {
243            request = request.number(block.number);
244        }
245
246        let storage_future = async move {
247            request.await.map_err(|err| {
248                SimulationDBError::SimulationError(format!(
249                    "Failed to fetch storage for {address:?} slot {index}: {err}"
250                ))
251            })
252        };
253
254        self.block_on(storage_future)
255    }
256
257    fn read_account_storage(
258        &self,
259    ) -> Result<RwLockReadGuard<'_, AccountStorage>, SimulationDBError> {
260        self.account_storage
261            .read()
262            .map_err(|_| SimulationDBError::Internal("Account storage read lock poisoned".into()))
263    }
264
265    fn write_account_storage(
266        &self,
267    ) -> Result<RwLockWriteGuard<'_, AccountStorage>, SimulationDBError> {
268        self.account_storage
269            .write()
270            .map_err(|_| SimulationDBError::Internal("Account storage write lock poisoned".into()))
271    }
272
273    fn block_on<F: core::future::Future>(&self, f: F) -> F::Output {
274        // If we get here and have to block the current thread, we really
275        // messed up indexing / filling the storage. In that case this will save us
276        // at the price of a very high time penalty.
277        match &self.runtime {
278            Some(runtime) => runtime.block_on(f),
279            None => futures::executor::block_on(f),
280        }
281    }
282}
283
284impl<P: Provider + Debug> EngineDatabaseInterface for SimulationDB<P>
285where
286    P: Provider + Send + Sync + 'static,
287{
288    type Error = SimulationDBError;
289
290    /// Sets up a single account
291    ///
292    /// Full control over setting up an accounts. Allows to set up EOAs as
293    /// well as smart contracts.
294    ///
295    /// # Arguments
296    ///
297    /// * `address` - Address of the account
298    /// * `account` - The account information
299    /// * `permanent_storage` - Storage to init the account with this storage can only be updated
300    ///   manually.
301    /// * `mocked` - Whether this account should be considered mocked. For mocked accounts, nothing
302    ///   is downloaded from a node; all data must be inserted manually.
303    fn init_account(
304        &self,
305        address: Address,
306        mut account: AccountInfo,
307        permanent_storage: Option<HashMap<U256, U256>>,
308        mocked: bool,
309    ) -> Result<(), <Self as EngineDatabaseInterface>::Error> {
310        if let Some(code) = account.code.clone() {
311            account.code = Some(code);
312        }
313
314        self.write_account_storage()?
315            .init_account(address, account, permanent_storage, mocked);
316
317        Ok(())
318    }
319
320    /// Clears temp storage
321    ///
322    /// It is recommended to call this after a new block is received,
323    /// to avoid stored state leading to wrong results.
324    fn clear_temp_storage(&mut self) -> Result<(), <Self as EngineDatabaseInterface>::Error> {
325        self.write_account_storage()?
326            .clear_temp_storage();
327
328        Ok(())
329    }
330
331    fn get_current_block(&self) -> Option<BlockHeader> {
332        self.block.clone()
333    }
334}
335
336#[derive(Error, Debug)]
337pub enum SimulationDBError {
338    #[error("Simulation error: {0} ")]
339    SimulationError(String),
340    #[error("Not implemented error: {0}")]
341    NotImplementedError(String),
342    #[error("Simulation DB internal error: {0}")]
343    Internal(String),
344}
345
346impl DBErrorMarker for SimulationDBError {}
347
348impl From<RpcError<TransportErrorKind>> for SimulationDBError {
349    fn from(err: RpcError<TransportErrorKind>) -> Self {
350        SimulationDBError::SimulationError(err.to_string())
351    }
352}
353
354impl<P: Provider> DatabaseRef for SimulationDB<P>
355where
356    P: Provider + Debug + Send + Sync + 'static,
357{
358    type Error = SimulationDBError;
359
360    /// Retrieves basic information about an account.
361    ///
362    /// This function retrieves the basic account information for the specified address.
363    /// If the account is present in the storage, the stored account information is returned.
364    /// If the account is not present in the storage, the function queries the account information
365    /// from the contract and initializes the account in the storage with the retrieved
366    /// information.
367    ///
368    /// # Arguments
369    ///
370    /// * `address`: The address of the account to retrieve the information for.
371    ///
372    /// # Returns
373    ///
374    /// Returns a `Result` containing an `Option` that holds the account information if it exists.
375    /// If the account is not found, `None` is returned.
376    ///
377    /// # Errors
378    ///
379    /// Returns an error if there was an issue querying the account information from the contract or
380    /// accessing the storage.
381    ///
382    /// # Notes
383    ///
384    /// * If the account is present in the storage, the function returns a clone of the stored
385    ///   account information.
386    ///
387    /// * If the account is not present in the storage, the function queries the account information
388    ///   from the contract, initializes the account in the storage with the retrieved information,
389    ///   and returns a clone of the account information.
390    fn basic_ref(&self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
391        if let Some(account) = {
392            self.read_account_storage()?
393                .get_account_info(&address)
394                .cloned()
395        } {
396            return Ok(Some(account));
397        }
398        let account_info = self.query_account_info(address)?;
399        self.init_account(address, account_info.clone(), None, false)?;
400        Ok(Some(account_info))
401    }
402
403    fn code_by_hash_ref(&self, _code_hash: B256) -> Result<Bytecode, Self::Error> {
404        Err(SimulationDBError::NotImplementedError(
405            "Code by hash is not implemented in SimulationDB".to_string(),
406        ))
407    }
408
409    /// Retrieves the storage value at the specified address and index.
410    ///
411    /// If we don't know the value, and the accessed contract is mocked, the function returns
412    /// an empty slot instead of querying a node, to avoid potentially returning garbage values.
413    ///
414    /// # Arguments
415    ///
416    /// * `address`: The address of the contract to retrieve the storage value from.
417    /// * `index`: The index of the storage value to retrieve.
418    ///
419    /// # Returns
420    ///
421    /// Returns a `Result` containing the storage value if it exists. If the contract is mocked
422    /// and the storage value is not found locally, an empty slot is returned as `U256::ZERO`.
423    ///
424    /// # Errors
425    ///
426    /// Returns an error if there was an issue querying the storage value from the contract or
427    /// accessing the storage.
428    ///
429    /// # Notes
430    ///
431    /// * If the contract is present locally and is mocked, the function first checks if the storage
432    ///   value exists locally. If found, it returns the stored value. If not found, it returns an
433    ///   empty slot. Mocked contracts are not expected to have valid storage values, so the
434    ///   function does not query a node in this case.
435    ///
436    /// * If the contract is present locally and is not mocked, the function checks if the storage
437    ///   value exists locally. If found, it returns the stored value. If not found, it queries the
438    ///   storage value from a node, stores it locally, and returns it.
439    ///
440    /// * If the contract is not present locally, the function queries the account info and storage
441    ///   value from a node, initializes the account locally with the retrieved information, and
442    ///   returns the storage value.
443    fn storage_ref(&self, address: Address, index: U256) -> Result<U256, Self::Error> {
444        debug!("Requested storage of account {:x?} slot {}", address, index);
445        let (is_mocked, local_value) = {
446            let account_storage = self.read_account_storage()?;
447            (
448                account_storage.is_mocked_account(&address),
449                account_storage.get_storage(&address, &index),
450            )
451        };
452
453        if let Some(storage_value) = local_value {
454            debug!(
455                "Got value locally. This is a {} account. Value: {}",
456                if is_mocked.unwrap_or(false) { "mocked" } else { "non-mocked" },
457                storage_value
458            );
459            return Ok(storage_value);
460        }
461
462        // At this point we know we don't have data for this storage slot.
463        match is_mocked {
464            Some(true) => {
465                debug!("This is a mocked account for which we don't have data. Returning zero.");
466                Ok(U256::ZERO)
467            }
468            Some(false) => {
469                let storage_value = self.query_storage(address, index)?;
470                self.write_account_storage()?
471                    .set_temp_storage(address, index, storage_value);
472                debug!(
473                    "This is a non-mocked account for which we didn't have data. Fetched value: {}",
474                    storage_value
475                );
476                Ok(storage_value)
477            }
478            None => {
479                let account_info = self.query_account_info(address)?;
480                let storage_value = self.query_storage(address, index)?;
481                self.init_account(address, account_info, None, false)?;
482                self.write_account_storage()?
483                    .set_temp_storage(address, index, storage_value);
484                debug!("This is non-initialised account. Fetched value: {}", storage_value);
485                Ok(storage_value)
486            }
487        }
488    }
489
490    /// If block header is set, returns the hash. Otherwise returns a zero hash
491    /// instead of querying a node.
492    fn block_hash_ref(&self, _number: u64) -> Result<B256, Self::Error> {
493        match &self.block {
494            Some(header) => Ok(B256::from_slice(&header.hash)),
495            None => Ok(B256::ZERO),
496        }
497    }
498}
499
500#[cfg(test)]
501mod tests {
502    use std::{error::Error, str::FromStr};
503
504    use alloy::primitives::U160;
505    use rstest::rstest;
506    use tycho_common::Bytes;
507
508    use super::*;
509    use crate::evm::engine_db::utils::{get_client, get_runtime};
510
511    #[rstest]
512    fn test_query_storage_latest_block() -> Result<(), Box<dyn Error>> {
513        let db = SimulationDB::new(
514            get_client(None).expect("Failed to create test client"),
515            get_runtime().expect("Failed to create test runtime"),
516            None,
517        );
518        let address = Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc")?;
519        let index = U256::from_limbs_slice(&[8]);
520        db.init_account(address, AccountInfo::default(), None, false)
521            .expect("Failed to init account");
522
523        db.query_storage(address, index)
524            .unwrap();
525
526        // There is no assertion, but has the querying failed, we would have panicked by now.
527        // This test is not deterministic as it depends on the current state of the blockchain.
528        // See the next test where we do this for a specific block.
529        Ok(())
530    }
531
532    #[rstest]
533    fn test_query_account_info() {
534        let mut db = SimulationDB::new(
535            get_client(None).expect("Failed to create test client"),
536            get_runtime().expect("Failed to create test runtime"),
537            None,
538        );
539        let block = BlockHeader {
540            number: 20308186,
541            hash: Bytes::from_str(
542                "0x61c51e3640b02ae58a03201be0271e84e02dac8a4826501995cbe4da24174b52",
543            )
544            .unwrap(),
545            timestamp: 234,
546            ..Default::default()
547        };
548        db.set_block(Some(block));
549        let address = Address::from_str("0x168b93113fe5902c87afaecE348581A1481d0f93").unwrap();
550        db.init_account(address, AccountInfo::default(), None, false)
551            .expect("Failed to init account");
552
553        let account_info = db.query_account_info(address).unwrap();
554
555        assert_eq!(account_info.balance, U256::from_str("6246978663692389").unwrap());
556        assert_eq!(account_info.nonce, 17);
557    }
558
559    #[rstest]
560    fn test_mock_account_get_acc_info() {
561        let db = SimulationDB::new(
562            get_client(None).expect("Failed to create test client"),
563            get_runtime().expect("Failed to create test runtime"),
564            None,
565        );
566        let mock_acc_address =
567            Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc").unwrap();
568        db.init_account(mock_acc_address, AccountInfo::default(), None, true)
569            .expect("Failed to init account");
570
571        let acc_info = db
572            .basic_ref(mock_acc_address)
573            .unwrap()
574            .unwrap();
575
576        assert_eq!(
577            db.account_storage
578                .read()
579                .unwrap()
580                .get_account_info(&mock_acc_address)
581                .unwrap(),
582            &acc_info
583        );
584    }
585
586    #[rstest]
587    fn test_mock_account_get_storage() {
588        let db = SimulationDB::new(
589            get_client(None).expect("Failed to create test client"),
590            get_runtime().expect("Failed to create test runtime"),
591            None,
592        );
593        let mock_acc_address =
594            Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc").unwrap();
595        let storage_address = U256::ZERO;
596        db.init_account(mock_acc_address, AccountInfo::default(), None, true)
597            .expect("Failed to init account");
598
599        let storage = db
600            .storage_ref(mock_acc_address, storage_address)
601            .unwrap();
602
603        assert_eq!(storage, U256::ZERO);
604    }
605
606    #[rstest]
607    fn test_update_state() {
608        let mut db = SimulationDB::new(
609            get_client(None).expect("Failed to create test client"),
610            get_runtime().expect("Failed to create test runtime"),
611            None,
612        );
613        let address = Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc").unwrap();
614        db.init_account(address, AccountInfo::default(), None, false)
615            .expect("Failed to init account");
616
617        let mut new_storage = HashMap::default();
618        let new_storage_value_index = U256::from_limbs_slice(&[123]);
619        new_storage.insert(new_storage_value_index, new_storage_value_index);
620        let new_balance = U256::from_limbs_slice(&[500]);
621        let update = StateUpdate { storage: Some(new_storage), balance: Some(new_balance) };
622        let mut updates = HashMap::default();
623        updates.insert(address, update);
624        let new_block = BlockHeader { number: 1, timestamp: 234, ..Default::default() };
625
626        let reverse_update = db
627            .update_state(&updates, new_block)
628            .expect("State update should succeed");
629
630        assert_eq!(
631            db.account_storage
632                .read()
633                .expect("Storage entry should exist")
634                .get_storage(&address, &new_storage_value_index)
635                .unwrap(),
636            new_storage_value_index
637        );
638        assert_eq!(
639            db.account_storage
640                .read()
641                .unwrap()
642                .get_account_info(&address)
643                .unwrap()
644                .balance,
645            new_balance
646        );
647        assert_eq!(db.block.unwrap().number, 1);
648
649        assert_eq!(
650            reverse_update
651                .get(&address)
652                .unwrap()
653                .balance
654                .unwrap(),
655            AccountInfo::default().balance
656        );
657        assert_eq!(
658            reverse_update
659                .get(&address)
660                .unwrap()
661                .storage,
662            Some(HashMap::default())
663        );
664    }
665
666    #[rstest]
667    fn test_overridden_db() {
668        let db = SimulationDB::new(
669            get_client(None).expect("Failed to create test client"),
670            get_runtime().expect("Failed to create test runtime"),
671            None,
672        );
673        let slot1 = U256::from_limbs_slice(&[1]);
674        let slot2 = U256::from_limbs_slice(&[2]);
675        let orig_value1 = U256::from_limbs_slice(&[100]);
676        let orig_value2 = U256::from_limbs_slice(&[200]);
677        let original_storage: HashMap<U256, U256> = [(slot1, orig_value1), (slot2, orig_value2)]
678            .iter()
679            .cloned()
680            .collect();
681
682        let address1 = Address::from(U160::from(1));
683        let address2 = Address::from(U160::from(2));
684        let address3 = Address::from(U160::from(3));
685
686        // override slot 1 of address 2
687        // and slot 1 of address 3 which doesn't exist in the original DB
688        db.init_account(address1, AccountInfo::default(), Some(original_storage.clone()), false)
689            .expect("Failed to init account");
690        db.init_account(address2, AccountInfo::default(), Some(original_storage), false)
691            .expect("Failed to init account");
692
693        let overridden_value1 = U256::from_limbs_slice(&[101]);
694        let mut overrides: HashMap<Address, HashMap<U256, U256>> = HashMap::new();
695        overrides.insert(
696            address2,
697            [(slot1, overridden_value1)]
698                .iter()
699                .cloned()
700                .collect(),
701        );
702        overrides.insert(
703            address3,
704            [(slot1, overridden_value1)]
705                .iter()
706                .cloned()
707                .collect(),
708        );
709
710        let native_balance_overrides = HashMap::new();
711        let overriden_db = OverriddenSimulationDB::new(&db, &overrides, &native_balance_overrides);
712
713        assert_eq!(
714            overriden_db
715                .storage_ref(address1, slot1)
716                .expect("Value should be available"),
717            orig_value1,
718            "Slots of non-overridden account should hold original values."
719        );
720
721        assert_eq!(
722            overriden_db
723                .storage_ref(address1, slot2)
724                .expect("Value should be available"),
725            orig_value2,
726            "Slots of non-overridden account should hold original values."
727        );
728
729        assert_eq!(
730            overriden_db
731                .storage_ref(address2, slot1)
732                .expect("Value should be available"),
733            overridden_value1,
734            "Overridden slot of overridden account should hold an overridden value."
735        );
736
737        assert_eq!(
738            overriden_db
739                .storage_ref(address2, slot2)
740                .expect("Value should be available"),
741            orig_value2,
742            "Non-overridden slot of an account with other slots overridden \
743            should hold an original value."
744        );
745
746        assert_eq!(
747            overriden_db
748                .storage_ref(address3, slot1)
749                .expect("Value should be available"),
750            overridden_value1,
751            "Overridden slot of an overridden non-existent account should hold an overriden value."
752        );
753    }
754
755    #[derive(Debug, Default)]
756    struct StaticDB {
757        accounts: HashMap<Address, AccountInfo>,
758        fail_basic_ref: bool,
759    }
760
761    impl DatabaseRef for StaticDB {
762        type Error = SimulationDBError;
763
764        fn basic_ref(&self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
765            if self.fail_basic_ref {
766                return Err(SimulationDBError::Internal("account unavailable".to_string()));
767            }
768            Ok(self.accounts.get(&address).cloned())
769        }
770
771        fn code_by_hash_ref(&self, _code_hash: B256) -> Result<Bytecode, Self::Error> {
772            Ok(Bytecode::default())
773        }
774
775        fn storage_ref(&self, _address: Address, _index: U256) -> Result<U256, Self::Error> {
776            Ok(U256::ZERO)
777        }
778
779        fn block_hash_ref(&self, _number: u64) -> Result<B256, Self::Error> {
780            Ok(B256::ZERO)
781        }
782    }
783
784    #[rstest]
785    fn test_balance_override_known_account() {
786        let address = Address::from(U160::from(1));
787        let bytecode = Bytecode::new_raw(AlloyBytes::from_static(&[0x00]));
788        let account = AccountInfo::new(U256::from(100), 7, bytecode.hash_slow(), bytecode.clone());
789        let db = StaticDB { accounts: HashMap::from([(address, account)]), ..Default::default() };
790        let overrides = HashMap::new();
791        let native_balance_overrides = HashMap::from([(address, U256::from(999))]);
792        let overridden_db = OverriddenSimulationDB::new(&db, &overrides, &native_balance_overrides);
793
794        let info = overridden_db
795            .basic_ref(address)
796            .expect("basic_ref should succeed")
797            .expect("account should exist");
798
799        assert_eq!(info.balance, U256::from(999), "Overridden balance should be returned.");
800        assert_eq!(info.nonce, 7, "Other account fields should be preserved.");
801        assert_eq!(info.code, Some(bytecode), "Contract code should be preserved.");
802    }
803
804    #[rstest]
805    fn test_balance_override_unknown_account() {
806        let address = Address::from(U160::from(1));
807        let db = StaticDB::default();
808        let overrides = HashMap::new();
809        let native_balance_overrides = HashMap::from([(address, U256::from(999))]);
810        let overridden_db = OverriddenSimulationDB::new(&db, &overrides, &native_balance_overrides);
811
812        let info = overridden_db
813            .basic_ref(address)
814            .expect("basic_ref should succeed")
815            .expect("overridden unknown account should be synthesized");
816
817        assert_eq!(
818            info,
819            AccountInfo { balance: U256::from(999), ..Default::default() },
820            "Unknown account should be synthesized as an EOA with the overridden balance."
821        );
822    }
823
824    #[rstest]
825    fn test_balance_override_materializes_account_when_inner_db_errors() {
826        let address = Address::from(U160::from(1));
827        let db = StaticDB { fail_basic_ref: true, ..Default::default() };
828        let overrides = HashMap::new();
829        let native_balance_overrides = HashMap::from([(address, U256::from(999))]);
830        let overridden_db = OverriddenSimulationDB::new(&db, &overrides, &native_balance_overrides);
831
832        let info = overridden_db
833            .basic_ref(address)
834            .expect("balance override should shield an inner account miss")
835            .expect("overridden account should be synthesized");
836
837        assert_eq!(info, AccountInfo { balance: U256::from(999), ..Default::default() });
838    }
839
840    #[rstest]
841    fn test_missing_balance_override_propagates_inner_error() {
842        let address = Address::from(U160::from(1));
843        let db = StaticDB { fail_basic_ref: true, ..Default::default() };
844        let overrides = HashMap::new();
845        let native_balance_overrides = HashMap::new();
846        let overridden_db = OverriddenSimulationDB::new(&db, &overrides, &native_balance_overrides);
847
848        let result = overridden_db.basic_ref(address);
849
850        match result {
851            Err(SimulationDBError::Internal(message)) => {
852                assert_eq!(message, "account unavailable")
853            }
854            other => panic!("expected inner database error, got {other:?}"),
855        }
856    }
857
858    #[rstest]
859    fn test_balance_override_absent_passthrough() {
860        let address = Address::from(U160::from(1));
861        let unknown_address = Address::from(U160::from(2));
862        let account = AccountInfo { balance: U256::from(100), nonce: 7, ..Default::default() };
863        let db = StaticDB {
864            accounts: HashMap::from([(address, account.clone())]),
865            ..Default::default()
866        };
867        let overrides = HashMap::new();
868        let native_balance_overrides = HashMap::new();
869        let overridden_db = OverriddenSimulationDB::new(&db, &overrides, &native_balance_overrides);
870
871        assert_eq!(
872            overridden_db
873                .basic_ref(address)
874                .expect("basic_ref should succeed"),
875            Some(account),
876            "Known account without override should pass through unchanged."
877        );
878        assert_eq!(
879            overridden_db
880                .basic_ref(unknown_address)
881                .expect("basic_ref should succeed"),
882            None,
883            "Unknown account without override should stay absent."
884        );
885    }
886}