1pub 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
26pub type BalIndex = u64;
28
29#[derive(Debug, Default, Clone, PartialEq, Eq)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32pub struct Bal {
33 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 pub fn new() -> Self {
48 Self {
49 accounts: IndexMap::default(),
50 }
51 }
52
53 #[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 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 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 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 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 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 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 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 #[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 #[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 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 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#[derive(Debug, Clone, PartialEq, Eq)]
227#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
228pub enum BalError {
229 AccountNotFound,
231 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}