Skip to main content

revm_state/bal/
account.rs

1//! BAL builder module
2
3#[cfg(feature = "account-ext")]
4use crate::AccountExtension;
5use crate::{
6    bal::{writes::BalWrites, BalAccountInfo, BalAccountLookup, BalError, BlockAccessIndex},
7    Account, AccountInfo, EvmStorage,
8};
9use alloy_eip7928::{
10    AccountChanges as AlloyAccountChanges, BalanceChange as AlloyBalanceChange,
11    CodeChange as AlloyCodeChange, NonceChange as AlloyNonceChange,
12    SlotChanges as AlloySlotChanges, StorageChange as AlloyStorageChange,
13};
14use bytecode::{Bytecode, BytecodeDecodeError};
15use core::ops::{Deref, DerefMut};
16use primitives::{Address, StorageKey, StorageValue, B256, U256};
17use std::{
18    collections::{btree_map::Entry, BTreeMap},
19    vec::Vec,
20};
21
22/// Account BAL structure.
23#[derive(Debug, Default, Clone, PartialEq, Eq)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25pub struct AccountBal {
26    /// Account info bal.
27    pub account_info: AccountInfoBal,
28    /// Storage bal.
29    pub storage: StorageBal,
30}
31
32impl Deref for AccountBal {
33    type Target = AccountInfoBal;
34
35    fn deref(&self) -> &Self::Target {
36        &self.account_info
37    }
38}
39
40impl DerefMut for AccountBal {
41    fn deref_mut(&mut self) -> &mut Self::Target {
42        &mut self.account_info
43    }
44}
45
46impl AccountBal {
47    /// Populate account from BAL. Return true if account info got changed
48    pub fn populate_account_info(
49        &self,
50        bal_index: BlockAccessIndex,
51        account: &mut AccountInfo,
52    ) -> bool {
53        self.account_info.populate_account_info(bal_index, account)
54    }
55
56    /// Extend account from another account.
57    #[inline]
58    pub fn update(&mut self, bal_index: BlockAccessIndex, account: &Account) {
59        if account.is_selfdestructed_locally() {
60            let empty_info = AccountInfo::default();
61            self.account_info
62                .update(bal_index, &account.original_info(), &empty_info);
63            // Selfdestruct wipes all storage to zero, record writes accordingly.
64            self.storage
65                .update_selfdestruct(bal_index, &account.storage);
66            return;
67        }
68
69        self.account_info
70            .update(bal_index, &account.original_info(), &account.info);
71
72        self.storage.update(bal_index, &account.storage);
73    }
74
75    /// Create an account BAL from EIP-7928 [`AlloyAccountChanges`].
76    ///
77    /// # Errors
78    ///
79    /// Returns [`BytecodeDecodeError`] if any code change contains bytecode rejected by
80    /// [`Bytecode::new_raw_checked`]. This currently happens for malformed EIP-7702
81    /// bytecode, such as bytes with the EIP-7702 magic prefix but an invalid length or
82    /// unsupported version.
83    #[inline]
84    pub fn try_from_alloy(
85        alloy_account: AlloyAccountChanges,
86    ) -> Result<(Address, Self), BytecodeDecodeError> {
87        Ok((
88            alloy_account.address,
89            AccountBal {
90                account_info: AccountInfoBal {
91                    nonce: BalWrites::from(alloy_account.nonce_changes),
92                    balance: BalWrites::from(alloy_account.balance_changes),
93                    code: BalWrites::try_from(alloy_account.code_changes)?,
94                    #[cfg(feature = "account-ext")]
95                    extension: BalWrites::default(),
96                },
97                storage: StorageBal::from_iter(
98                    alloy_account
99                        .storage_changes
100                        .into_iter()
101                        .chain(
102                            alloy_account
103                                .storage_reads
104                                .into_iter()
105                                .map(|key| AlloySlotChanges::new(key, Default::default())),
106                        )
107                        .map(|slot| (slot.slot, BalWrites::from(slot.changes))),
108                ),
109            },
110        ))
111    }
112
113    /// Clone an account BAL from EIP-7928 [`AlloyAccountChanges`] without consuming the source.
114    ///
115    /// # Errors
116    ///
117    /// Returns [`BytecodeDecodeError`] if any code change contains bytecode rejected by
118    /// [`Bytecode::new_raw_checked`]. This currently happens for malformed EIP-7702
119    /// bytecode, such as bytes with the EIP-7702 magic prefix but an invalid length or
120    /// unsupported version.
121    #[inline]
122    pub fn clone_from_alloy(
123        alloy_account: &AlloyAccountChanges,
124    ) -> Result<(Address, Self), BytecodeDecodeError> {
125        Ok((
126            alloy_account.address,
127            AccountBal {
128                account_info: AccountInfoBal {
129                    nonce: BalWrites::from(alloy_account.nonce_changes.as_slice()),
130                    balance: BalWrites::from(alloy_account.balance_changes.as_slice()),
131                    code: BalWrites::try_from(alloy_account.code_changes.as_slice())?,
132                    #[cfg(feature = "account-ext")]
133                    extension: BalWrites::default(),
134                },
135                storage: StorageBal::from_iter(
136                    alloy_account
137                        .storage_changes
138                        .iter()
139                        .map(|slot| (slot.slot, BalWrites::from(slot.changes.as_slice())))
140                        .chain(
141                            alloy_account
142                                .storage_reads
143                                .iter()
144                                .map(|key| (*key, BalWrites::default())),
145                        ),
146                ),
147            },
148        ))
149    }
150
151    /// Consumes `AccountBal` and converts it into canonical EIP-7928
152    /// [`AlloyAccountChanges`].
153    ///
154    /// The returned account changes are ordered deterministically: storage reads
155    /// and storage changes are sorted lexicographically by slot key, changes
156    /// within each storage slot are sorted by block access index, and balance,
157    /// nonce, and code changes are sorted by block access index.
158    ///
159    /// This matches the EIP-7928 ordering requirements:
160    /// <https://eips.ethereum.org/EIPS/eip-7928#ordering-uniqueness-and-determinism>.
161    #[inline]
162    pub fn into_alloy_account(self, address: Address) -> AlloyAccountChanges {
163        let storage_len = self.storage.storage.len();
164        let mut storage_reads = Vec::with_capacity(storage_len);
165        let mut storage_changes = Vec::with_capacity(storage_len);
166        for (key, value) in self.storage.storage {
167            if value.writes.is_empty() {
168                storage_reads.push(key);
169            } else {
170                let mut changes = value
171                    .writes
172                    .into_iter()
173                    .map(|(index, value)| AlloyStorageChange::new(index, value))
174                    .collect::<Vec<_>>();
175                changes.sort_unstable_by_key(|change| change.block_access_index);
176
177                storage_changes.push(AlloySlotChanges::new(key, changes));
178            }
179        }
180
181        let mut balance_changes = self
182            .account_info
183            .balance
184            .writes
185            .into_iter()
186            .map(|(index, value)| AlloyBalanceChange::new(index, value))
187            .collect::<Vec<_>>();
188        balance_changes.sort_unstable_by_key(|change| change.block_access_index);
189
190        let mut nonce_changes = self
191            .account_info
192            .nonce
193            .writes
194            .into_iter()
195            .map(|(index, value)| AlloyNonceChange::new(index, value))
196            .collect::<Vec<_>>();
197        nonce_changes.sort_unstable_by_key(|change| change.block_access_index);
198
199        let mut code_changes = self
200            .account_info
201            .code
202            .writes
203            .into_iter()
204            .map(|(index, (_, value))| AlloyCodeChange::new(index, value.original_bytes()))
205            .collect::<Vec<_>>();
206        code_changes.sort_unstable_by_key(|change| change.block_access_index);
207
208        AlloyAccountChanges {
209            address,
210            storage_changes,
211            storage_reads,
212            balance_changes,
213            nonce_changes,
214            code_changes,
215        }
216    }
217}
218
219/// Account info bal structure.
220#[derive(Debug, Default, Clone, PartialEq, Eq)]
221#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
222pub struct AccountInfoBal {
223    /// Nonce builder.
224    pub nonce: BalWrites<u64>,
225    /// Balance builder.
226    pub balance: BalWrites<U256>,
227    /// Code builder.
228    pub code: BalWrites<(B256, Bytecode)>,
229    /// Chain-specific account extension builder.
230    #[cfg(feature = "account-ext")]
231    #[cfg_attr(
232        feature = "serde",
233        serde(default, skip_serializing_if = "BalWrites::is_empty")
234    )]
235    pub extension: BalWrites<AccountExtension>,
236}
237
238impl AccountInfoBal {
239    /// Populate account info from BAL. Return true if account info got changed
240    pub fn populate_account_info(
241        &self,
242        bal_index: BlockAccessIndex,
243        account: &mut AccountInfo,
244    ) -> bool {
245        let mut changed = false;
246        if let Some(nonce) = self.nonce.get(bal_index) {
247            account.nonce = nonce;
248            changed = true;
249        }
250        if let Some(balance) = self.balance.get(bal_index) {
251            account.balance = balance;
252            changed = true;
253        }
254        if let Some(code) = self.code.get(bal_index) {
255            account.code_hash = code.0;
256            account.code = Some(code.1);
257            changed = true;
258        }
259        #[cfg(feature = "account-ext")]
260        if let Some(extension) = self.extension.get(bal_index) {
261            account.extension = extension;
262            changed = true;
263        }
264        changed
265    }
266
267    /// Looks up account fields written strictly before `bal_index`.
268    ///
269    /// Returns [`BalAccountLookup::Complete`] when balance, nonce, and code are all written, and
270    /// [`BalAccountLookup::Partial`] with the written fields otherwise. With the `account-ext`
271    /// feature, a complete account also requires an extension write, since [`BalAccountInfo`]
272    /// cannot carry the extension.
273    pub fn account_info_lookup(&self, bal_index: BlockAccessIndex) -> BalAccountLookup {
274        let code = self.code.get(bal_index);
275        let info = BalAccountInfo {
276            balance: self.balance.get(bal_index),
277            nonce: self.nonce.get(bal_index),
278            code_hash: code.as_ref().map(|(hash, _)| *hash),
279        };
280        let (Some(balance), Some(nonce), Some((code_hash, code))) =
281            (info.balance, info.nonce, code)
282        else {
283            return BalAccountLookup::Partial(info);
284        };
285        #[cfg(feature = "account-ext")]
286        let Some(extension) = self.extension.get(bal_index) else {
287            return BalAccountLookup::Partial(info);
288        };
289        let account = AccountInfo::new(balance, nonce, code_hash, code);
290        #[cfg(feature = "account-ext")]
291        let account = account.with_extension(extension);
292        BalAccountLookup::Complete(account)
293    }
294
295    /// Extend account info from another account info.
296    #[inline]
297    pub fn update(
298        &mut self,
299        index: BlockAccessIndex,
300        original: &AccountInfo,
301        present: &AccountInfo,
302    ) {
303        self.nonce.update(index, &original.nonce, present.nonce);
304        self.balance
305            .update(index, &original.balance, present.balance);
306        if original.code_hash != present.code_hash {
307            self.code.update_with_key(
308                index,
309                &original.code_hash,
310                (present.code_hash, present.code.clone().unwrap_or_default()),
311                |i| &i.0,
312            );
313        }
314        #[cfg(feature = "account-ext")]
315        self.extension
316            .update(index, &original.extension, present.extension.clone());
317    }
318
319    /// Extend account info from another account info.
320    #[inline]
321    pub fn extend(&mut self, bal_account: AccountInfoBal) {
322        self.nonce.extend(bal_account.nonce);
323        self.balance.extend(bal_account.balance);
324        self.code.extend(bal_account.code);
325        #[cfg(feature = "account-ext")]
326        self.extension.extend(bal_account.extension);
327    }
328
329    /// Update account balance in BAL.
330    #[inline]
331    pub fn balance_update(
332        &mut self,
333        bal_index: BlockAccessIndex,
334        original_balance: &U256,
335        balance: U256,
336    ) {
337        self.balance.update(bal_index, original_balance, balance);
338    }
339
340    /// Update account nonce in BAL.
341    #[inline]
342    pub fn nonce_update(&mut self, bal_index: BlockAccessIndex, original_nonce: &u64, nonce: u64) {
343        self.nonce.update(bal_index, original_nonce, nonce);
344    }
345
346    /// Update account code in BAL.
347    #[inline]
348    pub fn code_update(
349        &mut self,
350        bal_index: BlockAccessIndex,
351        original_code_hash: &B256,
352        code_hash: B256,
353        code: Bytecode,
354    ) {
355        self.code
356            .update_with_key(bal_index, original_code_hash, (code_hash, code), |i| &i.0);
357    }
358}
359
360/// Storage BAL
361#[derive(Debug, Default, Clone, PartialEq, Eq)]
362#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
363pub struct StorageBal {
364    /// Storage with writes and reads.
365    pub storage: BTreeMap<StorageKey, BalWrites<StorageValue>>,
366}
367
368impl StorageBal {
369    /// Get storage from the builder.
370    #[inline]
371    pub fn get(
372        &self,
373        address: &Address,
374        key: StorageKey,
375        bal_index: BlockAccessIndex,
376    ) -> Result<Option<StorageValue>, BalError> {
377        Ok(self.get_bal_writes(address, key)?.get(bal_index))
378    }
379
380    /// Get storage writes from the builder.
381    ///
382    /// `address` is only needed in case of an error to propagate the address.
383    #[inline]
384    pub fn get_bal_writes(
385        &self,
386        address: &Address,
387        key: StorageKey,
388    ) -> Result<&BalWrites<StorageValue>, BalError> {
389        self.storage.get(&key).ok_or(BalError::SlotNotFound {
390            address: *address,
391            slot: key,
392        })
393    }
394
395    /// Extend storage from another storage.
396    #[inline]
397    pub fn extend(&mut self, storage: StorageBal) {
398        for (key, value) in storage.storage {
399            match self.storage.entry(key) {
400                Entry::Occupied(mut entry) => {
401                    entry.get_mut().extend(value);
402                }
403                Entry::Vacant(entry) => {
404                    entry.insert(value);
405                }
406            }
407        }
408    }
409
410    /// Update storage from [`EvmStorage`].
411    #[inline]
412    pub fn update(&mut self, bal_index: BlockAccessIndex, storage: &EvmStorage) {
413        for (key, value) in storage {
414            self.storage.entry(*key).or_default().update(
415                bal_index,
416                &value.original_value,
417                value.present_value,
418            );
419        }
420    }
421
422    /// Update storage for a selfdestructed account.
423    ///
424    /// All accessed slots are recorded as written to zero since selfdestruct wipes storage.
425    #[inline]
426    pub fn update_selfdestruct(&mut self, bal_index: BlockAccessIndex, storage: &EvmStorage) {
427        for (key, value) in storage {
428            self.storage.entry(*key).or_default().update(
429                bal_index,
430                &value.original_value,
431                StorageValue::ZERO,
432            );
433        }
434    }
435
436    /// Update reads from [`EvmStorage`].
437    ///
438    /// It will expend inner map with new reads.
439    #[inline]
440    pub fn update_reads(&mut self, storage: impl Iterator<Item = StorageKey>) {
441        for key in storage {
442            self.storage.entry(key).or_default();
443        }
444    }
445
446    /// Insert storage into the builder.
447    pub fn extend_iter(
448        &mut self,
449        storage: impl Iterator<Item = (StorageKey, BalWrites<StorageValue>)>,
450    ) {
451        for (key, value) in storage {
452            self.storage.insert(key, value);
453        }
454    }
455
456    /// Convert the storage into a vector of reads and writes
457    pub fn into_vecs(self) -> (Vec<StorageKey>, Vec<(StorageKey, BalWrites<StorageValue>)>) {
458        let mut reads = Vec::new();
459        let mut writes = Vec::new();
460
461        for (key, value) in self.storage {
462            if value.writes.is_empty() {
463                reads.push(key);
464            } else {
465                writes.push((key, value));
466            }
467        }
468
469        (reads, writes)
470    }
471}
472
473impl FromIterator<(StorageKey, BalWrites<StorageValue>)> for StorageBal {
474    fn from_iter<I: IntoIterator<Item = (StorageKey, BalWrites<StorageValue>)>>(iter: I) -> Self {
475        Self {
476            storage: iter.into_iter().collect(),
477        }
478    }
479}