revm_state/
bal.rs

1//! Block Access List (BAL) data structures for efficient state access in blockchain execution.
2//!
3//! This module provides types for managing Block Access Lists, which optimize state access
4//! by pre-computing and organizing data that will be accessed during block execution.
5//!
6//! ## Key Types
7//!
8//! - **`BalIndex`**: Block access index (0 for pre-execution, 1..n for transactions, n+1 for post-execution)
9//! - **`Bal`**: Main BAL structure containing a map of accounts
10//! - **`BalWrites<T>`**: Array of (index, value) pairs representing sequential writes to a state item
11//! - **`AccountBal`**: Complete BAL structure for an account (balance, nonce, code, and storage)
12//! - **`AccountInfoBal`**: Account info BAL data (nonce, balance, code)
13//! - **`StorageBal`**: Storage-level BAL data for an account
14
15pub mod account;
16pub mod alloy;
17pub mod writes;
18
19pub use account::{AccountBal, AccountInfoBal, StorageBal};
20pub use writes::BalWrites;
21
22use crate::{Account, AccountInfo};
23use alloy_eip7928::BlockAccessList as AlloyBal;
24use primitives::{Address, IndexMap, StorageKey, StorageValue};
25
26/// Block access index (0 for pre-execution, 1..n for transactions, n+1 for post-execution)
27pub type BalIndex = u64;
28
29/// BAL structure.
30#[derive(Debug, Default, Clone, PartialEq, Eq)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32pub struct Bal {
33    /// Accounts bal.
34    pub accounts: IndexMap<Address, AccountBal>,
35}
36
37impl FromIterator<(Address, AccountBal)> for Bal {
38    fn from_iter<I: IntoIterator<Item = (Address, AccountBal)>>(iter: I) -> Self {
39        Self {
40            accounts: iter.into_iter().collect(),
41        }
42    }
43}
44
45impl Bal {
46    /// Create a new BAL builder.
47    pub fn new() -> Self {
48        Self {
49            accounts: IndexMap::default(),
50        }
51    }
52
53    /// Pretty print the entire BAL structure in a human-readable format.
54    #[cfg(feature = "std")]
55    pub fn pretty_print(&self) {
56        println!("=== Block Access List (BAL) ===");
57        println!("Total accounts: {}", self.accounts.len());
58        println!();
59
60        if self.accounts.is_empty() {
61            println!("(empty)");
62            return;
63        }
64
65        // Sort accounts by address before printing
66        let mut sorted_accounts: Vec<_> = self.accounts.iter().collect();
67        sorted_accounts.sort_by_key(|(address, _)| *address);
68
69        for (idx, (address, account)) in sorted_accounts.into_iter().enumerate() {
70            println!("Account #{idx} - Address: {address:?}");
71            println!("  Account Info:");
72
73            // Print nonce writes
74            if account.account_info.nonce.is_empty() {
75                println!("    Nonce: (read-only, no writes)");
76            } else {
77                println!("    Nonce writes:");
78                for (bal_index, nonce) in &account.account_info.nonce.writes {
79                    println!("      [{bal_index}] -> {nonce}");
80                }
81            }
82
83            // Print balance writes
84            if account.account_info.balance.is_empty() {
85                println!("    Balance: (read-only, no writes)");
86            } else {
87                println!("    Balance writes:");
88                for (bal_index, balance) in &account.account_info.balance.writes {
89                    println!("      [{bal_index}] -> {balance}");
90                }
91            }
92
93            // Print code writes
94            if account.account_info.code.is_empty() {
95                println!("    Code: (read-only, no writes)");
96            } else {
97                println!("    Code writes:");
98                for (bal_index, (code_hash, bytecode)) in &account.account_info.code.writes {
99                    println!(
100                        "      [{}] -> hash: {:?}, size: {} bytes",
101                        bal_index,
102                        code_hash,
103                        bytecode.len()
104                    );
105                }
106            }
107
108            // Print storage writes
109            println!("  Storage:");
110            if account.storage.storage.is_empty() {
111                println!("    (no storage slots)");
112            } else {
113                println!("    Total slots: {}", account.storage.storage.len());
114                for (storage_key, storage_writes) in &account.storage.storage {
115                    println!("    Slot: {storage_key:#x}");
116                    if storage_writes.is_empty() {
117                        println!("      (read-only, no writes)");
118                    } else {
119                        println!("      Writes:");
120                        for (bal_index, value) in &storage_writes.writes {
121                            println!("        [{bal_index}] -> {value:?}");
122                        }
123                    }
124                }
125            }
126
127            println!();
128        }
129        println!("=== End of BAL ===");
130    }
131
132    #[inline]
133    /// Extend BAL with account.
134    pub fn update_account(&mut self, bal_index: BalIndex, address: Address, account: &Account) {
135        let bal_account = self.accounts.entry(address).or_default();
136        bal_account.update(bal_index, account);
137    }
138
139    /// Populate account from BAL. Return true if account info got changed
140    pub fn populate_account_info(
141        &self,
142        account_id: usize,
143        bal_index: BalIndex,
144        account: &mut AccountInfo,
145    ) -> Result<bool, BalError> {
146        let Some((_, bal_account)) = self.accounts.get_index(account_id) else {
147            return Err(BalError::AccountNotFound);
148        };
149        account.account_id = Some(account_id);
150
151        Ok(bal_account.populate_account_info(bal_index, account))
152    }
153
154    /// Populate storage slot from BAL.
155    ///
156    /// If slot is not found in BAL, it will return an error.
157    #[inline]
158    pub fn populate_storage_slot_by_account_id(
159        &self,
160        account_index: usize,
161        bal_index: BalIndex,
162        key: StorageKey,
163        value: &mut StorageValue,
164    ) -> Result<(), BalError> {
165        let Some((_, bal_account)) = self.accounts.get_index(account_index) else {
166            return Err(BalError::AccountNotFound);
167        };
168
169        if let Some(bal_value) = bal_account.storage.get(key, bal_index)? {
170            *value = bal_value;
171        };
172
173        Ok(())
174    }
175
176    /// Populate storage slot from BAL by account address.
177    #[inline]
178    pub fn populate_storage_slot(
179        &self,
180        account_address: Address,
181        bal_index: BalIndex,
182        key: StorageKey,
183        value: &mut StorageValue,
184    ) -> Result<(), BalError> {
185        let Some(bal_account) = self.accounts.get(&account_address) else {
186            return Err(BalError::AccountNotFound);
187        };
188
189        if let Some(bal_value) = bal_account.storage.get(key, bal_index)? {
190            *value = bal_value;
191        };
192        Ok(())
193    }
194
195    /// Get storage from BAL.
196    pub fn account_storage(
197        &self,
198        account_index: usize,
199        key: StorageKey,
200        bal_index: BalIndex,
201    ) -> Result<StorageValue, BalError> {
202        let Some((_, bal_account)) = self.accounts.get_index(account_index) else {
203            return Err(BalError::AccountNotFound);
204        };
205
206        let Some(storage_value) = bal_account.storage.get(key, bal_index)? else {
207            return Err(BalError::SlotNotFound);
208        };
209
210        Ok(storage_value)
211    }
212
213    /// Consume Bal and create [`AlloyBal`]
214    pub fn into_alloy_bal(self) -> AlloyBal {
215        let mut alloy_bal = AlloyBal::from_iter(
216            self.accounts
217                .into_iter()
218                .map(|(address, account)| account.into_alloy_account(address)),
219        );
220        alloy_bal.sort_by_key(|a| a.address);
221        alloy_bal
222    }
223}
224
225/// BAL error.
226#[derive(Debug, Clone, PartialEq, Eq)]
227#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
228pub enum BalError {
229    /// Account not found in BAL.
230    AccountNotFound,
231    /// Slot not found in BAL.
232    SlotNotFound,
233}
234
235impl core::fmt::Display for BalError {
236    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
237        match self {
238            Self::AccountNotFound => write!(f, "Account not found in BAL"),
239            Self::SlotNotFound => write!(f, "Slot not found in BAL"),
240        }
241    }
242}