Skip to main content

revm_state/
lib.rs

1//! Account and storage state.
2#![cfg_attr(not(test), warn(unused_crate_dependencies))]
3#![cfg_attr(not(feature = "std"), no_std)]
4
5#[cfg(not(feature = "std"))]
6extern crate alloc as std;
7
8#[cfg(feature = "account-ext")]
9mod account_extension;
10mod account_info;
11#[cfg(feature = "account-ext")]
12pub use account_extension::AccountExtension;
13pub mod bal;
14mod types;
15
16pub use bytecode;
17
18pub use account_info::{AccountId, AccountInfo};
19pub use bytecode::Bytecode;
20pub use primitives;
21pub use types::{EvmState, EvmStorage, TransientStorage};
22
23use bitflags::bitflags;
24use nonmax::NonMaxU32;
25use primitives::{hardfork::SpecId, HashMap, StorageKey, StorageValue, U256};
26use std::boxed::Box;
27
28/// Transaction id used to track when account or storage slot was touched/loaded into the journal.
29///
30/// Wraps a [`NonMaxU32`] so that `Option<TransactionId>` benefits from niche optimization.
31#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33#[cfg_attr(feature = "serde", serde(transparent))]
34pub struct TransactionId(NonMaxU32);
35
36impl TransactionId {
37    /// The zero transaction id.
38    pub const ZERO: Self = Self(NonMaxU32::ZERO);
39
40    /// Creates a new [`TransactionId`].
41    ///
42    /// Returns `None` if the value does not fit in the internal representation.
43    #[inline]
44    pub fn new(id: usize) -> Option<Self> {
45        let id = u32::try_from(id).ok()?;
46        NonMaxU32::new(id).map(Self)
47    }
48
49    /// Returns the transaction id as a usize.
50    #[inline]
51    pub const fn get(self) -> usize {
52        self.0.get() as usize
53    }
54
55    /// Increments the transaction id by 1.
56    ///
57    /// # Panics
58    ///
59    /// Panics if the resulting value would equal `u32::MAX`.
60    #[inline]
61    pub const fn increment(&mut self) {
62        self.0 = match NonMaxU32::new(self.0.get() + 1) {
63            Some(id) => id,
64            None => panic!("transaction id overflow"),
65        };
66    }
67}
68
69/// The main account type used inside Revm. It is stored inside Journal and contains all the information about the account.
70///
71/// Other than standard Account information it contains its status that can be both cold and warm
72/// additional to that it contains BAL that is used to load data for this particular account.
73///
74/// On loading from database:
75///     * If CompiledBal is present, load values from BAL into Account (Assume account has read data from database)
76///     * In case of parallel execution, AccountInfo would be same over all parallel executions.
77///     * Maybe use transaction_id as a way to notify user that this is obsolete data.
78///     * Database needs to load account and tie to with BAL writes
79/// If CompiledBal is not present, use loaded values
80///     * Account is already up to date (uses present flow).
81#[derive(Debug, Clone, Eq, Default)]
82#[cfg_attr(feature = "serde", derive(serde::Serialize))]
83pub struct Account {
84    /// Balance, nonce, and code
85    pub info: AccountInfo,
86    /// Transaction id, used to track when account was touched/loaded into journal.
87    pub transaction_id: TransactionId,
88    /// Storage cache
89    pub storage: EvmStorage,
90    /// Account status flags
91    pub status: AccountStatus,
92
93    /// Original account info used by BAL, changed only on cold load by BAL.
94    /// `None` means `Default::default()`, to avoid allocations.
95    original_info: Option<Box<AccountInfo>>,
96}
97
98impl PartialEq for Account {
99    #[inline]
100    fn eq(&self, other: &Self) -> bool {
101        self.info == other.info
102            && self.transaction_id == other.transaction_id
103            && self.storage == other.storage
104            && self.status == other.status
105            && self.original_info() == other.original_info()
106    }
107}
108
109impl Account {
110    /// Creates new account and mark it as non existing.
111    #[inline]
112    pub fn new_not_existing(transaction_id: TransactionId) -> Self {
113        Self {
114            transaction_id,
115            status: AccountStatus::LoadedAsNotExisting,
116            ..Default::default()
117        }
118    }
119
120    /// Make changes to the caller account.
121    ///
122    /// It marks the account as touched, changes the balance and bumps the nonce if `is_call` is true.
123    ///
124    /// Returns the old balance.
125    #[inline]
126    pub fn caller_initial_modification(&mut self, new_balance: U256, is_call: bool) -> U256 {
127        // Touch account so we know it is changed.
128        self.mark_touch();
129
130        if is_call {
131            // Nonce is already checked
132            self.info.nonce = self.info.nonce.saturating_add(1);
133        }
134
135        core::mem::replace(&mut self.info.balance, new_balance)
136    }
137
138    /// Checks if account is empty and check if empty state before spurious dragon hardfork.
139    #[inline]
140    pub fn state_clear_aware_is_empty(&self, spec: SpecId) -> bool {
141        if SpecId::is_enabled_in(spec, SpecId::SPURIOUS_DRAGON) {
142            self.is_empty()
143        } else {
144            self.is_loaded_as_not_existing_not_touched()
145        }
146    }
147
148    /// Returns the original account info.
149    #[inline]
150    pub fn original_info(&self) -> AccountInfo {
151        self.original_info.as_deref().cloned().unwrap_or_default()
152    }
153
154    /// Returns a mutable reference to the original account info.
155    #[inline]
156    pub fn original_info_mut(&mut self) -> &mut AccountInfo {
157        self.original_info.get_or_insert_default()
158    }
159
160    /// Clones the current info into the original info.
161    pub fn set_current_info_as_original(&mut self) {
162        if self.original_info.is_none() && self.info.is_default() {
163            return;
164        }
165        self.original_info
166            .get_or_insert_default()
167            .as_mut()
168            .clone_from(&self.info);
169    }
170
171    /// Marks the account as self destructed.
172    #[inline]
173    pub fn mark_selfdestruct(&mut self) {
174        self.status |= AccountStatus::SelfDestructed;
175    }
176
177    /// Unmarks the account as self destructed.
178    #[inline]
179    pub fn unmark_selfdestruct(&mut self) {
180        self.status -= AccountStatus::SelfDestructed;
181    }
182
183    /// Is account marked for self destruct.
184    #[inline]
185    pub const fn is_selfdestructed(&self) -> bool {
186        self.status.contains(AccountStatus::SelfDestructed)
187    }
188
189    /// Marks the account as touched
190    #[inline]
191    pub fn mark_touch(&mut self) {
192        self.status |= AccountStatus::Touched;
193    }
194
195    /// Unmarks the touch flag.
196    #[inline]
197    pub fn unmark_touch(&mut self) {
198        self.status -= AccountStatus::Touched;
199    }
200
201    /// If account status is marked as touched.
202    #[inline]
203    pub const fn is_touched(&self) -> bool {
204        self.status.contains(AccountStatus::Touched)
205    }
206
207    /// Returns true if account info was changed.
208    #[inline]
209    pub fn is_changed(&self) -> bool {
210        self.original_info.as_deref().map_or_else(
211            || !self.info.is_default(),
212            |original| self.info != *original,
213        )
214    }
215
216    /// Marks the account as newly created.
217    #[inline]
218    pub fn mark_created(&mut self) {
219        self.status |= AccountStatus::Created;
220    }
221
222    /// Unmarks the created flag.
223    #[inline]
224    pub fn unmark_created(&mut self) {
225        self.status -= AccountStatus::Created;
226    }
227
228    /// Marks the account as cold.
229    #[inline]
230    pub fn mark_cold(&mut self) {
231        self.status |= AccountStatus::Cold;
232    }
233
234    /// Is account warm for given transaction id.
235    #[inline]
236    pub const fn is_cold_transaction_id(&self, transaction_id: TransactionId) -> bool {
237        self.transaction_id.get() != transaction_id.get()
238            || self.status.contains(AccountStatus::Cold)
239    }
240
241    /// Marks the account as warm and return true if it was previously cold.
242    #[inline]
243    pub fn mark_warm_with_transaction_id(&mut self, transaction_id: TransactionId) -> bool {
244        let is_cold = self.is_cold_transaction_id(transaction_id);
245        self.status -= AccountStatus::Cold;
246        self.transaction_id = transaction_id;
247        is_cold
248    }
249
250    /// Is account locally created
251    #[inline]
252    pub const fn is_created_locally(&self) -> bool {
253        self.status.contains(AccountStatus::CreatedLocal)
254    }
255
256    /// Is account locally selfdestructed
257    #[inline]
258    pub const fn is_selfdestructed_locally(&self) -> bool {
259        self.status.contains(AccountStatus::SelfDestructedLocal)
260    }
261
262    /// Selfdestruct the account by clearing its storage and resetting its account info
263    #[inline]
264    pub fn selfdestruct(&mut self) {
265        self.storage.clear();
266        self.info = AccountInfo::default();
267    }
268
269    /// Mark account as locally created and mark global created flag.
270    ///
271    /// Returns true if it is created globally for first time.
272    #[inline]
273    pub fn mark_created_locally(&mut self) -> bool {
274        self.mark_local_and_global(AccountStatus::CreatedLocal, AccountStatus::Created)
275    }
276
277    /// Unmark account as locally created
278    #[inline]
279    pub fn unmark_created_locally(&mut self) {
280        self.status -= AccountStatus::CreatedLocal;
281    }
282
283    /// Mark account as locally and globally selfdestructed
284    #[inline]
285    pub fn mark_selfdestructed_locally(&mut self) -> bool {
286        self.mark_local_and_global(
287            AccountStatus::SelfDestructedLocal,
288            AccountStatus::SelfDestructed,
289        )
290    }
291
292    #[inline]
293    fn mark_local_and_global(
294        &mut self,
295        local_flag: AccountStatus,
296        global_flag: AccountStatus,
297    ) -> bool {
298        self.status |= local_flag;
299        let is_global_first_time = !self.status.contains(global_flag);
300        self.status |= global_flag;
301        is_global_first_time
302    }
303
304    /// Unmark account as locally selfdestructed
305    #[inline]
306    pub fn unmark_selfdestructed_locally(&mut self) {
307        self.status -= AccountStatus::SelfDestructedLocal;
308    }
309
310    /// Is account loaded as not existing from database.
311    ///
312    /// This is needed for pre spurious dragon hardforks where
313    /// existing and empty were two separate states.
314    pub const fn is_loaded_as_not_existing(&self) -> bool {
315        self.status.contains(AccountStatus::LoadedAsNotExisting)
316    }
317
318    /// Is account loaded as not existing from database and not touched.
319    pub const fn is_loaded_as_not_existing_not_touched(&self) -> bool {
320        self.is_loaded_as_not_existing() && !self.is_touched()
321    }
322
323    /// Is account newly created in this transaction.
324    pub const fn is_created(&self) -> bool {
325        self.status.contains(AccountStatus::Created)
326    }
327
328    /// Is account empty, check if nonce and balance are zero and code is empty.
329    pub fn is_empty(&self) -> bool {
330        self.info.is_empty()
331    }
332
333    /// Returns an iterator over the storage slots that have been changed.
334    ///
335    /// See also [EvmStorageSlot::is_changed].
336    pub fn changed_storage_slots(&self) -> impl Iterator<Item = (&StorageKey, &EvmStorageSlot)> {
337        self.storage.iter().filter(|(_, slot)| slot.is_changed())
338    }
339
340    /// Sets account info and returns self for method chaining.
341    pub fn with_info(mut self, info: AccountInfo) -> Self {
342        self.info = info;
343        self
344    }
345
346    /// Populates storage from an iterator of storage slots and returns self for method chaining.
347    pub fn with_storage<I>(mut self, storage_iter: I) -> Self
348    where
349        I: Iterator<Item = (StorageKey, EvmStorageSlot)>,
350    {
351        for (key, slot) in storage_iter {
352            self.storage.insert(key, slot);
353        }
354        self
355    }
356
357    /// Marks the account as self destructed and returns self for method chaining.
358    pub fn with_selfdestruct_mark(mut self) -> Self {
359        self.mark_selfdestruct();
360        self
361    }
362
363    /// Marks the account as touched and returns self for method chaining.
364    pub fn with_touched_mark(mut self) -> Self {
365        self.mark_touch();
366        self
367    }
368
369    /// Marks the account as newly created and returns self for method chaining.
370    pub fn with_created_mark(mut self) -> Self {
371        self.mark_created();
372        self
373    }
374
375    /// Marks the account as cold and returns self for method chaining.
376    pub fn with_cold_mark(mut self) -> Self {
377        self.mark_cold();
378        self
379    }
380
381    /// Marks the account as warm (not cold) and returns self for method chaining.
382    /// Also returns whether the account was previously cold.
383    pub fn with_warm_mark(mut self, transaction_id: TransactionId) -> (Self, bool) {
384        let was_cold = self.mark_warm_with_transaction_id(transaction_id);
385        (self, was_cold)
386    }
387
388    /// Variant of with_warm_mark that doesn't return the previous state.
389    pub fn with_warm(mut self, transaction_id: TransactionId) -> Self {
390        self.mark_warm_with_transaction_id(transaction_id);
391        self
392    }
393}
394
395impl From<AccountInfo> for Account {
396    fn from(info: AccountInfo) -> Self {
397        let original_info = if info.is_default() {
398            None
399        } else {
400            Some(Box::new(info.clone()))
401        };
402        Self {
403            info,
404            original_info,
405            transaction_id: TransactionId::ZERO,
406            storage: HashMap::default(),
407            status: AccountStatus::empty(),
408        }
409    }
410}
411
412#[cfg(feature = "serde")]
413mod serde_impl {
414    use super::*;
415    use serde::Deserialize;
416
417    /// Distinguishes missing field (old format) from explicit `null` (new format).
418    #[derive(Default)]
419    enum MaybeOriginalInfo {
420        /// Field was missing from JSON (old format).
421        #[default]
422        Missing,
423        /// Present in JSON: `null` means default, `Some` is the value.
424        Present(Option<AccountInfo>),
425    }
426
427    impl<'de> Deserialize<'de> for MaybeOriginalInfo {
428        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
429        where
430            D: serde::Deserializer<'de>,
431        {
432            Option::<AccountInfo>::deserialize(deserializer).map(MaybeOriginalInfo::Present)
433        }
434    }
435
436    #[derive(Deserialize)]
437    // Field order must match `Account`'s declaration order: the derived `Serialize` emits
438    // fields positionally, and non-self-describing formats (bincode, postcard) replay them in
439    // this struct's declared order — only self-describing formats match fields by name.
440    struct AccountSerde {
441        info: AccountInfo,
442        transaction_id: TransactionId,
443        storage: EvmStorage,
444        status: AccountStatus,
445        #[serde(default)]
446        original_info: MaybeOriginalInfo,
447    }
448
449    impl<'de> Deserialize<'de> for super::Account {
450        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
451        where
452            D: serde::Deserializer<'de>,
453        {
454            let AccountSerde {
455                info,
456                original_info,
457                storage,
458                transaction_id,
459                status,
460            } = Deserialize::deserialize(deserializer)?;
461
462            let original_info = match original_info {
463                // Old format: field missing → use info as original.
464                MaybeOriginalInfo::Missing => Some(Box::new(info.clone())),
465                // New format: null → None (default).
466                MaybeOriginalInfo::Present(None) => None,
467                // New format: explicit value.
468                MaybeOriginalInfo::Present(Some(oi)) => Some(Box::new(oi)),
469            };
470
471            Ok(Account {
472                info,
473                original_info,
474                storage,
475                transaction_id,
476                status,
477            })
478        }
479    }
480}
481
482// The `bitflags!` macro generates `struct`s that manage a set of flags.
483bitflags! {
484    /// Account status flags. Generated by bitflags crate.
485    ///
486    /// With multi transaction feature there is a need to have both global and local fields.
487    /// Global across multiple transaction and local across one transaction execution.
488    ///
489    /// Empty state without any flags set represent account that is loaded from db but not interacted with.
490    ///
491    /// `Touched` flag is used by database to check if account is potentially changed in some way.
492    /// Additionally, after EIP-161 touch on empty-existing account would remove this account from state
493    /// after transaction execution ends. Touch can span across multiple transactions as it is needed
494    /// to be marked only once so it is safe to have only one global flag.
495    /// Only first touch have different behaviour from others, and touch in first transaction will invalidate
496    /// touch functionality in next transactions.
497    ///
498    /// `Created` flag is used to mark account as newly created in this transaction. This is used for optimization
499    /// where if this flag is set we will not access database to fetch storage values.
500    ///
501    /// `CreatedLocal` flag is used after cancun to enable selfdestruct cleanup if account is created in same transaction.
502    ///
503    /// `Selfdestructed` flag is used to mark account as selfdestructed. On multiple calls this flag is preserved
504    /// and on revert will stay selfdestructed.
505    ///
506    /// `SelfdestructLocal` is needed to award refund on first selfdestruct call. This flag is cleared on account loading.
507    /// Over multiple transaction account can be selfdestructed in one tx, created in second tx and selfdestructed again in
508    /// third tx.
509    /// Additionally if account is loaded in second tx, storage and account that was destroyed in first tx needs to be cleared.
510    ///
511    /// `LoadedAsNotExisting` is used to mark account as loaded from database but with `balance == 0 && nonce == 0 && code = 0x`.
512    /// This flag is fine to span across multiple transactions as it interucts with `Touched` flag this is used in global scope.
513    ///
514    /// `CreatedLocal`, `SelfdestructedLocal` and `Cold` flags are reset on first account loading of local scope.
515    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
516    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
517    #[cfg_attr(feature = "serde", serde(transparent))]
518    pub struct AccountStatus: u8 {
519        /// When account is newly created we will not access database
520        /// to fetch storage values.
521        const Created = 0b00000001;
522        /// When accounts gets loaded this flag is set to false. Create will always be true if CreatedLocal is true.
523        const CreatedLocal = 0b10000000;
524        /// If account is marked for self destruction.
525        const SelfDestructed = 0b00000010;
526        /// If account is marked for self destruction.
527        const SelfDestructedLocal = 0b01000000;
528        /// Only when account is marked as touched we will save it to database.
529        /// Additionally first touch on empty existing account (After EIP-161) will mark it
530        /// for removal from state after transaction execution.
531        const Touched = 0b00000100;
532        /// used only for pre spurious dragon hardforks where existing and empty were two separate states.
533        /// it became same state after EIP-161: State trie clearing
534        const LoadedAsNotExisting = 0b00001000;
535        /// used to mark account as cold.
536        /// It is used only in local scope and it is reset on account loading.
537        const Cold = 0b00010000;
538    }
539}
540
541impl AccountStatus {
542    /// Returns true if the account status is touched.
543    #[inline]
544    pub const fn is_touched(&self) -> bool {
545        self.contains(AccountStatus::Touched)
546    }
547}
548
549impl Default for AccountStatus {
550    fn default() -> Self {
551        AccountStatus::empty()
552    }
553}
554
555/// This type keeps track of the current value of a storage slot.
556#[derive(Debug, Clone, Default, PartialEq, Eq)]
557#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
558pub struct EvmStorageSlot {
559    /// Original value of the storage slot
560    pub original_value: StorageValue,
561    /// Present value of the storage slot
562    pub present_value: StorageValue,
563    /// Transaction id, used to track when storage slot was made warm.
564    pub transaction_id: TransactionId,
565    /// Represents if the storage slot is cold
566    pub is_cold: bool,
567}
568
569impl EvmStorageSlot {
570    /// Creates a new _unchanged_ `EvmStorageSlot` for the given value.
571    pub const fn new(original: StorageValue, transaction_id: TransactionId) -> Self {
572        Self {
573            original_value: original,
574            present_value: original,
575            transaction_id,
576            is_cold: false,
577        }
578    }
579
580    /// Creates a new _changed_ `EvmStorageSlot`.
581    pub const fn new_changed(
582        original_value: StorageValue,
583        present_value: StorageValue,
584        transaction_id: TransactionId,
585    ) -> Self {
586        Self {
587            original_value,
588            present_value,
589            transaction_id,
590            is_cold: false,
591        }
592    }
593    /// Returns true if the present value differs from the original value.
594    pub fn is_changed(&self) -> bool {
595        self.original_value != self.present_value
596    }
597
598    /// Returns the original value of the storage slot.
599    #[inline]
600    pub const fn original_value(&self) -> StorageValue {
601        self.original_value
602    }
603
604    /// Returns the current value of the storage slot.
605    #[inline]
606    pub const fn present_value(&self) -> StorageValue {
607        self.present_value
608    }
609
610    /// Marks the storage slot as cold. Does not change transaction_id.
611    #[inline]
612    pub const fn mark_cold(&mut self) {
613        self.is_cold = true;
614    }
615
616    /// Is storage slot cold for given transaction id.
617    #[inline]
618    pub const fn is_cold_transaction_id(&self, transaction_id: TransactionId) -> bool {
619        self.transaction_id.get() != transaction_id.get() || self.is_cold
620    }
621
622    /// Marks the storage slot as warm and sets transaction_id to the given value
623    ///
624    ///
625    /// Returns false if old transition_id is different from given id or in case they are same return `Self::is_cold` value.
626    #[inline]
627    pub const fn mark_warm_with_transaction_id(&mut self, transaction_id: TransactionId) -> bool {
628        let is_cold = self.is_cold_transaction_id(transaction_id);
629        // Re-baseline the EIP-2200 `original_value` only when the slot belongs to a *previous*
630        // transaction (transaction id mismatch). `is_cold` also covers a slot flagged cold
631        // within the same transaction (e.g. a reverted `StorageWarmed` entry, or an explicit
632        // `mark_cold`); those must keep the original value captured at the start of this
633        // transaction. The committed/original value is tx-scoped, not access-list-scoped.
634        if self.transaction_id.get() != transaction_id.get() {
635            self.original_value = self.present_value;
636        }
637        self.transaction_id = transaction_id;
638        self.is_cold = false;
639        is_cold
640    }
641}
642
643#[cfg(test)]
644mod tests {
645    use super::*;
646    use crate::EvmStorageSlot;
647    use primitives::{StorageKey, KECCAK_EMPTY, U256};
648
649    #[test]
650    fn account_is_empty_balance() {
651        let mut account = Account::default();
652        assert!(account.is_empty());
653
654        account.info.balance = U256::from(1);
655        assert!(!account.is_empty());
656
657        account.info.balance = U256::ZERO;
658        assert!(account.is_empty());
659    }
660
661    #[test]
662    fn account_is_empty_nonce() {
663        let mut account = Account::default();
664        assert!(account.is_empty());
665
666        account.info.nonce = 1;
667        assert!(!account.is_empty());
668
669        account.info.nonce = 0;
670        assert!(account.is_empty());
671    }
672
673    #[test]
674    fn account_is_empty_code_hash() {
675        let mut account = Account::default();
676        assert!(account.is_empty());
677
678        account.info.code_hash = [1; 32].into();
679        assert!(!account.is_empty());
680
681        account.info.code_hash = [0; 32].into();
682        assert!(account.is_empty());
683
684        account.info.code_hash = KECCAK_EMPTY;
685        assert!(account.is_empty());
686    }
687
688    #[test]
689    fn account_state() {
690        let mut account = Account::default();
691
692        assert!(!account.is_touched());
693        assert!(!account.is_selfdestructed());
694
695        account.mark_touch();
696        assert!(account.is_touched());
697        assert!(!account.is_selfdestructed());
698
699        account.mark_selfdestruct();
700        assert!(account.is_touched());
701        assert!(account.is_selfdestructed());
702
703        account.unmark_selfdestruct();
704        assert!(account.is_touched());
705        assert!(!account.is_selfdestructed());
706    }
707
708    #[test]
709    fn account_is_cold() {
710        let mut account = Account::default();
711
712        // Account is not cold by default
713        assert!(!account.status.contains(crate::AccountStatus::Cold));
714
715        // When marking warm account as warm again, it should return false
716        assert!(!account.mark_warm_with_transaction_id(TransactionId::ZERO));
717
718        // Mark account as cold
719        account.mark_cold();
720
721        // Account is cold
722        assert!(account.status.contains(crate::AccountStatus::Cold));
723
724        // When marking cold account as warm, it should return true
725        assert!(account.mark_warm_with_transaction_id(TransactionId::ZERO));
726    }
727
728    #[test]
729    fn test_account_with_info() {
730        let info = AccountInfo::default();
731        let account = Account::default().with_info(info.clone());
732
733        assert_eq!(account.info, info);
734        assert_eq!(account.storage, HashMap::default());
735        assert_eq!(account.status, AccountStatus::empty());
736    }
737
738    #[test]
739    fn test_account_with_storage() {
740        let mut storage = HashMap::<StorageKey, EvmStorageSlot>::default();
741        let key1 = StorageKey::from(1);
742        let key2 = StorageKey::from(2);
743        let slot1 = EvmStorageSlot::new(StorageValue::from(10), TransactionId::ZERO);
744        let slot2 = EvmStorageSlot::new(StorageValue::from(20), TransactionId::ZERO);
745
746        storage.insert(key1, slot1.clone());
747        storage.insert(key2, slot2.clone());
748
749        let account = Account::default().with_storage(storage.clone().into_iter());
750
751        assert_eq!(account.storage.len(), 2);
752        assert_eq!(account.storage.get(&key1), Some(&slot1));
753        assert_eq!(account.storage.get(&key2), Some(&slot2));
754    }
755
756    #[test]
757    fn test_account_with_selfdestruct_mark() {
758        let account = Account::default().with_selfdestruct_mark();
759
760        assert!(account.is_selfdestructed());
761        assert!(!account.is_touched());
762        assert!(!account.is_created());
763    }
764
765    #[test]
766    #[cfg(feature = "serde")]
767    fn test_account_serialize_deserialize() {
768        let account = Account::default().with_selfdestruct_mark();
769        let serialized = serde_json::to_string(&account).unwrap();
770        let deserialized: Account = serde_json::from_str(&serialized).unwrap();
771        assert_eq!(account, deserialized);
772    }
773
774    #[test]
775    #[cfg(feature = "serde")]
776    fn test_account_binary_round_trip() {
777        // Positional formats carry no field names, so a `Serialize`/`Deserialize` field-order
778        // mismatch is invisible to the JSON tests above and a default account round-trips by
779        // accident. Populate every field with a distinct non-default value, `original_info`
780        // differing from `info`, to pin each field's position.
781        let mut account = Account::from(AccountInfo {
782            nonce: 5,
783            ..AccountInfo::default()
784        });
785        account.info.nonce = 7;
786        account.transaction_id = TransactionId::new(3).unwrap();
787        account.storage.insert(
788            StorageKey::from(42u64),
789            EvmStorageSlot {
790                original_value: U256::from(1u64),
791                present_value: U256::from(2u64),
792                transaction_id: account.transaction_id,
793                is_cold: false,
794            },
795        );
796        account.status = AccountStatus::Touched;
797
798        // MessagePack delimits structs, so omitted account extensions cannot consume later fields.
799        #[cfg(feature = "account-ext")]
800        let decoded: Account =
801            rmp_serde::from_slice(&rmp_serde::to_vec(&account).unwrap()).unwrap();
802        #[cfg(not(feature = "account-ext"))]
803        let decoded: Account =
804            postcard::from_bytes(&postcard::to_allocvec(&account).unwrap()).unwrap();
805
806        assert_eq!(account, decoded);
807    }
808
809    #[test]
810    #[cfg(feature = "serde")]
811    fn test_account_original_info_none_roundtrip() {
812        let account = Account::new_not_existing(TransactionId::new(2).unwrap());
813        assert!(account.original_info.is_none());
814        let serialized = serde_json::to_string(&account).unwrap();
815        let deserialized: Account = serde_json::from_str(&serialized).unwrap();
816        assert!(deserialized.original_info.is_none());
817        assert_eq!(account, deserialized);
818    }
819
820    #[test]
821    #[cfg(feature = "serde")]
822    fn test_account_deserialize_original_info_missing_null_present() {
823        let code = r#"{"LegacyAnalyzed":{"bytecode":"0x00","original_len":0,"jump_table":{"order":"bitvec::order::Lsb0","head":{"width":8,"index":0},"bits":0,"data":[]}}}"#;
824        let info = format!(
825            r#"{{"balance":"0x2386f26fc10000","nonce":1,"code_hash":"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470","code":{code}}}"#
826        );
827
828        // Missing field (old format): original_info = Some(info.clone()).
829        let json =
830            format!(r#"{{"info":{info},"transaction_id":0,"storage":{{}},"status":"Touched"}}"#);
831        let acct: Account = serde_json::from_str(&json).unwrap();
832        assert!(acct.original_info.is_some());
833        assert_eq!(acct.original_info(), acct.info);
834
835        // Null (new format): original_info = None (default).
836        let json = format!(
837            r#"{{"info":{info},"original_info":null,"transaction_id":0,"storage":{{}},"status":"Touched"}}"#
838        );
839        let acct: Account = serde_json::from_str(&json).unwrap();
840        assert!(acct.original_info.is_none());
841
842        // Present value.
843        let original = r#"{"balance":"0x0","nonce":0,"code_hash":"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470","code":null}"#;
844        let json = format!(
845            r#"{{"info":{info},"original_info":{original},"transaction_id":0,"storage":{{}},"status":"Touched"}}"#
846        );
847        let acct: Account = serde_json::from_str(&json).unwrap();
848        assert!(acct.original_info.is_some());
849        assert_eq!(acct.original_info().nonce, 0);
850        assert_eq!(acct.original_info().balance, U256::ZERO);
851    }
852
853    #[test]
854    fn test_account_with_touched_mark() {
855        let account = Account::default().with_touched_mark();
856
857        assert!(!account.is_selfdestructed());
858        assert!(account.is_touched());
859        assert!(!account.is_created());
860    }
861
862    #[test]
863    fn test_account_with_created_mark() {
864        let account = Account::default().with_created_mark();
865
866        assert!(!account.is_selfdestructed());
867        assert!(!account.is_touched());
868        assert!(account.is_created());
869    }
870
871    #[test]
872    fn test_account_with_cold_mark() {
873        let account = Account::default().with_cold_mark();
874
875        assert!(account.status.contains(AccountStatus::Cold));
876    }
877
878    #[test]
879    fn test_storage_mark_warm_with_transaction_id() {
880        let tx_zero = TransactionId::ZERO;
881        let tx_one = TransactionId::new(1).unwrap();
882        let mut slot = EvmStorageSlot::new(U256::ZERO, tx_zero);
883        slot.is_cold = true;
884        slot.transaction_id = tx_zero;
885        assert!(slot.mark_warm_with_transaction_id(tx_one));
886
887        slot.is_cold = false;
888        slot.transaction_id = tx_zero;
889        assert!(slot.mark_warm_with_transaction_id(tx_one));
890
891        slot.is_cold = true;
892        slot.transaction_id = tx_one;
893        assert!(slot.mark_warm_with_transaction_id(tx_one));
894
895        slot.is_cold = false;
896        slot.transaction_id = tx_one;
897        // Only if transaction id is same and is_cold is false, return false.
898        assert!(!slot.mark_warm_with_transaction_id(tx_one));
899    }
900
901    #[test]
902    fn test_account_with_warm_mark() {
903        // Start with a cold account
904        let cold_account = Account::default().with_cold_mark();
905        assert!(cold_account.status.contains(AccountStatus::Cold));
906
907        // Use with_warm_mark to warm it
908        let (warm_account, was_cold) = cold_account.with_warm_mark(TransactionId::ZERO);
909
910        // Check that it's now warm and previously was cold
911        assert!(!warm_account.status.contains(AccountStatus::Cold));
912        assert!(was_cold);
913
914        // Try with an already warm account
915        let (still_warm_account, was_cold) = warm_account.with_warm_mark(TransactionId::ZERO);
916        assert!(!still_warm_account.status.contains(AccountStatus::Cold));
917        assert!(!was_cold);
918    }
919
920    #[test]
921    fn test_account_with_warm() {
922        // Start with a cold account
923        let cold_account = Account::default().with_cold_mark();
924        assert!(cold_account.status.contains(AccountStatus::Cold));
925
926        // Use with_warm to warm it
927        let warm_account = cold_account.with_warm(TransactionId::ZERO);
928
929        // Check that it's now warm
930        assert!(!warm_account.status.contains(AccountStatus::Cold));
931    }
932
933    #[test]
934    fn test_account_builder_chaining() {
935        let info = AccountInfo {
936            nonce: 5,
937            ..AccountInfo::default()
938        };
939
940        let slot_key = StorageKey::from(42);
941        let slot_value = EvmStorageSlot::new(StorageValue::from(123), TransactionId::ZERO);
942        let mut storage = HashMap::<StorageKey, EvmStorageSlot>::default();
943        storage.insert(slot_key, slot_value.clone());
944
945        // Chain multiple builder methods together
946        let account = Account::default()
947            .with_info(info.clone())
948            .with_storage(storage.into_iter())
949            .with_created_mark()
950            .with_touched_mark()
951            .with_cold_mark()
952            .with_warm(TransactionId::ZERO);
953
954        // Verify all modifications were applied
955        assert_eq!(account.info, info);
956        assert_eq!(account.storage.get(&slot_key), Some(&slot_value));
957        assert!(account.is_created());
958        assert!(account.is_touched());
959        assert!(!account.status.contains(AccountStatus::Cold));
960    }
961
962    #[test]
963    fn test_account_is_cold_transaction_id() {
964        let tx_zero = TransactionId::ZERO;
965        let tx_one = TransactionId::new(1).unwrap();
966        let mut account = Account::default();
967        // only case where it is warm.
968        assert!(!account.is_cold_transaction_id(tx_zero));
969
970        // all other cases are cold
971        assert!(account.is_cold_transaction_id(tx_one));
972        account.mark_cold();
973        assert!(account.is_cold_transaction_id(tx_zero));
974        assert!(account.is_cold_transaction_id(tx_one));
975    }
976}