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