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}