#[cfg(feature = "json")]
mod json;
mod schema;
mod validate;
pub use schema::{
AccountChanges, BalanceChange, BlockAccessIndex, BlockAccessList, CodeChange, NonceChange,
SlotChanges, StorageChange,
};
use alloy_primitives::{keccak256, Address, B256, U256};
use alloy_rlp::{Decodable, Encodable};
pub const EMPTY_BAL_HASH: B256 = B256::new([
0x1d, 0xcc, 0x4d, 0xe8, 0xde, 0xc7, 0x5d, 0x7a, 0xab, 0x85, 0xb5, 0x67, 0xb6, 0xcc, 0xd4, 0x1a,
0xd3, 0x12, 0x45, 0x1b, 0x94, 0x8a, 0x74, 0x13, 0xf0, 0xa1, 0x42, 0xfd, 0x40, 0xd4, 0x93, 0x47,
]);
#[derive(Debug, thiserror::Error)]
pub enum CodecError {
#[error("rlp: {0}")]
Rlp(#[from] alloy_rlp::Error),
#[error("trailing bytes after BAL: {0} bytes")]
Trailing(usize),
#[error("ordering violated: {0}")]
Ordering(&'static str),
#[error("duplicate {what} at position {pos}")]
Duplicate {
what: &'static str,
pos: usize,
},
#[error("storage key appears in both storage_changes and storage_reads for {0}")]
KeyInChangesAndReads(Address),
#[error("empty change list for slot {slot} of {address}")]
EmptySlotChanges {
address: Address,
slot: B256,
},
#[error("BAL hash mismatch: computed {computed}, expected {expected}")]
HashMismatch {
computed: B256,
expected: B256,
},
#[error("json: {0}")]
Json(String),
}
impl BlockAccessList {
pub fn decode(mut bytes: &[u8]) -> Result<Self, CodecError> {
let bal = <Self as Decodable>::decode(&mut bytes)?;
if !bytes.is_empty() {
return Err(CodecError::Trailing(bytes.len()));
}
validate::validate(&bal)?;
Ok(bal)
}
pub fn encode_rlp(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(self.length());
Encodable::encode(self, &mut out);
out
}
pub fn hash(&self) -> B256 {
keccak256(self.encode_rlp())
}
pub fn verify(&self, expected_bal_hash: B256) -> Result<(), CodecError> {
let computed = self.hash();
if computed == expected_bal_hash {
Ok(())
} else {
Err(CodecError::HashMismatch {
computed,
expected: expected_bal_hash,
})
}
}
pub fn account(&self, addr: &Address) -> Option<&AccountChanges> {
self.accounts
.binary_search_by(|a| a.address.cmp(addr))
.ok()
.map(|i| &self.accounts[i])
}
pub fn is_empty(&self) -> bool {
self.accounts.is_empty()
}
}
impl AccountChanges {
pub fn slot(&self, key: &B256) -> Option<&SlotChanges> {
let k = U256::from_be_bytes(key.0);
self.storage_changes
.binary_search_by(|s| s.slot.cmp(&k))
.ok()
.map(|i| &self.storage_changes[i])
}
pub fn has_storage_changes(&self) -> bool {
!self.storage_changes.is_empty()
}
}
impl SlotChanges {
pub fn slot_b256(&self) -> B256 {
B256::from(self.slot.to_be_bytes::<32>())
}
pub fn final_change(&self) -> &StorageChange {
static EMPTY: StorageChange = StorageChange {
block_access_index: 0,
value: U256::ZERO,
};
self.changes.last().unwrap_or(&EMPTY)
}
}
impl StorageChange {
pub fn value_b256(&self) -> B256 {
B256::from(self.value.to_be_bytes::<32>())
}
}