Skip to main content

revm_database/states/
cache.rs

1use super::{
2    plain_account::PlainStorage, transition_account::TransitionAccount, CacheAccount, PlainAccount,
3};
4use bytecode::Bytecode;
5use primitives::{Address, AddressMap, B256Map, HashMap};
6use state::{Account, AccountInfo};
7use std::vec::Vec;
8
9/// Cache state contains both modified and original values
10///
11/// # Note
12/// Cache state is main state that revm uses to access state.
13///
14/// It loads all accounts from database and applies revm output to it.
15///
16/// It generates transitions that is used to build BundleState.
17#[derive(Clone, Debug, PartialEq, Eq)]
18pub struct CacheState {
19    /// Block state account with account state
20    pub accounts: AddressMap<CacheAccount>,
21    /// Created contracts
22    pub contracts: B256Map<Bytecode>,
23    /// Has EIP-161 state clear enabled (Spurious Dragon hardfork)
24    pub has_state_clear: bool,
25}
26
27impl Default for CacheState {
28    fn default() -> Self {
29        Self::new(true)
30    }
31}
32
33impl CacheState {
34    /// Creates a new default state.
35    pub fn new(has_state_clear: bool) -> Self {
36        Self {
37            accounts: HashMap::default(),
38            contracts: HashMap::default(),
39            has_state_clear,
40        }
41    }
42
43    /// Sets state clear flag. EIP-161.
44    pub fn set_state_clear_flag(&mut self, has_state_clear: bool) {
45        self.has_state_clear = has_state_clear;
46    }
47
48    /// Clear the cache state.
49    pub fn clear(&mut self) {
50        self.accounts.clear();
51        self.contracts.clear();
52    }
53
54    /// Helper function that returns all accounts.
55    ///
56    /// Used inside tests to generate merkle tree.
57    pub fn trie_account(&self) -> impl IntoIterator<Item = (Address, &PlainAccount)> {
58        self.accounts.iter().filter_map(|(address, account)| {
59            account
60                .account
61                .as_ref()
62                .map(|plain_acc| (*address, plain_acc))
63        })
64    }
65
66    /// Inserts not existing account.
67    pub fn insert_not_existing(&mut self, address: Address) {
68        self.accounts
69            .insert(address, CacheAccount::new_loaded_not_existing());
70    }
71
72    /// Inserts Loaded (Or LoadedEmptyEip161 if account is empty) account.
73    pub fn insert_account(&mut self, address: Address, info: AccountInfo) {
74        let account = if !info.is_empty() {
75            CacheAccount::new_loaded(info, HashMap::default())
76        } else {
77            CacheAccount::new_loaded_empty_eip161(HashMap::default())
78        };
79        self.accounts.insert(address, account);
80    }
81
82    /// Similar to `insert_account` but with storage.
83    pub fn insert_account_with_storage(
84        &mut self,
85        address: Address,
86        info: AccountInfo,
87        storage: PlainStorage,
88    ) {
89        let account = if !info.is_empty() {
90            CacheAccount::new_loaded(info, storage)
91        } else {
92            CacheAccount::new_loaded_empty_eip161(storage)
93        };
94        self.accounts.insert(address, account);
95    }
96
97    /// Applies output of revm execution and create account transitions that are used to build BundleState.
98    #[inline]
99    pub fn apply_evm_state<F>(
100        &mut self,
101        evm_state: impl IntoIterator<Item = (Address, Account)>,
102        inspect: F,
103    ) -> Vec<(Address, TransitionAccount)>
104    where
105        F: FnMut(&Address, &Account),
106    {
107        self.apply_evm_state_iter(evm_state, inspect).collect()
108    }
109
110    /// Applies output of revm execution and creates an iterator of account transitions.
111    #[inline]
112    pub(crate) fn apply_evm_state_iter<'a, F, T>(
113        &'a mut self,
114        evm_state: T,
115        mut inspect: F,
116    ) -> impl Iterator<Item = (Address, TransitionAccount)> + use<'a, F, T>
117    where
118        F: FnMut(&Address, &Account),
119        T: IntoIterator<Item = (Address, Account)>,
120    {
121        evm_state.into_iter().filter_map(move |(address, account)| {
122            inspect(&address, &account);
123            self.apply_account_state(address, account)
124                .map(|transition| (address, transition))
125        })
126    }
127
128    /// Pretty print the cache state for debugging purposes.
129    #[cfg(feature = "std")]
130    pub fn pretty_print(&self) -> String {
131        let mut output = String::new();
132        output.push_str("CacheState:\n");
133        output.push_str(&format!(
134            "  (state_clear_enabled: {}, ",
135            self.has_state_clear
136        ));
137        output.push_str(&format!("accounts: {} total)\n", self.accounts.len()));
138
139        // Sort accounts by address for consistent output
140        let mut accounts: Vec<_> = self.accounts.iter().collect();
141        accounts.sort_by_key(|(addr, _)| *addr);
142
143        let mut contracts = self.contracts.clone();
144
145        for (address, account) in accounts {
146            output.push_str(&format!("  [{address}]:\n"));
147            output.push_str(&format!("    status: {:?}\n", account.status));
148
149            if let Some(plain_account) = &account.account {
150                let code_hash = plain_account.info.code_hash;
151                output.push_str(&format!("    balance: {}\n", plain_account.info.balance));
152                output.push_str(&format!("    nonce: {}\n", plain_account.info.nonce));
153                output.push_str(&format!("    code_hash: {code_hash}\n"));
154
155                if let Some(code) = &plain_account.info.code {
156                    if !code.is_empty() {
157                        contracts.insert(code_hash, code.clone());
158                    }
159                }
160
161                if !plain_account.storage.is_empty() {
162                    output.push_str(&format!(
163                        "    storage: {} slots\n",
164                        plain_account.storage.len()
165                    ));
166                    // Sort storage by key for consistent output
167                    let mut storage: Vec<_> = plain_account.storage.iter().collect();
168                    storage.sort_by_key(|(key, _)| *key);
169
170                    for (key, value) in storage.iter() {
171                        output.push_str(&format!("      [{key:#x}]: {value:#x}\n"));
172                    }
173                }
174            } else {
175                output.push_str("    account: None (destroyed or non-existent)\n");
176            }
177        }
178
179        if !contracts.is_empty() {
180            output.push_str(&format!("  contracts: {} total\n", contracts.len()));
181            for (hash, bytecode) in contracts.iter() {
182                let len = bytecode.len();
183                output.push_str(&format!("    [{hash}]: {len} bytes\n"));
184            }
185        }
186
187        output.push_str("}\n");
188        output
189    }
190
191    /// Applies updated account state to the cached account.
192    ///
193    /// Returns account transition if applicable.
194    pub(crate) fn apply_account_state(
195        &mut self,
196        address: Address,
197        account: Account,
198    ) -> Option<TransitionAccount> {
199        // Not touched account are never changed.
200        if !account.is_touched() {
201            return None;
202        }
203
204        let this_account = self
205            .accounts
206            .get_mut(&address)
207            .expect("All accounts should be present inside cache");
208
209        // If it is marked as selfdestructed inside revm
210        // we need to changed state to destroyed.
211        if account.is_selfdestructed() {
212            return this_account.selfdestruct();
213        }
214
215        let is_created = account.is_created();
216        let is_empty = account.is_empty();
217
218        // Transform evm storage to storage with previous value.
219        let changed_storage = account
220            .storage
221            .into_iter()
222            .filter(|(_, slot)| slot.is_changed())
223            .map(|(key, slot)| (key, slot.into()))
224            .collect();
225
226        // Note: It can happen that created contract get selfdestructed in same block
227        // that is why is_created is checked after selfdestructed
228        //
229        // Note: Create2 opcode (Petersburg) was after state clear EIP (Spurious Dragon)
230        //
231        // Note: It is possibility to create KECCAK_EMPTY contract with some storage
232        // by just setting storage inside CRATE constructor. Overlap of those contracts
233        // is not possible because CREATE2 is introduced later.
234        if is_created {
235            return Some(this_account.newly_created(account.info, changed_storage));
236        }
237
238        // Account is touched, but not selfdestructed or newly created.
239        // Account can be touched and not changed.
240        // And when empty account is touched it needs to be removed from database.
241        // EIP-161 state clear
242        if is_empty {
243            if self.has_state_clear {
244                // Touch empty account.
245                this_account.touch_empty_eip161()
246            } else {
247                // If account is empty and state clear is not enabled we should save
248                // empty account.
249                this_account.touch_create_pre_eip161(changed_storage)
250            }
251        } else {
252            Some(this_account.change(account.info, changed_storage))
253        }
254    }
255}