revm_database/states/
cache.rs

1use super::{
2    plain_account::PlainStorage, transition_account::TransitionAccount, CacheAccount, PlainAccount,
3};
4use bytecode::Bytecode;
5use primitives::{Address, HashMap, B256};
6use state::{Account, AccountInfo, EvmState};
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: HashMap<Address, CacheAccount>,
21    /// Created contracts
22    pub contracts: HashMap<B256, 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    /// Helper function that returns all accounts.
49    ///
50    /// Used inside tests to generate merkle tree.
51    pub fn trie_account(&self) -> impl IntoIterator<Item = (Address, &PlainAccount)> {
52        self.accounts.iter().filter_map(|(address, account)| {
53            account
54                .account
55                .as_ref()
56                .map(|plain_acc| (*address, plain_acc))
57        })
58    }
59
60    /// Inserts not existing account.
61    pub fn insert_not_existing(&mut self, address: Address) {
62        self.accounts
63            .insert(address, CacheAccount::new_loaded_not_existing());
64    }
65
66    /// Inserts Loaded (Or LoadedEmptyEip161 if account is empty) account.
67    pub fn insert_account(&mut self, address: Address, info: AccountInfo) {
68        let account = if !info.is_empty() {
69            CacheAccount::new_loaded(info, HashMap::default())
70        } else {
71            CacheAccount::new_loaded_empty_eip161(HashMap::default())
72        };
73        self.accounts.insert(address, account);
74    }
75
76    /// Similar to `insert_account` but with storage.
77    pub fn insert_account_with_storage(
78        &mut self,
79        address: Address,
80        info: AccountInfo,
81        storage: PlainStorage,
82    ) {
83        let account = if !info.is_empty() {
84            CacheAccount::new_loaded(info, storage)
85        } else {
86            CacheAccount::new_loaded_empty_eip161(storage)
87        };
88        self.accounts.insert(address, account);
89    }
90
91    /// Applies output of revm execution and create account transitions that are used to build BundleState.
92    pub fn apply_evm_state(&mut self, evm_state: EvmState) -> Vec<(Address, TransitionAccount)> {
93        let mut transitions = Vec::with_capacity(evm_state.len());
94        for (address, account) in evm_state {
95            if let Some(transition) = self.apply_account_state(address, account) {
96                transitions.push((address, transition));
97            }
98        }
99        transitions
100    }
101
102    /// Pretty print the cache state for debugging purposes.
103    #[cfg(feature = "std")]
104    pub fn pretty_print(&self) -> String {
105        let mut output = String::new();
106        output.push_str("CacheState:\n");
107        output.push_str(&format!(
108            "  (state_clear_enabled: {}, ",
109            self.has_state_clear
110        ));
111        output.push_str(&format!("accounts: {} total)\n", self.accounts.len()));
112
113        // Sort accounts by address for consistent output
114        let mut accounts: Vec<_> = self.accounts.iter().collect();
115        accounts.sort_by_key(|(addr, _)| *addr);
116
117        let mut contracts = self.contracts.clone();
118
119        for (address, account) in accounts {
120            output.push_str(&format!("  [{address}]:\n"));
121            output.push_str(&format!("    status: {:?}\n", account.status));
122
123            if let Some(plain_account) = &account.account {
124                let code_hash = plain_account.info.code_hash;
125                output.push_str(&format!("    balance: {}\n", plain_account.info.balance));
126                output.push_str(&format!("    nonce: {}\n", plain_account.info.nonce));
127                output.push_str(&format!("    code_hash: {code_hash}\n"));
128
129                if let Some(code) = &plain_account.info.code {
130                    if !code.is_empty() {
131                        contracts.insert(code_hash, code.clone());
132                    }
133                }
134
135                if !plain_account.storage.is_empty() {
136                    output.push_str(&format!(
137                        "    storage: {} slots\n",
138                        plain_account.storage.len()
139                    ));
140                    // Sort storage by key for consistent output
141                    let mut storage: Vec<_> = plain_account.storage.iter().collect();
142                    storage.sort_by_key(|(key, _)| *key);
143
144                    for (key, value) in storage.iter() {
145                        output.push_str(&format!("      [{key:#x}]: {value:#x}\n"));
146                    }
147                }
148            } else {
149                output.push_str("    account: None (destroyed or non-existent)\n");
150            }
151        }
152
153        if !contracts.is_empty() {
154            output.push_str(&format!("  contracts: {} total\n", contracts.len()));
155            for (hash, bytecode) in contracts.iter() {
156                let len = bytecode.len();
157                output.push_str(&format!("    [{hash}]: {len} bytes\n"));
158            }
159        }
160
161        output.push_str("}\n");
162        output
163    }
164
165    /// Applies updated account state to the cached account.
166    ///
167    /// Returns account transition if applicable.
168    fn apply_account_state(
169        &mut self,
170        address: Address,
171        account: Account,
172    ) -> Option<TransitionAccount> {
173        // Not touched account are never changed.
174        if !account.is_touched() {
175            return None;
176        }
177
178        let this_account = self
179            .accounts
180            .get_mut(&address)
181            .expect("All accounts should be present inside cache");
182
183        // If it is marked as selfdestructed inside revm
184        // we need to changed state to destroyed.
185        if account.is_selfdestructed() {
186            return this_account.selfdestruct();
187        }
188
189        let is_created = account.is_created();
190        let is_empty = account.is_empty();
191
192        // Transform evm storage to storage with previous value.
193        let changed_storage = account
194            .storage
195            .into_iter()
196            .filter(|(_, slot)| slot.is_changed())
197            .map(|(key, slot)| (key, slot.into()))
198            .collect();
199
200        // Note: It can happen that created contract get selfdestructed in same block
201        // that is why is_created is checked after selfdestructed
202        //
203        // Note: Create2 opcode (Petersburg) was after state clear EIP (Spurious Dragon)
204        //
205        // Note: It is possibility to create KECCAK_EMPTY contract with some storage
206        // by just setting storage inside CRATE constructor. Overlap of those contracts
207        // is not possible because CREATE2 is introduced later.
208        if is_created {
209            return Some(this_account.newly_created(account.info, changed_storage));
210        }
211
212        // Account is touched, but not selfdestructed or newly created.
213        // Account can be touched and not changed.
214        // And when empty account is touched it needs to be removed from database.
215        // EIP-161 state clear
216        if is_empty {
217            if self.has_state_clear {
218                // Touch empty account.
219                this_account.touch_empty_eip161()
220            } else {
221                // If account is empty and state clear is not enabled we should save
222                // empty account.
223                this_account.touch_create_pre_eip161(changed_storage)
224            }
225        } else {
226            Some(this_account.change(account.info, changed_storage))
227        }
228    }
229}