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