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