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