revm_state/
lib.rs

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