use crate::{
context::{SStoreResult, StateLoad},
journaled_state::{entry::JournalEntry, JournalLoadErasedError, JournalLoadError},
ErasedError,
};
use super::entry::JournalEntryTr;
use auto_impl::auto_impl;
use database_interface::Database;
use primitives::{
hash_map::Entry, Address, AddressMap, HashSet, StorageKey, StorageValue, B256, KECCAK_EMPTY,
U256,
};
use state::{Account, Bytecode, EvmStorageSlot};
use std::vec::Vec;
#[auto_impl(&mut, Box)]
pub trait JournaledAccountTr {
fn account(&self) -> &Account;
fn sload(
&mut self,
key: StorageKey,
skip_cold_load: bool,
) -> Result<StateLoad<&mut EvmStorageSlot>, JournalLoadErasedError>;
fn sstore(
&mut self,
key: StorageKey,
new: StorageValue,
skip_cold_load: bool,
) -> Result<StateLoad<SStoreResult>, JournalLoadErasedError>;
fn load_code(&mut self) -> Result<&Bytecode, JournalLoadErasedError>;
fn balance(&self) -> &U256;
fn nonce(&self) -> u64;
fn code_hash(&self) -> &B256;
fn code(&self) -> Option<&Bytecode>;
fn touch(&mut self);
fn unsafe_mark_cold(&mut self);
fn set_balance(&mut self, balance: U256);
fn incr_balance(&mut self, balance: U256) -> bool;
fn decr_balance(&mut self, balance: U256) -> bool;
fn bump_nonce(&mut self) -> bool;
fn set_nonce(&mut self, nonce: u64);
fn unsafe_set_nonce(&mut self, nonce: u64);
fn set_code(&mut self, code_hash: B256, code: Bytecode);
fn set_code_and_hash_slow(&mut self, code: Bytecode);
fn delegate(&mut self, address: Address);
}
#[derive(Debug, PartialEq, Eq)]
pub struct JournaledAccount<'a, DB, ENTRY: JournalEntryTr = JournalEntry> {
address: Address,
account: &'a mut Account,
journal_entries: &'a mut Vec<ENTRY>,
access_list: &'a AddressMap<HashSet<StorageKey>>,
transaction_id: usize,
db: &'a mut DB,
}
impl<'a, DB: Database, ENTRY: JournalEntryTr> JournaledAccount<'a, DB, ENTRY> {
#[inline]
pub fn new(
address: Address,
account: &'a mut Account,
journal_entries: &'a mut Vec<ENTRY>,
db: &'a mut DB,
access_list: &'a AddressMap<HashSet<StorageKey>>,
transaction_id: usize,
) -> Self {
Self {
address,
account,
journal_entries,
access_list,
transaction_id,
db,
}
}
#[inline(never)]
pub fn sload_concrete_error(
&mut self,
key: StorageKey,
skip_cold_load: bool,
) -> Result<StateLoad<&mut EvmStorageSlot>, JournalLoadError<DB::Error>> {
let is_newly_created = self.account.is_created();
let (slot, is_cold) = match self.account.storage.entry(key) {
Entry::Occupied(occ) => {
let slot = occ.into_mut();
let mut is_cold = false;
if slot.is_cold_transaction_id(self.transaction_id) {
is_cold = self
.access_list
.get(&self.address)
.and_then(|v| v.get(&key))
.is_none();
if is_cold && skip_cold_load {
return Err(JournalLoadError::ColdLoadSkipped);
}
}
slot.mark_warm_with_transaction_id(self.transaction_id);
(slot, is_cold)
}
Entry::Vacant(vac) => {
let is_cold = self
.access_list
.get(&self.address)
.and_then(|v| v.get(&key))
.is_none();
if is_cold && skip_cold_load {
return Err(JournalLoadError::ColdLoadSkipped);
}
let value = if is_newly_created {
StorageValue::ZERO
} else {
self.db.storage(self.address, key)?
};
let slot = vac.insert(EvmStorageSlot::new(value, self.transaction_id));
(slot, is_cold)
}
};
if is_cold {
self.journal_entries
.push(ENTRY::storage_warmed(self.address, key));
}
Ok(StateLoad::new(slot, is_cold))
}
#[inline]
pub fn sstore_concrete_error(
&mut self,
key: StorageKey,
new: StorageValue,
skip_cold_load: bool,
) -> Result<StateLoad<SStoreResult>, JournalLoadError<DB::Error>> {
self.touch();
let slot = self.sload_concrete_error(key, skip_cold_load)?;
let ret = Ok(StateLoad::new(
SStoreResult {
original_value: slot.original_value(),
present_value: slot.present_value(),
new_value: new,
},
slot.is_cold,
));
if slot.present_value != new {
let previous_value = slot.present_value;
slot.data.present_value = new;
self.journal_entries
.push(ENTRY::storage_changed(self.address, key, previous_value));
}
ret
}
#[inline]
pub fn load_code_preserve_error(&mut self) -> Result<&Bytecode, JournalLoadError<DB::Error>> {
if self.account.info.code.is_none() {
let hash = *self.code_hash();
let code = if hash == KECCAK_EMPTY {
Bytecode::default()
} else {
self.db.code_by_hash(hash)?
};
self.account.info.code = Some(code);
}
Ok(self.account.info.code.as_ref().unwrap())
}
#[inline]
pub fn into_account(self) -> &'a Account {
self.account
}
}
impl<'a, DB: Database, ENTRY: JournalEntryTr> JournaledAccountTr
for JournaledAccount<'a, DB, ENTRY>
{
fn account(&self) -> &Account {
self.account
}
#[inline]
fn balance(&self) -> &U256 {
&self.account.info.balance
}
#[inline]
fn nonce(&self) -> u64 {
self.account.info.nonce
}
#[inline]
fn code_hash(&self) -> &B256 {
&self.account.info.code_hash
}
#[inline]
fn code(&self) -> Option<&Bytecode> {
self.account.info.code.as_ref()
}
#[inline]
fn touch(&mut self) {
if !self.account.status.is_touched() {
self.account.mark_touch();
self.journal_entries
.push(ENTRY::account_touched(self.address));
}
}
#[inline]
fn unsafe_mark_cold(&mut self) {
self.account.mark_cold();
}
#[inline]
fn set_balance(&mut self, balance: U256) {
self.touch();
if self.account.info.balance != balance {
self.journal_entries.push(ENTRY::balance_changed(
self.address,
self.account.info.balance,
));
self.account.info.set_balance(balance);
}
}
#[inline]
fn incr_balance(&mut self, balance: U256) -> bool {
self.touch();
let Some(balance) = self.account.info.balance.checked_add(balance) else {
return false;
};
self.set_balance(balance);
true
}
#[inline]
fn decr_balance(&mut self, balance: U256) -> bool {
self.touch();
let Some(balance) = self.account.info.balance.checked_sub(balance) else {
return false;
};
self.set_balance(balance);
true
}
#[inline]
fn bump_nonce(&mut self) -> bool {
self.touch();
let Some(nonce) = self.account.info.nonce.checked_add(1) else {
return false;
};
self.account.info.set_nonce(nonce);
self.journal_entries.push(ENTRY::nonce_bumped(self.address));
true
}
#[inline]
fn set_nonce(&mut self, nonce: u64) {
self.touch();
let previous_nonce = self.account.info.nonce;
self.account.info.set_nonce(nonce);
self.journal_entries
.push(ENTRY::nonce_changed(self.address, previous_nonce));
}
#[inline]
fn unsafe_set_nonce(&mut self, nonce: u64) {
self.account.info.set_nonce(nonce);
}
#[inline]
fn set_code(&mut self, code_hash: B256, code: Bytecode) {
self.touch();
self.account.info.set_code_and_hash(code, code_hash);
self.journal_entries.push(ENTRY::code_changed(self.address));
}
#[inline]
fn set_code_and_hash_slow(&mut self, code: Bytecode) {
let code_hash = code.hash_slow();
self.set_code(code_hash, code);
}
#[inline]
fn delegate(&mut self, address: Address) {
let (bytecode, hash) = if address.is_zero() {
(Bytecode::default(), KECCAK_EMPTY)
} else {
let bytecode = Bytecode::new_eip7702(address);
let hash = bytecode.hash_slow();
(bytecode, hash)
};
self.touch();
self.set_code(hash, bytecode);
self.bump_nonce();
}
#[inline]
fn sload(
&mut self,
key: StorageKey,
skip_cold_load: bool,
) -> Result<StateLoad<&mut EvmStorageSlot>, JournalLoadErasedError> {
self.sload_concrete_error(key, skip_cold_load)
.map_err(|i| i.map(ErasedError::new))
}
#[inline]
fn sstore(
&mut self,
key: StorageKey,
new: StorageValue,
skip_cold_load: bool,
) -> Result<StateLoad<SStoreResult>, JournalLoadErasedError> {
self.sstore_concrete_error(key, new, skip_cold_load)
.map_err(|i| i.map(ErasedError::new))
}
#[inline]
fn load_code(&mut self) -> Result<&Bytecode, JournalLoadErasedError> {
self.load_code_preserve_error()
.map_err(|i| i.map(ErasedError::new))
}
}