Skip to main content

revm_context_interface/journaled_state/
entry.rs

1//! Contains the journal entry trait and implementations.
2//!
3//! Journal entries are used to track changes to the state and are used to revert it.
4//!
5//! They are created when there is change to the state from loading (making it warm), changes to the balance,
6//! or removal of the storage slot. Check [`JournalEntryTr`] for more details.
7
8use primitives::{Address, StorageKey, StorageValue, KECCAK_EMPTY, PRECOMPILE3, U256};
9use state::{EvmState, TransientStorage};
10
11/// Trait for tracking and reverting state changes in the EVM.
12/// Journal entry contains information about state changes that can be reverted.
13pub trait JournalEntryTr {
14    /// Creates a journal entry for when an account is accessed and marked as "warm" for gas metering
15    fn account_warmed(address: Address) -> Self;
16
17    /// Creates a journal entry for when an account is destroyed via SELFDESTRUCT
18    /// Records the target address that received the destroyed account's balance,
19    /// whether the account was already destroyed, and its balance before destruction
20    /// on revert, the balance is transferred back to the original account
21    fn account_destroyed(
22        address: Address,
23        target: Address,
24        destroyed_status: SelfdestructionRevertStatus,
25        had_balance: U256,
26    ) -> Self;
27
28    /// Creates a journal entry for when an account is "touched" - accessed in a way that may require saving it.
29    /// If account is empty and touch it will be removed from the state (EIP-161 state clear EIP)
30    fn account_touched(address: Address) -> Self;
31
32    /// Creates a journal entry for a balance transfer between accounts
33    fn balance_transfer(from: Address, to: Address, balance: U256) -> Self;
34
35    /// Creates a journal entry for when an account's balance is changed.
36    fn balance_changed(address: Address, old_balance: U256) -> Self;
37
38    /// Creates a journal entry for when an account's nonce is changed.
39    fn nonce_changed(address: Address, previous_nonce: u64) -> Self;
40
41    /// Creates a journal entry for when an account's nonce is bumped.
42    fn nonce_bumped(address: Address) -> Self;
43
44    /// Creates a journal entry for when a new account is created
45    fn account_created(address: Address, is_created_globally: bool) -> Self;
46
47    /// Creates a journal entry for when a storage slot is modified
48    /// Records the previous value for reverting
49    fn storage_changed(address: Address, key: StorageKey, had_value: StorageValue) -> Self;
50
51    /// Creates a journal entry for when a storage slot is accessed and marked as "warm" for gas metering
52    /// This is called with SLOAD opcode.
53    fn storage_warmed(address: Address, key: StorageKey) -> Self;
54
55    /// Creates a journal entry for when a transient storage slot is modified (EIP-1153)
56    /// Records the previous value for reverting
57    fn transient_storage_changed(
58        address: Address,
59        key: StorageKey,
60        had_value: StorageValue,
61    ) -> Self;
62
63    /// Creates a journal entry for when an account's code is modified
64    fn code_changed(address: Address) -> Self;
65
66    /// Reverts the state change recorded by this journal entry
67    ///
68    /// More information on what is reverted can be found in [`JournalEntry`] enum.
69    ///
70    /// If transient storage is not provided, revert on transient storage will not be performed.
71    /// This is used when we revert whole transaction and know that transient storage is empty.
72    ///
73    /// # Notes
74    ///
75    /// The spurious dragon flag is used to skip revertion 0x000..0003 precompile. This
76    /// Behaviour is special and it caused by bug in Geth and Parity that is explained in [PR#716](https://github.com/ethereum/EIPs/issues/716).
77    ///
78    /// From yellow paper:
79    /// ```text
80    /// K.1. Deletion of an Account Despite Out-of-gas. At block 2675119, in the transaction 0xcf416c536ec1a19ed1fb89e
81    /// 4ec7ffb3cf73aa413b3aa9b77d60e4fd81a4296ba, an account at address 0x03 was called and an out-of-gas occurred during
82    /// the call. Against the equation (209), this added 0x03 in the set of touched addresses, and this transaction turned σ[0x03]
83    /// into ∅.
84    /// ```
85    fn revert(
86        self,
87        state: &mut EvmState,
88        transient_storage: Option<&mut TransientStorage>,
89        is_spurious_dragon_enabled: bool,
90    );
91}
92
93/// Status of selfdestruction revert.
94///
95/// Global selfdestruction means that selfdestruct is called for first time in global scope.
96///
97/// Locally selfdesturction that selfdestruct is called for first time in one transaction scope.
98///
99/// Repeated selfdestruction means local selfdesturction was already called in one transaction scope.
100#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
101#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
102pub enum SelfdestructionRevertStatus {
103    /// Selfdestruct is called for first time in global scope.
104    GloballySelfdestroyed,
105    /// Selfdestruct is called for first time in one transaction scope.
106    LocallySelfdestroyed,
107    /// Selfdestruct is called again in one transaction scope.
108    RepeatedSelfdestruction,
109}
110
111/// Journal entries that are used to track changes to the state and are used to revert it.
112#[derive(Debug, Clone, PartialEq, Eq, Hash)]
113#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
114pub enum JournalEntry {
115    /// Used to mark account that is warm inside EVM in regard to EIP-2929 AccessList.
116    /// Action: We will add Account to state.
117    /// Revert: we will remove account from state.
118    AccountWarmed {
119        /// Address of warmed account.
120        address: Address,
121    },
122    /// Mark account to be destroyed and journal balance to be reverted
123    /// Action: Mark account and transfer the balance
124    /// Revert: Unmark the account and transfer balance back
125    AccountDestroyed {
126        /// Balance of account got transferred to target.
127        had_balance: U256,
128        /// Address of account to be destroyed.
129        address: Address,
130        /// Address of account that received the balance.
131        target: Address,
132        /// Status of selfdestruction revert.
133        destroyed_status: SelfdestructionRevertStatus,
134    },
135    /// Loading account does not mean that account will need to be added to MerkleTree (touched).
136    /// Only when account is called (to execute contract or transfer balance) only then account is made touched.
137    /// Action: Mark account touched
138    /// Revert: Unmark account touched
139    AccountTouched {
140        /// Address of account that is touched.
141        address: Address,
142    },
143    /// Balance changed
144    /// Action: Balance changed
145    /// Revert: Revert to previous balance
146    BalanceChange {
147        /// New balance of account.
148        old_balance: U256,
149        /// Address of account that had its balance changed.
150        address: Address,
151    },
152    /// Transfer balance between two accounts
153    /// Action: Transfer balance
154    /// Revert: Transfer balance back
155    BalanceTransfer {
156        /// Balance that is transferred.
157        balance: U256,
158        /// Address of account that sent the balance.
159        from: Address,
160        /// Address of account that received the balance.
161        to: Address,
162    },
163    /// Increment nonce
164    /// Action: Set nonce
165    /// Revert: Revert to previous nonce
166    NonceChange {
167        /// Address of account that had its nonce changed.
168        /// Nonce is incremented by one.
169        address: Address,
170        /// Previous nonce of account.
171        previous_nonce: u64,
172    },
173    /// Increment nonce
174    /// Action: Increment nonce by one
175    /// Revert: Decrement nonce by one
176    NonceBump {
177        /// Address of account that had its nonce changed.
178        /// Nonce is incremented by one.
179        address: Address,
180    },
181    /// Create account:
182    /// Actions: Mark account as created
183    /// Revert: Unmark account as created and reset nonce to zero.
184    AccountCreated {
185        /// Address of account that is created.
186        /// On revert, this account will be set to empty.
187        address: Address,
188        /// If account is created globally for first time.
189        is_created_globally: bool,
190    },
191    /// Entry used to track storage changes
192    /// Action: Storage change
193    /// Revert: Revert to previous value
194    StorageChanged {
195        /// Key of storage slot that is changed.
196        key: StorageKey,
197        /// Previous value of storage slot.
198        had_value: StorageValue,
199        /// Address of account that had its storage changed.
200        address: Address,
201    },
202    /// Entry used to track storage warming introduced by EIP-2929.
203    /// Action: Storage warmed
204    /// Revert: Revert to cold state
205    StorageWarmed {
206        /// Key of storage slot that is warmed.
207        key: StorageKey,
208        /// Address of account that had its storage warmed. By SLOAD or SSTORE opcode.
209        address: Address,
210    },
211    /// It is used to track an EIP-1153 transient storage change.
212    /// Action: Transient storage changed.
213    /// Revert: Revert to previous value.
214    TransientStorageChange {
215        /// Key of transient storage slot that is changed.
216        key: StorageKey,
217        /// Previous value of transient storage slot.
218        had_value: StorageValue,
219        /// Address of account that had its transient storage changed.
220        address: Address,
221    },
222    /// Code changed
223    /// Action: Account code changed
224    /// Revert: Revert to previous bytecode.
225    CodeChange {
226        /// Address of account that had its code changed.
227        address: Address,
228    },
229}
230
231impl JournalEntryTr for JournalEntry {
232    fn account_warmed(address: Address) -> Self {
233        JournalEntry::AccountWarmed { address }
234    }
235
236    fn account_destroyed(
237        address: Address,
238        target: Address,
239        destroyed_status: SelfdestructionRevertStatus,
240        had_balance: StorageValue,
241    ) -> Self {
242        JournalEntry::AccountDestroyed {
243            address,
244            target,
245            destroyed_status,
246            had_balance,
247        }
248    }
249
250    fn account_touched(address: Address) -> Self {
251        JournalEntry::AccountTouched { address }
252    }
253
254    fn balance_changed(address: Address, old_balance: U256) -> Self {
255        JournalEntry::BalanceChange {
256            address,
257            old_balance,
258        }
259    }
260
261    fn balance_transfer(from: Address, to: Address, balance: U256) -> Self {
262        JournalEntry::BalanceTransfer { from, to, balance }
263    }
264
265    fn account_created(address: Address, is_created_globally: bool) -> Self {
266        JournalEntry::AccountCreated {
267            address,
268            is_created_globally,
269        }
270    }
271
272    fn storage_changed(address: Address, key: StorageKey, had_value: StorageValue) -> Self {
273        JournalEntry::StorageChanged {
274            address,
275            key,
276            had_value,
277        }
278    }
279
280    fn nonce_changed(address: Address, previous_nonce: u64) -> Self {
281        JournalEntry::NonceChange {
282            address,
283            previous_nonce,
284        }
285    }
286
287    fn nonce_bumped(address: Address) -> Self {
288        JournalEntry::NonceBump { address }
289    }
290
291    fn storage_warmed(address: Address, key: StorageKey) -> Self {
292        JournalEntry::StorageWarmed { address, key }
293    }
294
295    fn transient_storage_changed(
296        address: Address,
297        key: StorageKey,
298        had_value: StorageValue,
299    ) -> Self {
300        JournalEntry::TransientStorageChange {
301            address,
302            key,
303            had_value,
304        }
305    }
306
307    fn code_changed(address: Address) -> Self {
308        JournalEntry::CodeChange { address }
309    }
310
311    fn revert(
312        self,
313        state: &mut EvmState,
314        transient_storage: Option<&mut TransientStorage>,
315        is_spurious_dragon_enabled: bool,
316    ) {
317        match self {
318            JournalEntry::AccountWarmed { address } => {
319                state.get_mut(&address).unwrap().mark_cold();
320            }
321            JournalEntry::AccountTouched { address } => {
322                if is_spurious_dragon_enabled && address == PRECOMPILE3 {
323                    return;
324                }
325                // remove touched status
326                state.get_mut(&address).unwrap().unmark_touch();
327            }
328            JournalEntry::AccountDestroyed {
329                address,
330                target,
331                destroyed_status,
332                had_balance,
333            } => {
334                let account = state.get_mut(&address).unwrap();
335                // set previous state of selfdestructed flag, as there could be multiple
336                // selfdestructs in one transaction.
337                match destroyed_status {
338                    SelfdestructionRevertStatus::GloballySelfdestroyed => {
339                        account.unmark_selfdestruct();
340                        account.unmark_selfdestructed_locally();
341                    }
342                    SelfdestructionRevertStatus::LocallySelfdestroyed => {
343                        account.unmark_selfdestructed_locally();
344                    }
345                    // do nothing on repeated selfdestruction
346                    SelfdestructionRevertStatus::RepeatedSelfdestruction => (),
347                }
348
349                account.info.balance += had_balance;
350
351                if address != target {
352                    let target = state.get_mut(&target).unwrap();
353                    target.info.balance -= had_balance;
354                }
355            }
356            JournalEntry::BalanceChange {
357                address,
358                old_balance,
359            } => {
360                let account = state.get_mut(&address).unwrap();
361                account.info.balance = old_balance;
362            }
363            JournalEntry::BalanceTransfer { from, to, balance } => {
364                // we don't need to check overflow and underflow when adding and subtracting the balance.
365                let from = state.get_mut(&from).unwrap();
366                from.info.balance += balance;
367                let to = state.get_mut(&to).unwrap();
368                to.info.balance -= balance;
369            }
370            JournalEntry::NonceChange {
371                address,
372                previous_nonce,
373            } => {
374                state.get_mut(&address).unwrap().info.nonce = previous_nonce;
375            }
376            JournalEntry::NonceBump { address } => {
377                let nonce = &mut state.get_mut(&address).unwrap().info.nonce;
378                *nonce = nonce.saturating_sub(1);
379            }
380            JournalEntry::AccountCreated {
381                address,
382                is_created_globally,
383            } => {
384                let account = &mut state.get_mut(&address).unwrap();
385                account.unmark_created_locally();
386                if is_created_globally {
387                    account.unmark_created();
388                }
389                // only account that have nonce == 0 can be created so it is safe to set it to 0.
390                account.info.nonce = 0;
391            }
392            JournalEntry::StorageWarmed { address, key } => {
393                state
394                    .get_mut(&address)
395                    .unwrap()
396                    .storage
397                    .get_mut(&key)
398                    .unwrap()
399                    .mark_cold();
400            }
401            JournalEntry::StorageChanged {
402                address,
403                key,
404                had_value,
405            } => {
406                state
407                    .get_mut(&address)
408                    .unwrap()
409                    .storage
410                    .get_mut(&key)
411                    .unwrap()
412                    .present_value = had_value;
413            }
414            JournalEntry::TransientStorageChange {
415                address,
416                key,
417                had_value,
418            } => {
419                let Some(transient_storage) = transient_storage else {
420                    return;
421                };
422                if had_value.is_zero() {
423                    // if previous value is zero, remove it
424                    transient_storage.remove_value(address, key);
425                } else {
426                    // if not zero, reinsert old value to transient storage.
427                    transient_storage.insert_value(address, key, had_value);
428                }
429            }
430            JournalEntry::CodeChange { address } => {
431                let acc = state.get_mut(&address).unwrap();
432                acc.info.code_hash = KECCAK_EMPTY;
433                acc.info.code = None;
434            }
435        }
436    }
437}