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