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, 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    /// Extend account info from another account info.
268    #[inline]
269    pub fn update(
270        &mut self,
271        index: BlockAccessIndex,
272        original: &AccountInfo,
273        present: &AccountInfo,
274    ) {
275        self.nonce.update(index, &original.nonce, present.nonce);
276        self.balance
277            .update(index, &original.balance, present.balance);
278        if original.code_hash != present.code_hash {
279            self.code.update_with_key(
280                index,
281                &original.code_hash,
282                (present.code_hash, present.code.clone().unwrap_or_default()),
283                |i| &i.0,
284            );
285        }
286        #[cfg(feature = "account-ext")]
287        self.extension
288            .update(index, &original.extension, present.extension.clone());
289    }
290
291    /// Extend account info from another account info.
292    #[inline]
293    pub fn extend(&mut self, bal_account: AccountInfoBal) {
294        self.nonce.extend(bal_account.nonce);
295        self.balance.extend(bal_account.balance);
296        self.code.extend(bal_account.code);
297        #[cfg(feature = "account-ext")]
298        self.extension.extend(bal_account.extension);
299    }
300
301    /// Update account balance in BAL.
302    #[inline]
303    pub fn balance_update(
304        &mut self,
305        bal_index: BlockAccessIndex,
306        original_balance: &U256,
307        balance: U256,
308    ) {
309        self.balance.update(bal_index, original_balance, balance);
310    }
311
312    /// Update account nonce in BAL.
313    #[inline]
314    pub fn nonce_update(&mut self, bal_index: BlockAccessIndex, original_nonce: &u64, nonce: u64) {
315        self.nonce.update(bal_index, original_nonce, nonce);
316    }
317
318    /// Update account code in BAL.
319    #[inline]
320    pub fn code_update(
321        &mut self,
322        bal_index: BlockAccessIndex,
323        original_code_hash: &B256,
324        code_hash: B256,
325        code: Bytecode,
326    ) {
327        self.code
328            .update_with_key(bal_index, original_code_hash, (code_hash, code), |i| &i.0);
329    }
330}
331
332/// Storage BAL
333#[derive(Debug, Default, Clone, PartialEq, Eq)]
334#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
335pub struct StorageBal {
336    /// Storage with writes and reads.
337    pub storage: BTreeMap<StorageKey, BalWrites<StorageValue>>,
338}
339
340impl StorageBal {
341    /// Get storage from the builder.
342    #[inline]
343    pub fn get(
344        &self,
345        address: &Address,
346        key: StorageKey,
347        bal_index: BlockAccessIndex,
348    ) -> Result<Option<StorageValue>, BalError> {
349        Ok(self.get_bal_writes(address, key)?.get(bal_index))
350    }
351
352    /// Get storage writes from the builder.
353    ///
354    /// `address` is only needed in case of an error to propagate the address.
355    #[inline]
356    pub fn get_bal_writes(
357        &self,
358        address: &Address,
359        key: StorageKey,
360    ) -> Result<&BalWrites<StorageValue>, BalError> {
361        self.storage.get(&key).ok_or(BalError::SlotNotFound {
362            address: *address,
363            slot: key,
364        })
365    }
366
367    /// Extend storage from another storage.
368    #[inline]
369    pub fn extend(&mut self, storage: StorageBal) {
370        for (key, value) in storage.storage {
371            match self.storage.entry(key) {
372                Entry::Occupied(mut entry) => {
373                    entry.get_mut().extend(value);
374                }
375                Entry::Vacant(entry) => {
376                    entry.insert(value);
377                }
378            }
379        }
380    }
381
382    /// Update storage from [`EvmStorage`].
383    #[inline]
384    pub fn update(&mut self, bal_index: BlockAccessIndex, storage: &EvmStorage) {
385        for (key, value) in storage {
386            self.storage.entry(*key).or_default().update(
387                bal_index,
388                &value.original_value,
389                value.present_value,
390            );
391        }
392    }
393
394    /// Update storage for a selfdestructed account.
395    ///
396    /// All accessed slots are recorded as written to zero since selfdestruct wipes storage.
397    #[inline]
398    pub fn update_selfdestruct(&mut self, bal_index: BlockAccessIndex, storage: &EvmStorage) {
399        for (key, value) in storage {
400            self.storage.entry(*key).or_default().update(
401                bal_index,
402                &value.original_value,
403                StorageValue::ZERO,
404            );
405        }
406    }
407
408    /// Update reads from [`EvmStorage`].
409    ///
410    /// It will expend inner map with new reads.
411    #[inline]
412    pub fn update_reads(&mut self, storage: impl Iterator<Item = StorageKey>) {
413        for key in storage {
414            self.storage.entry(key).or_default();
415        }
416    }
417
418    /// Insert storage into the builder.
419    pub fn extend_iter(
420        &mut self,
421        storage: impl Iterator<Item = (StorageKey, BalWrites<StorageValue>)>,
422    ) {
423        for (key, value) in storage {
424            self.storage.insert(key, value);
425        }
426    }
427
428    /// Convert the storage into a vector of reads and writes
429    pub fn into_vecs(self) -> (Vec<StorageKey>, Vec<(StorageKey, BalWrites<StorageValue>)>) {
430        let mut reads = Vec::new();
431        let mut writes = Vec::new();
432
433        for (key, value) in self.storage {
434            if value.writes.is_empty() {
435                reads.push(key);
436            } else {
437                writes.push((key, value));
438            }
439        }
440
441        (reads, writes)
442    }
443}
444
445impl FromIterator<(StorageKey, BalWrites<StorageValue>)> for StorageBal {
446    fn from_iter<I: IntoIterator<Item = (StorageKey, BalWrites<StorageValue>)>>(iter: I) -> Self {
447        Self {
448            storage: iter.into_iter().collect(),
449        }
450    }
451}