alloy_eip7928/
account_info.rs1use crate::AccountChanges;
5use alloy_primitives::{B256, U256};
6
7#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
17pub struct BalAccountInfo {
18 pub balance: Option<U256>,
20 pub nonce: Option<u64>,
22 pub code_hash: Option<B256>,
27}
28
29impl BalAccountInfo {
30 pub fn from_changes(changes: &AccountChanges) -> Self {
32 Self {
33 balance: changes.balance_post_state(),
34 nonce: changes.nonce_post_state(),
35 code_hash: changes.code_hash_post_state(),
36 }
37 }
38
39 #[inline]
44 pub const fn is_empty(&self) -> bool {
45 self.balance.is_none() && self.nonce.is_none() && self.code_hash.is_none()
46 }
47
48 #[inline]
55 pub fn changes_state_root(&self, changes: &AccountChanges) -> bool {
56 !self.is_empty() || changes.has_storage_changes()
57 }
58
59 #[inline]
64 pub const fn is_complete(&self) -> bool {
65 self.balance.is_some() && self.nonce.is_some() && self.code_hash.is_some()
66 }
67}
68
69impl From<&AccountChanges> for BalAccountInfo {
70 fn from(changes: &AccountChanges) -> Self {
71 Self::from_changes(changes)
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78 use crate::{
79 BalanceChange, BlockAccessIndex, CodeChange, NonceChange, SlotChanges, StorageChange,
80 };
81 use alloy_primitives::{Address, Bytes, KECCAK256_EMPTY, bytes, keccak256};
82
83 const fn index(value: u64) -> BlockAccessIndex {
84 BlockAccessIndex::new(value)
85 }
86
87 #[test]
88 fn changed_fields_take_the_last_recorded_value() {
89 let code = bytes!("6002");
90 let changes = AccountChanges::new(Address::repeat_byte(0xaa))
91 .with_balance_change(BalanceChange::new(index(1), U256::from(10)))
92 .with_balance_change(BalanceChange::new(index(3), U256::from(30)))
93 .with_nonce_change(NonceChange::new(index(1), 5))
94 .with_nonce_change(NonceChange::new(index(2), 7))
95 .with_code_change(CodeChange::new(index(1), bytes!("6001")))
96 .with_code_change(CodeChange::new(index(2), code.clone()));
97
98 let info = BalAccountInfo::from_changes(&changes);
99
100 assert!(info.is_complete());
101 assert_eq!(info.balance, Some(U256::from(30)));
102 assert_eq!(info.nonce, Some(7));
103 assert_eq!(info.code_hash, Some(keccak256(&code)));
104 assert_eq!(info, BalAccountInfo::from(&changes));
105 }
106
107 #[test]
108 fn unchanged_fields_stay_absent() {
109 let changes = AccountChanges::new(Address::repeat_byte(0xaa))
110 .with_balance_change(BalanceChange::new(index(1), U256::from(10)));
111
112 let info = BalAccountInfo::from_changes(&changes);
113
114 assert!(!info.is_empty());
115 assert!(!info.is_complete());
116 assert_eq!(info, BalAccountInfo { balance: Some(U256::from(10)), ..Default::default() });
117 }
118
119 #[test]
120 fn read_only_entries_are_empty() {
121 let changes =
122 AccountChanges::new(Address::repeat_byte(0xdd)).with_storage_read(U256::from(1));
123
124 let info = BalAccountInfo::from_changes(&changes);
125
126 assert!(info.is_empty());
127 assert!(!info.changes_state_root(&changes));
128 assert_eq!(info, BalAccountInfo::default());
129 }
130
131 #[test]
132 fn storage_only_entries_change_the_state_root_while_empty() {
133 let changes = AccountChanges::new(Address::repeat_byte(0xbb)).with_storage_change(
134 SlotChanges::new(U256::from(1), vec![StorageChange::new(index(0), U256::from(2))]),
135 );
136
137 let info = BalAccountInfo::from_changes(&changes);
138
139 assert!(info.is_empty());
140 assert!(info.changes_state_root(&changes));
141 }
142
143 #[test]
144 fn changing_the_state_root_agrees_with_the_entry() {
145 let entries = [
146 AccountChanges::new(Address::ZERO).with_storage_read(U256::from(1)),
147 AccountChanges::new(Address::ZERO)
148 .with_storage_change(SlotChanges::new(U256::from(1), vec![])),
149 AccountChanges::new(Address::ZERO).with_storage_change(SlotChanges::new(
150 U256::from(1),
151 vec![StorageChange::new(index(0), U256::from(2))],
152 )),
153 AccountChanges::new(Address::ZERO)
154 .with_balance_change(BalanceChange::new(index(0), U256::from(1))),
155 AccountChanges::new(Address::ZERO).with_nonce_change(NonceChange::new(index(0), 1)),
156 AccountChanges::new(Address::ZERO)
157 .with_code_change(CodeChange::new(index(0), Bytes::new())),
158 ];
159
160 for changes in entries {
161 let info = BalAccountInfo::from_changes(&changes);
162 assert_eq!(info.changes_state_root(&changes), changes.has_changes());
163 }
164 }
165
166 #[test]
167 fn cleared_code_hashes_to_the_empty_code_hash() {
168 let changes = AccountChanges::new(Address::repeat_byte(0xcc))
169 .with_code_change(CodeChange::new(index(1), Bytes::new()));
170
171 let info = BalAccountInfo::from_changes(&changes);
172
173 assert!(!info.is_empty());
174 assert_eq!(info.code_hash, Some(KECCAK256_EMPTY));
175 }
176}