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