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