pub mod account;
pub mod alloy;
pub mod writes;
pub use account::{AccountBal, AccountInfoBal, StorageBal};
pub use writes::BalWrites;
use crate::{Account, AccountInfo};
use alloy_eip7928::BlockAccessList as AlloyBal;
use primitives::{Address, AddressIndexMap, StorageKey, StorageValue};
pub type BalIndex = u64;
#[derive(Debug, Default, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Bal {
pub accounts: AddressIndexMap<AccountBal>,
}
impl FromIterator<(Address, AccountBal)> for Bal {
fn from_iter<I: IntoIterator<Item = (Address, AccountBal)>>(iter: I) -> Self {
Self {
accounts: iter.into_iter().collect(),
}
}
}
impl Bal {
pub fn new() -> Self {
Self {
accounts: AddressIndexMap::default(),
}
}
#[cfg(feature = "std")]
pub fn pretty_print(&self) {
println!("=== Block Access List (BAL) ===");
println!("Total accounts: {}", self.accounts.len());
println!();
if self.accounts.is_empty() {
println!("(empty)");
return;
}
let mut sorted_accounts: Vec<_> = self.accounts.iter().collect();
sorted_accounts.sort_by_key(|(address, _)| *address);
for (idx, (address, account)) in sorted_accounts.into_iter().enumerate() {
println!("Account #{idx} - Address: {address:?}");
println!(" Account Info:");
if account.account_info.nonce.is_empty() {
println!(" Nonce: (read-only, no writes)");
} else {
println!(" Nonce writes:");
for (bal_index, nonce) in &account.account_info.nonce.writes {
println!(" [{bal_index}] -> {nonce}");
}
}
if account.account_info.balance.is_empty() {
println!(" Balance: (read-only, no writes)");
} else {
println!(" Balance writes:");
for (bal_index, balance) in &account.account_info.balance.writes {
println!(" [{bal_index}] -> {balance}");
}
}
if account.account_info.code.is_empty() {
println!(" Code: (read-only, no writes)");
} else {
println!(" Code writes:");
for (bal_index, (code_hash, bytecode)) in &account.account_info.code.writes {
println!(
" [{}] -> hash: {:?}, size: {} bytes",
bal_index,
code_hash,
bytecode.len()
);
}
}
println!(" Storage:");
if account.storage.storage.is_empty() {
println!(" (no storage slots)");
} else {
println!(" Total slots: {}", account.storage.storage.len());
for (storage_key, storage_writes) in &account.storage.storage {
println!(" Slot: {storage_key:#x}");
if storage_writes.is_empty() {
println!(" (read-only, no writes)");
} else {
println!(" Writes:");
for (bal_index, value) in &storage_writes.writes {
println!(" [{bal_index}] -> {value:?}");
}
}
}
}
println!();
}
println!("=== End of BAL ===");
}
#[inline]
pub fn update_account(&mut self, bal_index: BalIndex, address: Address, account: &Account) {
let bal_account = self.accounts.entry(address).or_default();
bal_account.update(bal_index, account);
}
pub fn populate_account_info(
&self,
account_id: usize,
bal_index: BalIndex,
account: &mut AccountInfo,
) -> Result<bool, BalError> {
let Some((_, bal_account)) = self.accounts.get_index(account_id) else {
return Err(BalError::AccountNotFound);
};
account.account_id = Some(account_id);
Ok(bal_account.populate_account_info(bal_index, account))
}
#[inline]
pub fn populate_storage_slot_by_account_id(
&self,
account_index: usize,
bal_index: BalIndex,
key: StorageKey,
value: &mut StorageValue,
) -> Result<(), BalError> {
let Some((_, bal_account)) = self.accounts.get_index(account_index) else {
return Err(BalError::AccountNotFound);
};
if let Some(bal_value) = bal_account.storage.get(key, bal_index)? {
*value = bal_value;
};
Ok(())
}
#[inline]
pub fn populate_storage_slot(
&self,
account_address: Address,
bal_index: BalIndex,
key: StorageKey,
value: &mut StorageValue,
) -> Result<(), BalError> {
let Some(bal_account) = self.accounts.get(&account_address) else {
return Err(BalError::AccountNotFound);
};
if let Some(bal_value) = bal_account.storage.get(key, bal_index)? {
*value = bal_value;
};
Ok(())
}
pub fn account_storage(
&self,
account_index: usize,
key: StorageKey,
bal_index: BalIndex,
) -> Result<StorageValue, BalError> {
let Some((_, bal_account)) = self.accounts.get_index(account_index) else {
return Err(BalError::AccountNotFound);
};
let Some(storage_value) = bal_account.storage.get(key, bal_index)? else {
return Err(BalError::SlotNotFound);
};
Ok(storage_value)
}
pub fn into_alloy_bal(self) -> AlloyBal {
let mut alloy_bal = AlloyBal::from_iter(
self.accounts
.into_iter()
.map(|(address, account)| account.into_alloy_account(address)),
);
alloy_bal.sort_by_key(|a| a.address);
alloy_bal
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum BalError {
AccountNotFound,
SlotNotFound,
}
impl core::fmt::Display for BalError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::AccountNotFound => write!(f, "Account not found in BAL"),
Self::SlotNotFound => write!(f, "Slot not found in BAL"),
}
}
}
impl core::error::Error for BalError {}