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