Skip to main content

revm_state/bal/
account.rs

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