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}