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