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}
230impl JournalEntryTr for JournalEntry {
231 fn account_warmed(address: Address) -> Self {
232 JournalEntry::AccountWarmed { address }
233 }
234
235 fn account_destroyed(
236 address: Address,
237 target: Address,
238 destroyed_status: SelfdestructionRevertStatus,
239 had_balance: StorageValue,
240 ) -> Self {
241 JournalEntry::AccountDestroyed {
242 address,
243 target,
244 destroyed_status,
245 had_balance,
246 }
247 }
248
249 fn account_touched(address: Address) -> Self {
250 JournalEntry::AccountTouched { address }
251 }
252
253 fn balance_changed(address: Address, old_balance: U256) -> Self {
254 JournalEntry::BalanceChange {
255 address,
256 old_balance,
257 }
258 }
259
260 fn balance_transfer(from: Address, to: Address, balance: U256) -> Self {
261 JournalEntry::BalanceTransfer { from, to, balance }
262 }
263
264 fn account_created(address: Address, is_created_globally: bool) -> Self {
265 JournalEntry::AccountCreated {
266 address,
267 is_created_globally,
268 }
269 }
270
271 fn storage_changed(address: Address, key: StorageKey, had_value: StorageValue) -> Self {
272 JournalEntry::StorageChanged {
273 address,
274 key,
275 had_value,
276 }
277 }
278
279 fn nonce_changed(address: Address, previous_nonce: u64) -> Self {
280 JournalEntry::NonceChange {
281 address,
282 previous_nonce,
283 }
284 }
285
286 fn nonce_bumped(address: Address) -> Self {
287 JournalEntry::NonceBump { address }
288 }
289
290 fn storage_warmed(address: Address, key: StorageKey) -> Self {
291 JournalEntry::StorageWarmed { address, key }
292 }
293
294 fn transient_storage_changed(
295 address: Address,
296 key: StorageKey,
297 had_value: StorageValue,
298 ) -> Self {
299 JournalEntry::TransientStorageChange {
300 address,
301 key,
302 had_value,
303 }
304 }
305
306 fn code_changed(address: Address) -> Self {
307 JournalEntry::CodeChange { address }
308 }
309
310 fn revert(
311 self,
312 state: &mut EvmState,
313 transient_storage: Option<&mut TransientStorage>,
314 is_spurious_dragon_enabled: bool,
315 ) {
316 match self {
317 JournalEntry::AccountWarmed { address } => {
318 state.get_mut(&address).unwrap().mark_cold();
319 }
320 JournalEntry::AccountTouched { address } => {
321 if is_spurious_dragon_enabled && address == PRECOMPILE3 {
322 return;
323 }
324 // remove touched status
325 state.get_mut(&address).unwrap().unmark_touch();
326 }
327 JournalEntry::AccountDestroyed {
328 address,
329 target,
330 destroyed_status,
331 had_balance,
332 } => {
333 let account = state.get_mut(&address).unwrap();
334 // set previous state of selfdestructed flag, as there could be multiple
335 // selfdestructs in one transaction.
336 match destroyed_status {
337 SelfdestructionRevertStatus::GloballySelfdestroyed => {
338 account.unmark_selfdestruct();
339 account.unmark_selfdestructed_locally();
340 }
341 SelfdestructionRevertStatus::LocallySelfdestroyed => {
342 account.unmark_selfdestructed_locally();
343 }
344 // do nothing on repeated selfdestruction
345 SelfdestructionRevertStatus::RepeatedSelfdestruction => (),
346 }
347
348 account.info.balance += had_balance;
349
350 if address != target {
351 let target = state.get_mut(&target).unwrap();
352 target.info.balance -= had_balance;
353 }
354 }
355 JournalEntry::BalanceChange {
356 address,
357 old_balance,
358 } => {
359 let account = state.get_mut(&address).unwrap();
360 account.info.balance = old_balance;
361 }
362 JournalEntry::BalanceTransfer { from, to, balance } => {
363 // we don't need to check overflow and underflow when adding and subtracting the balance.
364 let from = state.get_mut(&from).unwrap();
365 from.info.balance += balance;
366 let to = state.get_mut(&to).unwrap();
367 to.info.balance -= balance;
368 }
369 JournalEntry::NonceChange {
370 address,
371 previous_nonce,
372 } => {
373 state.get_mut(&address).unwrap().info.nonce = previous_nonce;
374 }
375 JournalEntry::NonceBump { address } => {
376 let nonce = &mut state.get_mut(&address).unwrap().info.nonce;
377 *nonce = nonce.saturating_sub(1);
378 }
379 JournalEntry::AccountCreated {
380 address,
381 is_created_globally,
382 } => {
383 let account = &mut state.get_mut(&address).unwrap();
384 account.unmark_created_locally();
385 if is_created_globally {
386 account.unmark_created();
387 }
388 // only account that have nonce == 0 can be created so it is safe to set it to 0.
389 account.info.nonce = 0;
390 }
391 JournalEntry::StorageWarmed { address, key } => {
392 state
393 .get_mut(&address)
394 .unwrap()
395 .storage
396 .get_mut(&key)
397 .unwrap()
398 .mark_cold();
399 }
400 JournalEntry::StorageChanged {
401 address,
402 key,
403 had_value,
404 } => {
405 state
406 .get_mut(&address)
407 .unwrap()
408 .storage
409 .get_mut(&key)
410 .unwrap()
411 .present_value = had_value;
412 }
413 JournalEntry::TransientStorageChange {
414 address,
415 key,
416 had_value,
417 } => {
418 let Some(transient_storage) = transient_storage else {
419 return;
420 };
421 let tkey = (address, key);
422 if had_value.is_zero() {
423 // if previous value is zero, remove it
424 transient_storage.remove(&tkey);
425 } else {
426 // if not zero, reinsert old value to transient storage.
427 transient_storage.insert(tkey, 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}