1use super::warm_addresses::WarmAddresses;
3use bytecode::Bytecode;
4use context_interface::{
5 context::{SStoreResult, SelfDestructResult, StateLoad},
6 journaled_state::{
7 account::JournaledAccount,
8 entry::{JournalEntryTr, SelfdestructionRevertStatus},
9 },
10 journaled_state::{AccountLoad, JournalCheckpoint, JournalLoadError, TransferError},
11};
12use core::mem;
13use database_interface::Database;
14use primitives::{
15 hardfork::SpecId::{self, *},
16 hash_map::Entry,
17 Address, HashMap, Log, StorageKey, StorageValue, B256, KECCAK_EMPTY, U256,
18};
19use state::{Account, EvmState, EvmStorageSlot, TransientStorage};
20use std::vec::Vec;
21#[derive(Debug, Clone, PartialEq, Eq)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26pub struct JournalInner<ENTRY> {
27 pub state: EvmState,
29 pub transient_storage: TransientStorage,
33 pub logs: Vec<Log>,
35 pub depth: usize,
37 pub journal: Vec<ENTRY>,
39 pub transaction_id: usize,
45 pub spec: SpecId,
58 pub warm_addresses: WarmAddresses,
60}
61
62impl<ENTRY: JournalEntryTr> Default for JournalInner<ENTRY> {
63 fn default() -> Self {
64 Self::new()
65 }
66}
67
68impl<ENTRY: JournalEntryTr> JournalInner<ENTRY> {
69 pub fn new() -> JournalInner<ENTRY> {
74 Self {
75 state: HashMap::default(),
76 transient_storage: TransientStorage::default(),
77 logs: Vec::new(),
78 journal: Vec::default(),
79 transaction_id: 0,
80 depth: 0,
81 spec: SpecId::default(),
82 warm_addresses: WarmAddresses::new(),
83 }
84 }
85
86 #[inline]
88 pub fn take_logs(&mut self) -> Vec<Log> {
89 mem::take(&mut self.logs)
90 }
91
92 pub fn commit_tx(&mut self) {
100 let Self {
103 state,
104 transient_storage,
105 logs,
106 depth,
107 journal,
108 transaction_id,
109 spec,
110 warm_addresses,
111 } = self;
112 let _ = spec;
114 let _ = state;
115 transient_storage.clear();
116 *depth = 0;
117
118 journal.clear();
120
121 warm_addresses.clear_coinbase_and_access_list();
123 *transaction_id += 1;
125 logs.clear();
126 }
127
128 pub fn discard_tx(&mut self) {
130 let Self {
132 state,
133 transient_storage,
134 logs,
135 depth,
136 journal,
137 transaction_id,
138 spec,
139 warm_addresses,
140 } = self;
141 let is_spurious_dragon_enabled = spec.is_enabled_in(SPURIOUS_DRAGON);
142 journal.drain(..).rev().for_each(|entry| {
144 entry.revert(state, None, is_spurious_dragon_enabled);
145 });
146 transient_storage.clear();
147 *depth = 0;
148 logs.clear();
149 *transaction_id += 1;
150
151 warm_addresses.clear_coinbase_and_access_list();
153 }
154
155 #[inline]
160 pub fn finalize(&mut self) -> EvmState {
161 let Self {
164 state,
165 transient_storage,
166 logs,
167 depth,
168 journal,
169 transaction_id,
170 spec,
171 warm_addresses,
172 } = self;
173 let _ = spec;
175 warm_addresses.clear_coinbase_and_access_list();
177
178 let state = mem::take(state);
179 logs.clear();
180 transient_storage.clear();
181
182 journal.clear();
184 *depth = 0;
185 *transaction_id = 0;
187
188 state
189 }
190
191 #[inline]
193 pub fn state(&mut self) -> &mut EvmState {
194 &mut self.state
195 }
196
197 #[inline]
199 pub fn set_spec_id(&mut self, spec: SpecId) {
200 self.spec = spec;
201 }
202
203 #[inline]
207 pub fn touch(&mut self, address: Address) {
208 if let Some(account) = self.state.get_mut(&address) {
209 Self::touch_account(&mut self.journal, address, account);
210 }
211 }
212
213 #[inline]
215 fn touch_account(journal: &mut Vec<ENTRY>, address: Address, account: &mut Account) {
216 if !account.is_touched() {
217 journal.push(ENTRY::account_touched(address));
218 account.mark_touch();
219 }
220 }
221
222 #[inline]
230 pub fn account(&self, address: Address) -> &Account {
231 self.state
232 .get(&address)
233 .expect("Account expected to be loaded") }
235
236 #[inline]
240 pub fn set_code_with_hash(&mut self, address: Address, code: Bytecode, hash: B256) {
241 let account = self.state.get_mut(&address).unwrap();
242 Self::touch_account(&mut self.journal, address, account);
243
244 self.journal.push(ENTRY::code_changed(address));
245
246 account.info.code_hash = hash;
247 account.info.code = Some(code);
248 }
249
250 #[inline]
256 pub fn set_code(&mut self, address: Address, code: Bytecode) {
257 if let Bytecode::Eip7702(eip7702_bytecode) = &code {
258 if eip7702_bytecode.address().is_zero() {
259 self.set_code_with_hash(address, Bytecode::default(), KECCAK_EMPTY);
260 return;
261 }
262 }
263
264 let hash = code.hash_slow();
265 self.set_code_with_hash(address, code, hash)
266 }
267
268 #[inline]
270 pub fn caller_accounting_journal_entry(
271 &mut self,
272 address: Address,
273 old_balance: U256,
274 bump_nonce: bool,
275 ) {
276 self.journal
278 .push(ENTRY::balance_changed(address, old_balance));
279 self.journal.push(ENTRY::account_touched(address));
281
282 if bump_nonce {
283 self.journal.push(ENTRY::nonce_changed(address));
285 }
286 }
287
288 #[inline]
292 pub fn balance_incr<DB: Database>(
293 &mut self,
294 db: &mut DB,
295 address: Address,
296 balance: U256,
297 ) -> Result<(), DB::Error> {
298 let mut account = self.load_account_mut(db, address)?.data;
299 account.incr_balance(balance);
300 Ok(())
301 }
302
303 #[inline]
305 pub fn nonce_bump_journal_entry(&mut self, address: Address) {
306 self.journal.push(ENTRY::nonce_changed(address));
307 }
308
309 #[inline]
315 pub fn transfer_loaded(
316 &mut self,
317 from: Address,
318 to: Address,
319 balance: U256,
320 ) -> Option<TransferError> {
321 if from == to {
322 let from_balance = self.state.get_mut(&to).unwrap().info.balance;
323 if balance > from_balance {
325 return Some(TransferError::OutOfFunds);
326 }
327 return None;
328 }
329
330 if balance.is_zero() {
331 Self::touch_account(&mut self.journal, to, self.state.get_mut(&to).unwrap());
332 return None;
333 }
334
335 let from_account = self.state.get_mut(&from).unwrap();
337 Self::touch_account(&mut self.journal, from, from_account);
338 let from_balance = &mut from_account.info.balance;
339 let Some(from_balance_decr) = from_balance.checked_sub(balance) else {
340 return Some(TransferError::OutOfFunds);
341 };
342 *from_balance = from_balance_decr;
343
344 let to_account = self.state.get_mut(&to).unwrap();
346 Self::touch_account(&mut self.journal, to, to_account);
347 let to_balance = &mut to_account.info.balance;
348 let Some(to_balance_incr) = to_balance.checked_add(balance) else {
349 return Some(TransferError::OverflowPayment);
351 };
352 *to_balance = to_balance_incr;
353
354 self.journal
356 .push(ENTRY::balance_transfer(from, to, balance));
357
358 None
359 }
360
361 #[inline]
363 pub fn transfer<DB: Database>(
364 &mut self,
365 db: &mut DB,
366 from: Address,
367 to: Address,
368 balance: U256,
369 ) -> Result<Option<TransferError>, DB::Error> {
370 self.load_account(db, from)?;
371 self.load_account(db, to)?;
372 Ok(self.transfer_loaded(from, to, balance))
373 }
374
375 #[inline]
391 pub fn create_account_checkpoint(
392 &mut self,
393 caller: Address,
394 target_address: Address,
395 balance: U256,
396 spec_id: SpecId,
397 ) -> Result<JournalCheckpoint, TransferError> {
398 let checkpoint = self.checkpoint();
400
401 let target_acc = self.state.get_mut(&target_address).unwrap();
403 let last_journal = &mut self.journal;
404
405 if target_acc.info.code_hash != KECCAK_EMPTY || target_acc.info.nonce != 0 {
410 self.checkpoint_revert(checkpoint);
411 return Err(TransferError::CreateCollision);
412 }
413
414 let is_created_globally = target_acc.mark_created_locally();
416
417 last_journal.push(ENTRY::account_created(target_address, is_created_globally));
419 target_acc.info.code = None;
420 if spec_id.is_enabled_in(SPURIOUS_DRAGON) {
422 target_acc.info.nonce = 1;
424 }
425
426 Self::touch_account(last_journal, target_address, target_acc);
429
430 let Some(new_balance) = target_acc.info.balance.checked_add(balance) else {
432 self.checkpoint_revert(checkpoint);
433 return Err(TransferError::OverflowPayment);
434 };
435 target_acc.info.balance = new_balance;
436
437 self.state.get_mut(&caller).unwrap().info.balance -= balance;
439
440 last_journal.push(ENTRY::balance_transfer(caller, target_address, balance));
442
443 Ok(checkpoint)
444 }
445
446 #[inline]
448 pub fn checkpoint(&mut self) -> JournalCheckpoint {
449 let checkpoint = JournalCheckpoint {
450 log_i: self.logs.len(),
451 journal_i: self.journal.len(),
452 };
453 self.depth += 1;
454 checkpoint
455 }
456
457 #[inline]
459 pub fn checkpoint_commit(&mut self) {
460 self.depth = self.depth.saturating_sub(1);
461 }
462
463 #[inline]
465 pub fn checkpoint_revert(&mut self, checkpoint: JournalCheckpoint) {
466 let is_spurious_dragon_enabled = self.spec.is_enabled_in(SPURIOUS_DRAGON);
467 let state = &mut self.state;
468 let transient_storage = &mut self.transient_storage;
469 self.depth = self.depth.saturating_sub(1);
470 self.logs.truncate(checkpoint.log_i);
471
472 if checkpoint.journal_i < self.journal.len() {
474 self.journal
475 .drain(checkpoint.journal_i..)
476 .rev()
477 .for_each(|entry| {
478 entry.revert(state, Some(transient_storage), is_spurious_dragon_enabled);
479 });
480 }
481 }
482
483 #[inline]
495 pub fn selfdestruct<DB: Database>(
496 &mut self,
497 db: &mut DB,
498 address: Address,
499 target: Address,
500 skip_cold_load: bool,
501 ) -> Result<StateLoad<SelfDestructResult>, JournalLoadError<<DB as Database>::Error>> {
502 let spec = self.spec;
503 let account_load = self.load_account_optional(db, target, false, skip_cold_load)?;
504 let is_cold = account_load.is_cold;
505 let is_empty = account_load.state_clear_aware_is_empty(spec);
506
507 if address != target {
508 let acc_balance = self.state.get(&address).unwrap().info.balance;
511
512 let target_account = self.state.get_mut(&target).unwrap();
513 Self::touch_account(&mut self.journal, target, target_account);
514 target_account.info.balance += acc_balance;
515 }
516
517 let acc = self.state.get_mut(&address).unwrap();
518 let balance = acc.info.balance;
519
520 let destroyed_status = if !acc.is_selfdestructed() {
521 SelfdestructionRevertStatus::GloballySelfdestroyed
522 } else if !acc.is_selfdestructed_locally() {
523 SelfdestructionRevertStatus::LocallySelfdestroyed
524 } else {
525 SelfdestructionRevertStatus::RepeatedSelfdestruction
526 };
527
528 let is_cancun_enabled = spec.is_enabled_in(CANCUN);
529
530 let journal_entry = if acc.is_created_locally() || !is_cancun_enabled {
532 acc.mark_selfdestructed_locally();
533 acc.info.balance = U256::ZERO;
534 Some(ENTRY::account_destroyed(
535 address,
536 target,
537 destroyed_status,
538 balance,
539 ))
540 } else if address != target {
541 acc.info.balance = U256::ZERO;
542 Some(ENTRY::balance_transfer(address, target, balance))
543 } else {
544 None
549 };
550
551 if let Some(entry) = journal_entry {
552 self.journal.push(entry);
553 };
554
555 Ok(StateLoad {
556 data: SelfDestructResult {
557 had_value: !balance.is_zero(),
558 target_exists: !is_empty,
559 previously_destroyed: destroyed_status
560 == SelfdestructionRevertStatus::RepeatedSelfdestruction,
561 },
562 is_cold,
563 })
564 }
565
566 #[inline]
568 pub fn load_account<DB: Database>(
569 &mut self,
570 db: &mut DB,
571 address: Address,
572 ) -> Result<StateLoad<&Account>, DB::Error> {
573 self.load_account_optional(db, address, false, false)
574 .map_err(JournalLoadError::unwrap_db_error)
575 }
576
577 #[inline]
585 pub fn load_account_delegated<DB: Database>(
586 &mut self,
587 db: &mut DB,
588 address: Address,
589 ) -> Result<StateLoad<AccountLoad>, DB::Error> {
590 let spec = self.spec;
591 let is_eip7702_enabled = spec.is_enabled_in(SpecId::PRAGUE);
592 let account = self
593 .load_account_optional(db, address, is_eip7702_enabled, false)
594 .map_err(JournalLoadError::unwrap_db_error)?;
595 let is_empty = account.state_clear_aware_is_empty(spec);
596
597 let mut account_load = StateLoad::new(
598 AccountLoad {
599 is_delegate_account_cold: None,
600 is_empty,
601 },
602 account.is_cold,
603 );
604
605 if let Some(Bytecode::Eip7702(code)) = &account.info.code {
607 let address = code.address();
608 let delegate_account = self
609 .load_account_optional(db, address, true, false)
610 .map_err(JournalLoadError::unwrap_db_error)?;
611 account_load.data.is_delegate_account_cold = Some(delegate_account.is_cold);
612 }
613
614 Ok(account_load)
615 }
616
617 #[inline]
624 pub fn load_code<DB: Database>(
625 &mut self,
626 db: &mut DB,
627 address: Address,
628 ) -> Result<StateLoad<&Account>, DB::Error> {
629 self.load_account_optional(db, address, true, false)
630 .map_err(JournalLoadError::unwrap_db_error)
631 }
632
633 #[inline]
635 pub fn load_account_optional<DB: Database>(
636 &mut self,
637 db: &mut DB,
638 address: Address,
639 load_code: bool,
640 skip_cold_load: bool,
641 ) -> Result<StateLoad<&Account>, JournalLoadError<DB::Error>> {
642 let load = self.load_account_mut_optional_code(db, address, load_code, skip_cold_load)?;
643 Ok(load.map(|i| i.into_account_ref()))
644 }
645
646 #[inline]
648 pub fn load_account_mut<DB: Database>(
649 &mut self,
650 db: &mut DB,
651 address: Address,
652 ) -> Result<StateLoad<JournaledAccount<'_, ENTRY>>, DB::Error> {
653 self.load_account_mut_optional_code(db, address, false, false)
654 .map_err(JournalLoadError::unwrap_db_error)
655 }
656
657 #[inline(never)]
659 pub fn load_account_mut_optional_code<DB: Database>(
660 &mut self,
661 db: &mut DB,
662 address: Address,
663 load_code: bool,
664 skip_cold_load: bool,
665 ) -> Result<StateLoad<JournaledAccount<'_, ENTRY>>, JournalLoadError<DB::Error>> {
666 let load = match self.state.entry(address) {
667 Entry::Occupied(entry) => {
668 let account = entry.into_mut();
669
670 let mut is_cold = account.is_cold_transaction_id(self.transaction_id);
672 if is_cold {
673 let should_be_cold = self.warm_addresses.is_cold(&address);
675
676 if should_be_cold && skip_cold_load {
678 return Err(JournalLoadError::ColdLoadSkipped);
679 }
680 is_cold = should_be_cold;
681
682 account.mark_warm_with_transaction_id(self.transaction_id);
684
685 if account.is_selfdestructed_locally() {
688 account.selfdestruct();
689 account.unmark_selfdestructed_locally();
690 }
691 account.unmark_created_locally();
693 }
694 StateLoad {
695 data: account,
696 is_cold,
697 }
698 }
699 Entry::Vacant(vac) => {
700 let is_cold = self.warm_addresses.is_cold(&address);
702
703 if is_cold && skip_cold_load {
705 return Err(JournalLoadError::ColdLoadSkipped);
706 }
707 let account = if let Some(account) = db.basic(address)? {
708 account.into()
709 } else {
710 Account::new_not_existing(self.transaction_id)
711 };
712
713 StateLoad {
714 data: vac.insert(account),
715 is_cold,
716 }
717 }
718 };
719
720 if load.is_cold {
722 self.journal.push(ENTRY::account_warmed(address));
723 }
724
725 if load_code && load.data.info.code.is_none() {
726 let info = &mut load.data.info;
727 let code = if info.code_hash == KECCAK_EMPTY {
728 Bytecode::default()
729 } else {
730 db.code_by_hash(info.code_hash)?
731 };
732 info.code = Some(code);
733 }
734
735 Ok(load.map(|i| JournaledAccount::new(address, i, &mut self.journal)))
736 }
737
738 #[inline]
744 pub fn sload<DB: Database>(
745 &mut self,
746 db: &mut DB,
747 address: Address,
748 key: StorageKey,
749 skip_cold_load: bool,
750 ) -> Result<StateLoad<StorageValue>, JournalLoadError<DB::Error>> {
751 let account = self.state.get_mut(&address).unwrap();
753
754 let is_newly_created = account.is_created();
755 let (value, is_cold) = match account.storage.entry(key) {
756 Entry::Occupied(occ) => {
757 let slot = occ.into_mut();
758 let is_cold = slot.is_cold_transaction_id(self.transaction_id);
760 if skip_cold_load && is_cold {
761 return Err(JournalLoadError::ColdLoadSkipped);
762 }
763 slot.mark_warm_with_transaction_id(self.transaction_id);
764 (slot.present_value, is_cold)
765 }
766 Entry::Vacant(vac) => {
767 let is_cold = !self.warm_addresses.is_storage_warm(&address, &key);
769
770 if is_cold && skip_cold_load {
771 return Err(JournalLoadError::ColdLoadSkipped);
772 }
773 let value = if is_newly_created {
775 StorageValue::ZERO
776 } else {
777 db.storage(address, key)?
778 };
779 vac.insert(EvmStorageSlot::new(value, self.transaction_id));
780
781 (value, is_cold)
782 }
783 };
784
785 if is_cold {
786 self.journal.push(ENTRY::storage_warmed(address, key));
788 }
789
790 Ok(StateLoad::new(value, is_cold))
791 }
792
793 #[inline]
799 pub fn sstore<DB: Database>(
800 &mut self,
801 db: &mut DB,
802 address: Address,
803 key: StorageKey,
804 new: StorageValue,
805 skip_cold_load: bool,
806 ) -> Result<StateLoad<SStoreResult>, JournalLoadError<DB::Error>> {
807 let present = self.sload(db, address, key, skip_cold_load)?;
809 let acc = self.state.get_mut(&address).unwrap();
810
811 let slot = acc.storage.get_mut(&key).unwrap();
813
814 if present.data == new {
816 return Ok(StateLoad::new(
817 SStoreResult {
818 original_value: slot.original_value(),
819 present_value: present.data,
820 new_value: new,
821 },
822 present.is_cold,
823 ));
824 }
825
826 self.journal
827 .push(ENTRY::storage_changed(address, key, present.data));
828 slot.present_value = new;
830 Ok(StateLoad::new(
831 SStoreResult {
832 original_value: slot.original_value(),
833 present_value: present.data,
834 new_value: new,
835 },
836 present.is_cold,
837 ))
838 }
839
840 #[inline]
844 pub fn tload(&mut self, address: Address, key: StorageKey) -> StorageValue {
845 self.transient_storage
846 .get(&(address, key))
847 .copied()
848 .unwrap_or_default()
849 }
850
851 #[inline]
858 pub fn tstore(&mut self, address: Address, key: StorageKey, new: StorageValue) {
859 let had_value = if new.is_zero() {
860 self.transient_storage.remove(&(address, key))
864 } else {
865 let previous_value = self
867 .transient_storage
868 .insert((address, key), new)
869 .unwrap_or_default();
870
871 if previous_value != new {
873 Some(previous_value)
875 } else {
876 None
877 }
878 };
879
880 if let Some(had_value) = had_value {
881 self.journal
883 .push(ENTRY::transient_storage_changed(address, key, had_value));
884 }
885 }
886
887 #[inline]
889 pub fn log(&mut self, log: Log) {
890 self.logs.push(log);
891 }
892}
893
894#[cfg(test)]
895mod tests {
896 use super::*;
897 use context_interface::journaled_state::entry::JournalEntry;
898 use database_interface::EmptyDB;
899 use primitives::{address, HashSet, U256};
900 use state::AccountInfo;
901
902 #[test]
903 fn test_sload_skip_cold_load() {
904 let mut journal = JournalInner::<JournalEntry>::new();
905 let test_address = address!("1000000000000000000000000000000000000000");
906 let test_key = U256::from(1);
907
908 let account_info = AccountInfo {
910 balance: U256::from(1000),
911 nonce: 1,
912 code_hash: KECCAK_EMPTY,
913 code: Some(Bytecode::default()),
914 };
915 journal
916 .state
917 .insert(test_address, Account::from(account_info));
918
919 let mut access_list = HashMap::default();
921 let mut storage_keys = HashSet::default();
922 storage_keys.insert(test_key);
923 access_list.insert(test_address, storage_keys);
924 journal.warm_addresses.set_access_list(access_list);
925
926 let mut db = EmptyDB::new();
928 let result = journal.sload(&mut db, test_address, test_key, true);
929
930 assert!(result.is_ok());
932 let state_load = result.unwrap();
933 assert!(!state_load.is_cold); assert_eq!(state_load.data, U256::ZERO); }
936}