1use crate::account_changes::AccountChanges;
4use alloc::vec::Vec;
5
6#[cfg(not(feature = "std"))]
7use once_cell::race::OnceBox as OnceLock;
8#[cfg(feature = "std")]
9use std::sync::OnceLock;
10
11pub type BlockAccessList = Vec<AccountChanges>;
13
14#[cfg(feature = "rlp")]
16pub fn compute_block_access_list_hash(bal: &[AccountChanges]) -> alloy_primitives::B256 {
17 let mut buf = Vec::new();
18 alloy_rlp::encode_list(bal, &mut buf);
19 alloy_primitives::keccak256(&buf)
20}
21
22pub fn total_bal_items(bal: &[AccountChanges]) -> u64 {
25 let mut bal_items: u64 = 0;
26
27 for account in bal {
28 bal_items += 1;
30
31 let mut unique_slots = alloy_primitives::map::HashSet::new();
33
34 for change in account.storage_changes() {
35 unique_slots.insert(change.slot);
36 }
37
38 for slot in account.storage_reads() {
39 unique_slots.insert(*slot);
40 }
41
42 bal_items += unique_slots.len() as u64;
44 }
45 bal_items
46}
47
48pub mod bal {
50 use super::OnceLock;
51 use crate::account_changes::AccountChanges;
52 use alloc::vec::{IntoIter, Vec};
53 use alloy_primitives::Bytes;
54 use core::{
55 ops::{Deref, Index},
56 slice::Iter,
57 };
58
59 #[derive(Clone, Debug, Default, PartialEq, Eq)]
65 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
66 #[cfg_attr(
67 feature = "rlp",
68 derive(alloy_rlp::RlpEncodableWrapper, alloy_rlp::RlpDecodableWrapper)
69 )]
70 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
71 #[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
72 pub struct Bal(Vec<AccountChanges>);
73
74 impl From<Bal> for Vec<AccountChanges> {
75 fn from(this: Bal) -> Self {
76 this.0
77 }
78 }
79
80 impl From<Vec<AccountChanges>> for Bal {
81 fn from(list: Vec<AccountChanges>) -> Self {
82 Self(list)
83 }
84 }
85
86 #[cfg(feature = "rlp")]
87 impl alloy_primitives::Sealable for Bal {
88 fn hash_slow(&self) -> alloy_primitives::B256 {
89 self.compute_hash()
90 }
91 }
92
93 impl Deref for Bal {
94 type Target = [AccountChanges];
95
96 fn deref(&self) -> &Self::Target {
97 self.as_slice()
98 }
99 }
100
101 impl IntoIterator for Bal {
102 type Item = AccountChanges;
103 type IntoIter = IntoIter<AccountChanges>;
104
105 fn into_iter(self) -> Self::IntoIter {
106 self.0.into_iter()
107 }
108 }
109
110 impl<'a> IntoIterator for &'a Bal {
111 type Item = &'a AccountChanges;
112 type IntoIter = Iter<'a, AccountChanges>;
113
114 fn into_iter(self) -> Self::IntoIter {
115 self.iter()
116 }
117 }
118
119 impl FromIterator<AccountChanges> for Bal {
120 fn from_iter<I: IntoIterator<Item = AccountChanges>>(iter: I) -> Self {
121 Self(iter.into_iter().collect())
122 }
123 }
124
125 impl<I> Index<I> for Bal
126 where
127 I: core::slice::SliceIndex<[AccountChanges]>,
128 {
129 type Output = I::Output;
130
131 #[inline]
132 fn index(&self, index: I) -> &Self::Output {
133 &self.0[index]
134 }
135 }
136
137 impl Bal {
138 pub const fn new(account_changes: Vec<AccountChanges>) -> Self {
140 Self(account_changes)
141 }
142
143 pub fn push(&mut self, account_changes: AccountChanges) {
145 self.0.push(account_changes)
146 }
147
148 #[inline]
150 pub const fn is_empty(&self) -> bool {
151 self.0.is_empty()
152 }
153
154 #[inline]
156 pub const fn len(&self) -> usize {
157 self.0.len()
158 }
159
160 #[inline]
162 pub fn iter(&self) -> Iter<'_, AccountChanges> {
163 self.0.iter()
164 }
165
166 #[inline]
168 pub const fn as_slice(&self) -> &[AccountChanges] {
169 self.0.as_slice()
170 }
171
172 pub fn into_inner(self) -> Vec<AccountChanges> {
174 self.0
175 }
176
177 pub fn sort(&mut self) {
196 self.0.sort_unstable_by_key(|account| account.address);
197
198 for account in &mut self.0 {
199 account.sort();
200 }
201 }
202
203 #[inline]
205 pub const fn account_count(&self) -> usize {
206 self.0.len()
207 }
208
209 pub fn total_storage_changes(&self) -> usize {
211 self.0.iter().map(|a| a.storage_changes.len()).sum()
212 }
213
214 pub fn total_storage_reads(&self) -> usize {
216 self.0.iter().map(|a| a.storage_reads.len()).sum()
217 }
218
219 pub fn total_slots(&self) -> usize {
221 self.0.iter().map(|a| a.storage_changes.len() + a.storage_reads.len()).sum()
222 }
223
224 pub fn total_balance_changes(&self) -> usize {
226 self.0.iter().map(|a| a.balance_changes.len()).sum()
227 }
228
229 pub fn total_nonce_changes(&self) -> usize {
231 self.0.iter().map(|a| a.nonce_changes.len()).sum()
232 }
233
234 pub fn total_code_changes(&self) -> usize {
236 self.0.iter().map(|a| a.code_changes.len()).sum()
237 }
238
239 pub fn change_counts(&self) -> BalChangeCounts {
241 let mut counts = BalChangeCounts::default();
242 for account in &self.0 {
243 counts.accounts += 1;
244 counts.storage += account.storage_changes.len();
245 counts.balance += account.balance_changes.len();
246 counts.nonce += account.nonce_changes.len();
247 counts.code += account.code_changes.len();
248 }
249 counts
250 }
251
252 pub fn total_bal_items(&self) -> u64 {
255 super::total_bal_items(&self.0)
256 }
257
258 #[cfg(feature = "rlp")]
260 pub fn compute_hash(&self) -> alloy_primitives::B256 {
261 if self.0.is_empty() {
262 return crate::constants::EMPTY_BLOCK_ACCESS_LIST_HASH;
263 }
264 super::compute_block_access_list_hash(&self.0)
265 }
266 }
267
268 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
270 pub struct BalChangeCounts {
271 pub accounts: usize,
273 pub storage: usize,
275 pub balance: usize,
277 pub nonce: usize,
279 pub code: usize,
281 }
282
283 #[derive(Clone, Debug)]
288 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
289 pub struct DecodedBal {
290 decoded: Bal,
292 raw: Bytes,
294 #[cfg_attr(feature = "serde", serde(skip, default))]
296 hash: OnceLock<alloy_primitives::B256>,
297 }
298
299 impl PartialEq for DecodedBal {
300 fn eq(&self, other: &Self) -> bool {
301 self.decoded == other.decoded && self.raw == other.raw
302 }
303 }
304
305 impl Eq for DecodedBal {}
306
307 impl DecodedBal {
308 pub const fn new(decoded: Bal, raw: Bytes) -> Self {
310 Self { decoded, raw, hash: OnceLock::new() }
311 }
312
313 #[cfg(feature = "rlp")]
315 pub fn from_rlp_bytes(raw: Bytes) -> Result<Self, alloy_rlp::Error> {
316 let mut slice = raw.as_ref();
317 let decoded = <Bal as alloy_rlp::Decodable>::decode(&mut slice)?;
318 if !slice.is_empty() {
319 return Err(alloy_rlp::Error::UnexpectedLength);
320 }
321 Ok(Self::new(decoded, raw))
322 }
323
324 pub const fn as_bal(&self) -> &Bal {
326 &self.decoded
327 }
328
329 pub const fn as_raw(&self) -> &Bytes {
331 &self.raw
332 }
333
334 #[cfg(feature = "rlp")]
336 pub fn as_sealed_bal(&self) -> alloy_primitives::Sealed<&Bal> {
337 alloy_primitives::Sealable::seal_ref_unchecked(&self.decoded, self.hash())
338 }
339
340 pub fn split(self) -> (Bal, Bytes) {
342 (self.decoded, self.raw)
343 }
344
345 pub fn into_parts(self) -> (Bal, Bytes, alloy_primitives::B256) {
347 let hash = self.hash();
348 let (decoded, raw) = self.split();
349 (decoded, raw, hash)
350 }
351
352 #[cfg(feature = "rlp")]
354 pub fn into_sealed(self) -> alloy_primitives::Sealed<Bal> {
355 let seal = self.hash();
356 let (decoded, _) = self.split();
357 alloy_primitives::Sealable::seal_unchecked(decoded, seal)
358 }
359
360 pub fn hash(&self) -> alloy_primitives::B256 {
364 #[allow(clippy::useless_conversion)]
365 *self.hash.get_or_init(|| alloy_primitives::keccak256(self.raw.as_ref()).into())
366 }
367 }
368
369 #[cfg(feature = "rlp")]
370 impl alloy_rlp::Decodable for DecodedBal {
371 fn decode(buf: &mut &[u8]) -> Result<Self, alloy_rlp::Error> {
372 let original = *buf;
373 let decoded = <Bal as alloy_rlp::Decodable>::decode(buf)?;
374 let consumed = original.len() - buf.len();
375 let raw = Bytes::copy_from_slice(&original[..consumed]);
376 Ok(Self::new(decoded, raw))
377 }
378 }
379
380 #[cfg(feature = "rlp")]
381 impl alloy_rlp::Encodable for DecodedBal {
382 fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
383 out.put_slice(&self.raw);
384 }
385
386 fn length(&self) -> usize {
387 self.raw.len()
388 }
389 }
390}
391
392#[cfg(test)]
393mod hash_tests {
394 use super::bal::{Bal, DecodedBal};
395 use crate::{
396 AccountChanges, BalanceChange, BlockAccessIndex, CodeChange, NonceChange, SlotChanges,
397 StorageChange,
398 };
399 use alloy_primitives::{Address, Bytes, U256};
400
401 #[test]
402 fn decoded_bal_hash_uses_raw_bytes_without_rlp_feature() {
403 let raw = Bytes::from_static(&[0xc0]);
404 let decoded = DecodedBal::new(Bal::default(), raw.clone());
405
406 assert_eq!(decoded.hash(), alloy_primitives::keccak256(raw.as_ref()));
407
408 let (bal, split_raw, split_hash) = decoded.into_parts();
409 assert!(bal.is_empty());
410 assert_eq!(split_raw, raw);
411 assert_eq!(split_hash, alloy_primitives::keccak256(raw.as_ref()));
412 }
413
414 #[test]
415 fn bal_sort_orders_all_eip7928_lists() {
416 let address_1 = Address::from([0x11; 20]);
417 let address_2 = Address::from([0x22; 20]);
418 let mut bal = Bal::new(vec![
419 AccountChanges {
420 address: address_2,
421 storage_changes: vec![
422 SlotChanges::new(
423 U256::from(3),
424 vec![
425 StorageChange::new(BlockAccessIndex::new(8), U256::from(0x80)),
426 StorageChange::new(BlockAccessIndex::new(2), U256::from(0x20)),
427 ],
428 ),
429 SlotChanges::new(
430 U256::from(1),
431 vec![
432 StorageChange::new(BlockAccessIndex::new(5), U256::from(0x50)),
433 StorageChange::new(BlockAccessIndex::new(1), U256::from(0x10)),
434 ],
435 ),
436 ],
437 storage_reads: vec![U256::from(4), U256::from(2)],
438 balance_changes: vec![
439 BalanceChange::new(BlockAccessIndex::new(6), U256::from(600)),
440 BalanceChange::new(BlockAccessIndex::new(3), U256::from(300)),
441 ],
442 nonce_changes: vec![
443 NonceChange::new(BlockAccessIndex::new(7), 70),
444 NonceChange::new(BlockAccessIndex::new(4), 40),
445 ],
446 code_changes: vec![
447 CodeChange::new(BlockAccessIndex::new(9), Bytes::from_static(&[0x60, 0x09])),
448 CodeChange::new(BlockAccessIndex::new(5), Bytes::from_static(&[0x60, 0x05])),
449 ],
450 },
451 AccountChanges {
452 address: address_1,
453 storage_changes: vec![
454 SlotChanges::new(
455 U256::from(2),
456 vec![
457 StorageChange::new(BlockAccessIndex::new(4), U256::from(0x40)),
458 StorageChange::new(BlockAccessIndex::new(0), U256::from(0x00)),
459 ],
460 ),
461 SlotChanges::new(
462 U256::from(1),
463 vec![
464 StorageChange::new(BlockAccessIndex::new(3), U256::from(0x30)),
465 StorageChange::new(BlockAccessIndex::new(1), U256::from(0x10)),
466 ],
467 ),
468 ],
469 storage_reads: vec![U256::from(5), U256::from(3)],
470 balance_changes: vec![
471 BalanceChange::new(BlockAccessIndex::new(5), U256::from(500)),
472 BalanceChange::new(BlockAccessIndex::new(2), U256::from(200)),
473 ],
474 nonce_changes: vec![
475 NonceChange::new(BlockAccessIndex::new(8), 80),
476 NonceChange::new(BlockAccessIndex::new(1), 10),
477 ],
478 code_changes: vec![
479 CodeChange::new(BlockAccessIndex::new(4), Bytes::from_static(&[0x60, 0x04])),
480 CodeChange::new(BlockAccessIndex::new(2), Bytes::from_static(&[0x60, 0x02])),
481 ],
482 },
483 ]);
484
485 bal.sort();
486
487 assert_eq!(bal[0].address, address_1);
488 assert_eq!(bal[1].address, address_2);
489
490 for account in bal.iter() {
491 assert!(account.storage_changes.windows(2).all(|slots| slots[0].slot <= slots[1].slot));
492 for slot_changes in &account.storage_changes {
493 assert!(
494 slot_changes
495 .changes
496 .windows(2)
497 .all(|changes| changes[0].block_access_index
498 <= changes[1].block_access_index)
499 );
500 }
501 assert!(account.storage_reads.windows(2).all(|slots| slots[0] <= slots[1]));
502 assert!(
503 account
504 .balance_changes
505 .windows(2)
506 .all(|changes| changes[0].block_access_index <= changes[1].block_access_index)
507 );
508 assert!(
509 account
510 .nonce_changes
511 .windows(2)
512 .all(|changes| changes[0].block_access_index <= changes[1].block_access_index)
513 );
514 assert!(
515 account
516 .code_changes
517 .windows(2)
518 .all(|changes| changes[0].block_access_index <= changes[1].block_access_index)
519 );
520 }
521 }
522}
523
524#[cfg(all(test, feature = "rlp"))]
525mod tests {
526 use super::bal::{Bal, DecodedBal};
527 use crate::{
528 AccountChanges, BalanceChange, BlockAccessIndex, CodeChange, NonceChange, SlotChanges,
529 StorageChange, constants::EMPTY_BLOCK_ACCESS_LIST_HASH,
530 };
531 use alloy_primitives::{Address, Bytes, U256};
532
533 fn sample_bal() -> Bal {
534 Bal::new(vec![
535 AccountChanges::new(Address::from([0x11; 20]))
536 .with_storage_read(U256::from(0x10))
537 .with_storage_change(SlotChanges::new(
538 U256::from(0x01),
539 vec![StorageChange::new(BlockAccessIndex::new(0), U256::from(0xaa))],
540 ))
541 .with_balance_change(BalanceChange::new(
542 BlockAccessIndex::new(1),
543 U256::from(1_000),
544 ))
545 .with_nonce_change(NonceChange::new(BlockAccessIndex::new(2), 7))
546 .with_code_change(CodeChange::new(
547 BlockAccessIndex::new(3),
548 Bytes::from(vec![0x60, 0x00]),
549 )),
550 AccountChanges::new(Address::from([0x22; 20]))
551 .with_storage_read(U256::from(0x20))
552 .with_storage_change(SlotChanges::new(
553 U256::from(0x02),
554 vec![StorageChange::new(BlockAccessIndex::new(4), U256::from(0xbb))],
555 )),
556 ])
557 }
558
559 #[test]
560 fn bal_compute_hash_returns_empty_hash_for_empty_bal() {
561 let bal = Bal::default();
562
563 assert_eq!(bal.compute_hash(), EMPTY_BLOCK_ACCESS_LIST_HASH);
564 }
565
566 #[test]
567 fn bal_compute_hash_matches_free_function_for_non_empty_bal() {
568 let bal = sample_bal();
569
570 assert_eq!(bal.compute_hash(), super::compute_block_access_list_hash(bal.as_slice()));
571 assert_ne!(bal.compute_hash(), EMPTY_BLOCK_ACCESS_LIST_HASH);
572 }
573
574 #[test]
575 fn decoded_bal_from_rlp_bytes_preserves_raw_and_hash() {
576 let bal = sample_bal();
577 let raw = Bytes::from(alloy_rlp::encode(&bal));
578 let decoded = DecodedBal::from_rlp_bytes(raw.clone()).unwrap();
579
580 assert_eq!(decoded.as_bal(), &bal);
581 assert_eq!(decoded.as_raw(), &raw);
582 assert_eq!(decoded.hash(), bal.compute_hash());
583 assert_eq!(decoded.hash(), alloy_primitives::keccak256(raw.as_ref()));
584 assert_eq!(decoded.as_sealed_bal().hash(), bal.compute_hash());
585 assert_eq!(decoded.as_sealed_bal().inner(), &decoded.as_bal());
586
587 let (split_bal, split_raw) = decoded.clone().split();
588 assert_eq!(split_bal, bal);
589 assert_eq!(split_raw, raw);
590
591 let (split_bal, split_raw, split_hash) = decoded.clone().into_parts();
592 assert_eq!(split_bal, bal);
593 assert_eq!(split_raw, raw);
594 assert_eq!(split_hash, bal.compute_hash());
595
596 let sealed = decoded.into_sealed();
597 assert_eq!(sealed.hash(), bal.compute_hash());
598 assert_eq!(sealed.inner(), &bal);
599 }
600
601 #[test]
602 fn decoded_bal_decode_consumes_exact_raw_rlp_item() {
603 let bal = sample_bal();
604 let raw = alloy_rlp::encode(&bal);
605 let mut buf = raw.as_ref();
606 let decoded = <DecodedBal as alloy_rlp::Decodable>::decode(&mut buf).unwrap();
607
608 assert!(buf.is_empty());
609 assert_eq!(decoded.as_bal(), &bal);
610 assert_eq!(decoded.as_raw().as_ref(), raw.as_slice());
611 assert_eq!(alloy_rlp::encode(&decoded), raw);
612 }
613}