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