use crate::AccountChanges;
use alloy_primitives::{B256, U256};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct BalAccountInfo {
pub balance: Option<U256>,
pub nonce: Option<u64>,
pub code_hash: Option<B256>,
}
impl BalAccountInfo {
pub fn from_changes(changes: &AccountChanges) -> Self {
Self {
balance: changes.balance_post_state(),
nonce: changes.nonce_post_state(),
code_hash: changes.code_hash_post_state(),
}
}
#[inline]
pub const fn is_empty(&self) -> bool {
self.balance.is_none() && self.nonce.is_none() && self.code_hash.is_none()
}
#[inline]
pub fn changes_state_root(&self, changes: &AccountChanges) -> bool {
!self.is_empty() || changes.has_storage_changes()
}
#[inline]
pub const fn is_complete(&self) -> bool {
self.balance.is_some() && self.nonce.is_some() && self.code_hash.is_some()
}
}
impl From<&AccountChanges> for BalAccountInfo {
fn from(changes: &AccountChanges) -> Self {
Self::from_changes(changes)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
BalanceChange, BlockAccessIndex, CodeChange, NonceChange, SlotChanges, StorageChange,
};
use alloy_primitives::{Address, Bytes, KECCAK256_EMPTY, bytes, keccak256};
const fn index(value: u64) -> BlockAccessIndex {
BlockAccessIndex::new(value)
}
#[test]
fn changed_fields_take_the_last_recorded_value() {
let code = bytes!("6002");
let changes = AccountChanges::new(Address::repeat_byte(0xaa))
.with_balance_change(BalanceChange::new(index(1), U256::from(10)))
.with_balance_change(BalanceChange::new(index(3), U256::from(30)))
.with_nonce_change(NonceChange::new(index(1), 5))
.with_nonce_change(NonceChange::new(index(2), 7))
.with_code_change(CodeChange::new(index(1), bytes!("6001")))
.with_code_change(CodeChange::new(index(2), code.clone()));
let info = BalAccountInfo::from_changes(&changes);
assert!(info.is_complete());
assert_eq!(info.balance, Some(U256::from(30)));
assert_eq!(info.nonce, Some(7));
assert_eq!(info.code_hash, Some(keccak256(&code)));
assert_eq!(info, BalAccountInfo::from(&changes));
}
#[test]
fn unchanged_fields_stay_absent() {
let changes = AccountChanges::new(Address::repeat_byte(0xaa))
.with_balance_change(BalanceChange::new(index(1), U256::from(10)));
let info = BalAccountInfo::from_changes(&changes);
assert!(!info.is_empty());
assert!(!info.is_complete());
assert_eq!(info, BalAccountInfo { balance: Some(U256::from(10)), ..Default::default() });
}
#[test]
fn read_only_entries_are_empty() {
let changes =
AccountChanges::new(Address::repeat_byte(0xdd)).with_storage_read(U256::from(1));
let info = BalAccountInfo::from_changes(&changes);
assert!(info.is_empty());
assert!(!info.changes_state_root(&changes));
assert_eq!(info, BalAccountInfo::default());
}
#[test]
fn storage_only_entries_change_the_state_root_while_empty() {
let changes = AccountChanges::new(Address::repeat_byte(0xbb)).with_storage_change(
SlotChanges::new(U256::from(1), vec![StorageChange::new(index(0), U256::from(2))]),
);
let info = BalAccountInfo::from_changes(&changes);
assert!(info.is_empty());
assert!(info.changes_state_root(&changes));
}
#[test]
fn changing_the_state_root_agrees_with_the_entry() {
let entries = [
AccountChanges::new(Address::ZERO).with_storage_read(U256::from(1)),
AccountChanges::new(Address::ZERO)
.with_storage_change(SlotChanges::new(U256::from(1), vec![])),
AccountChanges::new(Address::ZERO).with_storage_change(SlotChanges::new(
U256::from(1),
vec![StorageChange::new(index(0), U256::from(2))],
)),
AccountChanges::new(Address::ZERO)
.with_balance_change(BalanceChange::new(index(0), U256::from(1))),
AccountChanges::new(Address::ZERO).with_nonce_change(NonceChange::new(index(0), 1)),
AccountChanges::new(Address::ZERO)
.with_code_change(CodeChange::new(index(0), Bytes::new())),
];
for changes in entries {
let info = BalAccountInfo::from_changes(&changes);
assert_eq!(info.changes_state_root(&changes), changes.has_changes());
}
}
#[test]
fn cleared_code_hashes_to_the_empty_code_hash() {
let changes = AccountChanges::new(Address::repeat_byte(0xcc))
.with_code_change(CodeChange::new(index(1), Bytes::new()));
let info = BalAccountInfo::from_changes(&changes);
assert!(!info.is_empty());
assert_eq!(info.code_hash, Some(KECCAK256_EMPTY));
}
}