Skip to main content

tycho_simulation/evm/
account_storage.rs

1use std::collections::{hash_map::Entry::Vacant, HashMap};
2
3use alloy::primitives::{Address, U256};
4use revm::state::AccountInfo;
5use tracing::{debug, trace, warn};
6
7/// Represents an account in the account storage.
8///
9/// # Fields
10///
11/// * `info` - The account information of type `AccountInfo`.
12/// * `permanent_storage` - The permanent storage of the account.
13/// * `temp_storage` - The temporary storage of the account.
14/// * `mocked` - A boolean flag indicating whether the account is mocked.
15#[derive(Clone, Default, Debug)]
16pub struct Account {
17    pub info: AccountInfo,
18    pub permanent_storage: HashMap<U256, U256>,
19    pub temp_storage: HashMap<U256, U256>,
20    pub mocked: bool,
21}
22
23#[derive(Default, Clone, PartialEq, Eq, Debug)]
24pub struct StateUpdate {
25    pub storage: Option<HashMap<U256, U256>>,
26    pub balance: Option<U256>,
27}
28#[derive(Clone, Default, Debug)]
29/// A simpler implementation of CacheDB that can't query a node. It just stores data.
30pub struct AccountStorage {
31    accounts: HashMap<Address, Account>,
32}
33
34impl AccountStorage {
35    pub fn new() -> Self {
36        Self::default()
37    }
38
39    /// Clear all accounts from the storage.
40    pub fn clear(&mut self) {
41        self.accounts.clear();
42    }
43
44    /// Inserts account data into the current instance.
45    ///
46    /// # Arguments
47    ///
48    /// * `address` - The address of the account to insert.
49    /// * `info` - The account information to insert.
50    /// * `permanent_storage` - Optional storage information associated with the account.
51    /// * `mocked` - Whether this account should be considered mocked.
52    ///
53    /// # Notes
54    ///
55    /// This function checks if the `address` is already present in the `accounts`
56    /// collection. If so, it logs a warning and returns without modifying the instance.
57    /// Otherwise, it stores a new `Account` instance with the provided data at the given address.
58    pub fn init_account(
59        &mut self,
60        address: Address,
61        info: AccountInfo,
62        permanent_storage: Option<HashMap<U256, U256>>,
63        mocked: bool,
64    ) {
65        if let Vacant(e) = self.accounts.entry(address) {
66            e.insert(Account {
67                info,
68                permanent_storage: permanent_storage.unwrap_or_default(),
69                temp_storage: HashMap::new(),
70                mocked,
71            });
72            debug!(
73                "Inserted a {} account {:x?}",
74                if mocked { "mocked" } else { "non-mocked" },
75                address
76            );
77        } else {
78            trace!("Skipped init for already-existing account {:x?}", address);
79        }
80    }
81
82    /// Inserts account data into the current instance, replacing any existing entry.
83    ///
84    /// # Arguments
85    ///
86    /// * `address` - The address of the account to insert.
87    /// * `info` - The account information to insert.
88    /// * `permanent_storage` - Optional storage information associated with the account.
89    /// * `mocked` - Whether this account should be considered mocked.
90    ///
91    /// # Notes
92    ///
93    /// Unlike `init_account`, this function always replaces the account at the given address,
94    /// even if one already exists. Use this for `ChangeType::Creation` updates where the latest
95    /// snapshot data must win over any previously inserted placeholder.
96    pub fn overwrite_account(
97        &mut self,
98        address: Address,
99        info: AccountInfo,
100        permanent_storage: Option<HashMap<U256, U256>>,
101        mocked: bool,
102    ) {
103        self.accounts.insert(
104            address,
105            Account {
106                info,
107                permanent_storage: permanent_storage.unwrap_or_default(),
108                temp_storage: HashMap::new(),
109                mocked,
110            },
111        );
112        debug!(
113            "Overwrote a {} account {:x?}",
114            if mocked { "mocked" } else { "non-mocked" },
115            address
116        );
117    }
118
119    /// Updates the account information and storage associated with the given address.
120    ///
121    /// # Arguments
122    ///
123    /// * `address` - The address of the account to update.
124    /// * `update` - The state update containing the new information to apply.
125    ///
126    /// # Notes
127    ///
128    /// This function looks for the account information and storage associated with the provided
129    /// `address`. If the `address` exists in the `accounts` collection, it updates the account
130    /// information based on the `balance` field in the `update` parameter. If the `address` exists
131    /// in the `storage` collection, it updates the storage information based on the `storage` field
132    /// in the `update` parameter.
133    ///
134    /// If the `address` is not found in either collection, a warning is logged and no changes are
135    /// made.
136    pub fn update_account(&mut self, address: &Address, update: &StateUpdate) {
137        if let Some(account) = self.accounts.get_mut(address) {
138            if let Some(new_balance) = update.balance {
139                account.info.balance = new_balance;
140            }
141            if let Some(new_storage) = &update.storage {
142                for (index, value) in new_storage {
143                    account
144                        .permanent_storage
145                        .insert(*index, *value);
146                }
147            }
148        } else {
149            warn!(?address, "Tried to update account {:x?} that was not initialized", address);
150        }
151    }
152
153    /// Retrieves the account information for a given address.
154    ///
155    /// This function retrieves the account information associated with the specified address from
156    /// the storage.
157    ///
158    /// # Arguments
159    ///
160    /// * `address`: The address of the account to retrieve the information for.
161    ///
162    /// # Returns
163    ///
164    /// Returns an `Option` that holds a reference to the `AccountInfo`. If the account is not
165    /// found, `None` is returned.
166    pub fn get_account_info(&self, address: &Address) -> Option<&AccountInfo> {
167        self.accounts
168            .get(address)
169            .map(|acc| &acc.info)
170    }
171
172    /// Checks if an account with the given address is present in the storage.
173    ///
174    /// # Arguments
175    ///
176    /// * `address`: A reference to the address of the account to check.
177    ///
178    /// # Returns
179    ///
180    /// Returns `true` if an account with the specified address is present in the storage,
181    /// otherwise returns `false`.
182    pub fn account_present(&self, address: &Address) -> bool {
183        self.accounts.contains_key(address)
184    }
185
186    /// Sets the storage value at the specified index for the given account.
187    ///
188    /// If the account exists in the storage, the storage value at the specified `index` is updated.
189    /// If the account does not exist, a warning message is logged indicating an attempt to set
190    /// storage on an uninitialized account.
191    ///
192    /// # Arguments
193    ///
194    /// * `address`: The address of the account to set the storage value for.
195    /// * `index`: The index of the storage value to set.
196    /// * `value`: The new value to set for the storage.
197    pub fn set_temp_storage(&mut self, address: Address, index: U256, value: U256) {
198        if let Some(acc) = self.accounts.get_mut(&address) {
199            acc.temp_storage.insert(index, value);
200        } else {
201            warn!("Trying to set storage on unitialized account {:x?}.", address);
202        }
203    }
204
205    /// Retrieves the storage value at the specified index for the given account, if it exists.
206    ///
207    /// If the account exists in the storage, the storage value at the specified `index` is returned
208    /// as a reference. Temp storage takes priority over permanent storage.
209    /// If the account does not exist, `None` is returned.
210    ///
211    /// # Arguments
212    ///
213    /// * `address`: A reference to the address of the account to retrieve the storage value from.
214    /// * `index`: A reference to the index of the storage value to retrieve.
215    ///
216    /// # Returns
217    ///
218    /// Returns an `Option` containing a reference to the storage value if it exists, otherwise
219    /// returns `None`.
220    pub fn get_storage(&self, address: &Address, index: &U256) -> Option<U256> {
221        if let Some(acc) = self.accounts.get(address) {
222            if let Some(s) = acc.temp_storage.get(index) {
223                Some(*s)
224            } else {
225                acc.permanent_storage
226                    .get(index)
227                    .copied()
228            }
229        } else {
230            None
231        }
232    }
233
234    /// Retrieves the permanent storage value for the given address and index.
235    ///
236    /// If an account with the specified address exists in the account storage, this function
237    /// retrieves the corresponding permanent storage value associated with the given index.
238    ///
239    /// # Arguments
240    ///
241    /// * `address` - The address of the account.
242    /// * `index` - The index of the desired storage value.
243    pub fn get_permanent_storage(&self, address: &Address, index: &U256) -> Option<U256> {
244        if let Some(acc) = self.accounts.get(address) {
245            acc.permanent_storage
246                .get(index)
247                .copied()
248        } else {
249            None
250        }
251    }
252
253    /// Removes all temp storage values.
254    ///
255    /// Iterates over the accounts in the storage and removes all temp storage values
256    pub fn clear_temp_storage(&mut self) {
257        self.accounts
258            .values_mut()
259            .for_each(|acc| acc.temp_storage.clear());
260    }
261
262    /// Checks if an account is mocked based on its address.
263    ///
264    /// # Arguments
265    ///
266    /// * `address` - A reference to the account address.
267    pub fn is_mocked_account(&self, address: &Address) -> Option<bool> {
268        self.accounts
269            .get(address)
270            .map(|acc| acc.mocked)
271    }
272}
273
274#[cfg(test)]
275mod tests {
276    use std::{error::Error, str::FromStr};
277
278    use revm::primitives::KECCAK_EMPTY;
279
280    use super::*;
281    use crate::evm::account_storage::{Account, AccountStorage};
282
283    #[test]
284    fn test_insert_account() -> Result<(), Box<dyn Error>> {
285        let mut account_storage = AccountStorage::default();
286        let expected_nonce = 100;
287        let expected_balance = U256::from(500);
288        let acc_address = Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc")?;
289        let info: AccountInfo = AccountInfo {
290            nonce: expected_nonce,
291            balance: expected_balance,
292            code: None,
293            code_hash: KECCAK_EMPTY,
294        };
295        let mut storage_new = HashMap::new();
296        let expected_storage_value = U256::from_str("5").unwrap();
297        storage_new.insert(U256::from_str("1").unwrap(), expected_storage_value);
298
299        account_storage.init_account(acc_address, info, Some(storage_new), false);
300
301        let acc = account_storage
302            .get_account_info(&acc_address)
303            .unwrap();
304        let storage_value = account_storage
305            .get_storage(&acc_address, &U256::from_str("1").unwrap())
306            .unwrap();
307        assert_eq!(acc.nonce, expected_nonce, "Nonce should match expected value");
308        assert_eq!(acc.balance, expected_balance, "Balance should match expected value");
309        assert_eq!(acc.code_hash, KECCAK_EMPTY, "Code hash should match expected value");
310        assert_eq!(
311            storage_value, expected_storage_value,
312            "Storage value should match expected value"
313        );
314        Ok(())
315    }
316
317    #[test]
318    fn test_update_account_info() -> Result<(), Box<dyn Error>> {
319        let mut account_storage = AccountStorage::default();
320        let acc_address = Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc")?;
321        let info: AccountInfo = AccountInfo {
322            nonce: 100,
323            balance: U256::from(500),
324            code: None,
325            code_hash: KECCAK_EMPTY,
326        };
327        let mut original_storage = HashMap::new();
328        let storage_index = U256::from_str("1").unwrap();
329        original_storage.insert(storage_index, U256::from_str("5").unwrap());
330        account_storage.accounts.insert(
331            acc_address,
332            Account {
333                info,
334                permanent_storage: original_storage,
335                temp_storage: HashMap::new(),
336                mocked: false,
337            },
338        );
339        let updated_balance = U256::from(100);
340        let updated_storage_value = U256::from_str("999").unwrap();
341        let mut updated_storage = HashMap::new();
342        updated_storage.insert(storage_index, updated_storage_value);
343        let state_update =
344            StateUpdate { balance: Some(updated_balance), storage: Some(updated_storage) };
345
346        account_storage.update_account(&acc_address, &state_update);
347
348        assert_eq!(
349            account_storage
350                .get_account_info(&acc_address)
351                .unwrap()
352                .balance,
353            updated_balance,
354            "Account balance should be updated"
355        );
356        assert_eq!(
357            account_storage
358                .get_storage(&acc_address, &storage_index)
359                .unwrap(),
360            updated_storage_value,
361            "Storage value should be updated"
362        );
363        Ok(())
364    }
365
366    #[test]
367    fn test_get_account_info() {
368        let mut account_storage = AccountStorage::default();
369        let address_1 = Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc").unwrap();
370        let address_2 = Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dd").unwrap();
371        let account_info_1 = AccountInfo::default();
372        let account_info_2 = AccountInfo { nonce: 500, ..Default::default() };
373        account_storage.init_account(address_1, account_info_1, None, false);
374        account_storage.init_account(address_2, account_info_2, None, false);
375
376        let existing_account = account_storage.get_account_info(&address_1);
377        let address_3 = Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9de").unwrap();
378        let non_existing_account = account_storage.get_account_info(&address_3);
379
380        assert_eq!(
381            existing_account.unwrap().nonce,
382            AccountInfo::default().nonce,
383            "Existing account's nonce should match the expected value"
384        );
385        assert_eq!(non_existing_account, None, "Non-existing account should return None");
386    }
387
388    #[test]
389    fn test_account_present() {
390        let mut account_storage = AccountStorage::default();
391        let existing_account =
392            Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc").unwrap();
393        let address_2 = Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dd").unwrap();
394        let non_existing_account =
395            Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9de").unwrap();
396        account_storage
397            .accounts
398            .insert(existing_account, Account::default());
399        account_storage
400            .accounts
401            .insert(address_2, Account::default());
402
403        assert!(
404            account_storage.account_present(&existing_account),
405            "Existing account should be present in the AccountStorage"
406        );
407        assert!(
408            !account_storage.account_present(&non_existing_account),
409            "Non-existing account should not be present in the AccountStorage"
410        );
411    }
412
413    #[test]
414    fn test_set_get_storage() {
415        // Create a new instance of the struct for testing
416        let mut account_storage = AccountStorage::default();
417        // Add a test account
418        let address = Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc").unwrap();
419        let non_existing_address =
420            Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dd").unwrap();
421        let account = Account::default();
422        account_storage
423            .accounts
424            .insert(address, account);
425        let index = U256::from_str("1").unwrap();
426        let value = U256::from_str("1").unwrap();
427        let non_existing_index = U256::from_str("2").unwrap();
428        let non_existing_value = U256::from_str("2").unwrap();
429        account_storage.set_temp_storage(
430            non_existing_address,
431            non_existing_index,
432            non_existing_value,
433        );
434        account_storage.set_temp_storage(address, index, value);
435
436        let storage = account_storage.get_storage(&address, &index);
437        let empty_storage = account_storage.get_storage(&non_existing_address, &non_existing_index);
438
439        assert_eq!(storage, Some(value), "Storage value should match the value that was set");
440        assert_eq!(empty_storage, None, "Storage value should be None for a non-existing account");
441    }
442
443    #[test]
444    fn test_get_storage() {
445        let mut account_storage = AccountStorage::default();
446        let existing_address =
447            Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc").unwrap();
448        let non_existent_address =
449            Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dd").unwrap();
450        let index = U256::from(42);
451        let value = U256::from(100);
452        let non_existent_index = U256::from(999);
453        let mut account = Account::default();
454        account
455            .temp_storage
456            .insert(index, value);
457        account_storage
458            .accounts
459            .insert(existing_address, account);
460
461        assert_eq!(
462            account_storage.get_storage(&existing_address, &index),
463            Some(value), "If the storage features the address and index the value at that position should be retunred."
464        );
465
466        // Test with non-existent address
467        assert_eq!(
468            account_storage.get_storage(&non_existent_address, &index),
469            None,
470            "If the storage does not feature the address None should be returned."
471        );
472
473        // Test with non-existent index
474        assert_eq!(
475            account_storage.get_storage(&existing_address, &non_existent_index),
476            None,
477            "If the storage does not feature the index None should be returned."
478        );
479    }
480
481    #[test]
482    fn test_get_storage_priority() {
483        let mut account_storage = AccountStorage::default();
484        let address = Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc").unwrap();
485        let index = U256::from(69);
486        let temp_value = U256::from(100);
487        let permanent_value = U256::from(200);
488        let mut account = Account::default();
489        account
490            .temp_storage
491            .insert(index, temp_value);
492        account
493            .permanent_storage
494            .insert(index, permanent_value);
495        account_storage
496            .accounts
497            .insert(address, account);
498
499        assert_eq!(
500            account_storage.get_storage(&address, &index),
501            Some(temp_value),
502            "Temp storage value should take priority over permanent storage value"
503        );
504    }
505
506    #[test]
507    fn test_is_mocked_account() {
508        let mut account_storage = AccountStorage::default();
509        let mocked_account_address =
510            Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc").unwrap();
511        let not_mocked_account_address =
512            Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dd").unwrap();
513        let unknown_address =
514            Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9de").unwrap();
515        let mocked_account = Account { mocked: true, ..Default::default() };
516        let not_mocked_account = Account { mocked: false, ..Default::default() };
517        account_storage
518            .accounts
519            .insert(mocked_account_address, mocked_account);
520        account_storage
521            .accounts
522            .insert(not_mocked_account_address, not_mocked_account);
523
524        assert_eq!(account_storage.is_mocked_account(&mocked_account_address), Some(true));
525        assert_eq!(account_storage.is_mocked_account(&not_mocked_account_address), Some(false));
526        assert_eq!(account_storage.is_mocked_account(&unknown_address), None);
527    }
528
529    #[test]
530    fn test_clear_temp_storage() {
531        let mut account_storage = AccountStorage::default();
532        let address_1 = Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc").unwrap();
533        let address_2 = Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dd").unwrap();
534        let mut account_1 = Account::default();
535        account_1
536            .temp_storage
537            .insert(U256::from(1), U256::from(10));
538        let mut account_2 = Account::default();
539        account_2
540            .temp_storage
541            .insert(U256::from(2), U256::from(20));
542        account_storage
543            .accounts
544            .insert(address_1, account_1);
545        account_storage
546            .accounts
547            .insert(address_2, account_2);
548
549        account_storage.clear_temp_storage();
550
551        let account_1_temp_storage = account_storage.accounts[&address_1]
552            .temp_storage
553            .len();
554        let account_2_temp_storage = account_storage.accounts[&address_2]
555            .temp_storage
556            .len();
557        assert_eq!(account_1_temp_storage, 0, "Temporary storage of account 1 should be cleared");
558        assert_eq!(account_2_temp_storage, 0, "Temporary storage of account 2 should be cleared");
559    }
560
561    #[test]
562    fn test_get_permanent_storage() {
563        let mut account_storage = AccountStorage::default();
564        let address = Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc").unwrap();
565        let non_existing_address =
566            Address::from_str("0xb4e16d0168e52d35cacd2c6185b44281ec28c9dd").unwrap();
567        let index = U256::from_str("123").unwrap();
568        let value = U256::from_str("456").unwrap();
569        let mut account = Account::default();
570        account
571            .permanent_storage
572            .insert(index, value);
573        account_storage
574            .accounts
575            .insert(address, account);
576
577        let result = account_storage.get_permanent_storage(&address, &index);
578        let not_existing_result =
579            account_storage.get_permanent_storage(&non_existing_address, &index);
580        let empty_index = U256::from_str("789").unwrap();
581        let no_storage = account_storage.get_permanent_storage(&address, &empty_index);
582
583        assert_eq!(
584            result,
585            Some(value),
586            "Expected value for existing account with permanent storage"
587        );
588        assert_eq!(not_existing_result, None, "Expected None for non-existing account");
589        assert_eq!(
590            no_storage, None,
591            "Expected None for existing account without permanent storage"
592        );
593    }
594}