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