use crate::{EvmTr, PrecompileProvider};
use bytecode::Bytecode;
use context_interface::transaction::{AccessListItemTr, AuthorizationTr};
use context_interface::ContextTr;
use context_interface::{
journaled_state::JournalTr,
result::InvalidTransaction,
transaction::{Transaction, TransactionType},
Block, Cfg, Database,
};
use core::cmp::Ordering;
use primitives::StorageKey;
use primitives::{eip7702, hardfork::SpecId, KECCAK_EMPTY, U256};
use state::AccountInfo;
use std::boxed::Box;
pub fn load_accounts<
EVM: EvmTr<Precompiles: PrecompileProvider<EVM::Context>>,
ERROR: From<<<EVM::Context as ContextTr>::Db as Database>::Error>,
>(
evm: &mut EVM,
) -> Result<(), ERROR> {
let (context, precompiles) = evm.ctx_precompiles();
let gen_spec = context.cfg().spec();
let spec = gen_spec.clone().into();
context.journal_mut().set_spec_id(spec);
let precompiles_changed = precompiles.set_spec(gen_spec);
let empty_warmed_precompiles = context.journal_mut().precompile_addresses().is_empty();
if precompiles_changed || empty_warmed_precompiles {
context
.journal_mut()
.warm_precompiles(precompiles.warm_addresses().collect());
}
if spec.is_enabled_in(SpecId::SHANGHAI) {
let coinbase = context.block().beneficiary();
context.journal_mut().warm_account(coinbase);
}
let (tx, journal) = context.tx_journal_mut();
if tx.tx_type() != TransactionType::Legacy {
if let Some(access_list) = tx.access_list() {
for item in access_list {
let address = item.address();
let mut storage = item.storage_slots().peekable();
if storage.peek().is_none() {
journal.warm_account(*address);
} else {
journal.warm_account_and_storage(
*address,
storage.map(|i| StorageKey::from_be_bytes(i.0)),
)?;
}
}
}
}
Ok(())
}
#[inline]
pub fn validate_account_nonce_and_code(
caller_info: &mut AccountInfo,
tx_nonce: u64,
is_eip3607_disabled: bool,
is_nonce_check_disabled: bool,
) -> Result<(), InvalidTransaction> {
if !is_eip3607_disabled {
let bytecode = match caller_info.code.as_ref() {
Some(code) => code,
None => &Bytecode::default(),
};
if !bytecode.is_empty() && !bytecode.is_eip7702() {
return Err(InvalidTransaction::RejectCallerWithCode);
}
}
if !is_nonce_check_disabled {
let tx = tx_nonce;
let state = caller_info.nonce;
match tx.cmp(&state) {
Ordering::Greater => {
return Err(InvalidTransaction::NonceTooHigh { tx, state });
}
Ordering::Less => {
return Err(InvalidTransaction::NonceTooLow { tx, state });
}
_ => {}
}
}
Ok(())
}
#[inline]
pub fn validate_against_state_and_deduct_caller<
CTX: ContextTr,
ERROR: From<InvalidTransaction> + From<<CTX::Db as Database>::Error>,
>(
context: &mut CTX,
) -> Result<(), ERROR> {
let basefee = context.block().basefee() as u128;
let blob_price = context.block().blob_gasprice().unwrap_or_default();
let is_balance_check_disabled = context.cfg().is_balance_check_disabled();
let is_eip3607_disabled = context.cfg().is_eip3607_disabled();
let is_nonce_check_disabled = context.cfg().is_nonce_check_disabled();
let (tx, journal) = context.tx_journal_mut();
let caller_account = journal.load_account_code(tx.caller())?.data;
validate_account_nonce_and_code(
&mut caller_account.info,
tx.nonce(),
is_eip3607_disabled,
is_nonce_check_disabled,
)?;
if tx.kind().is_call() {
caller_account.info.nonce = caller_account.info.nonce.saturating_add(1);
}
let max_balance_spending = tx.max_balance_spending()?;
let mut new_balance = caller_account.info.balance;
if is_balance_check_disabled {
new_balance = caller_account.info.balance.max(tx.value());
} else if max_balance_spending > caller_account.info.balance {
return Err(InvalidTransaction::LackOfFundForMaxFee {
fee: Box::new(max_balance_spending),
balance: Box::new(caller_account.info.balance),
}
.into());
} else {
let effective_balance_spending = tx
.effective_balance_spending(basefee, blob_price)
.expect("effective balance is always smaller than max balance so it can't overflow");
let gas_balance_spending = effective_balance_spending - tx.value();
new_balance = new_balance.saturating_sub(gas_balance_spending);
}
let old_balance = caller_account.info.balance;
caller_account.mark_touch();
caller_account.info.balance = new_balance;
journal.caller_accounting_journal_entry(tx.caller(), old_balance, tx.kind().is_call());
Ok(())
}
#[inline]
pub fn apply_eip7702_auth_list<
CTX: ContextTr,
ERROR: From<InvalidTransaction> + From<<CTX::Db as Database>::Error>,
>(
context: &mut CTX,
) -> Result<u64, ERROR> {
let tx = context.tx();
if tx.tx_type() != TransactionType::Eip7702 {
return Ok(0);
}
let chain_id = context.cfg().chain_id();
let (tx, journal) = context.tx_journal_mut();
let mut refunded_accounts = 0;
for authorization in tx.authorization_list() {
let auth_chain_id = authorization.chain_id();
if !auth_chain_id.is_zero() && auth_chain_id != U256::from(chain_id) {
continue;
}
if authorization.nonce() == u64::MAX {
continue;
}
let Some(authority) = authorization.authority() else {
continue;
};
let mut authority_acc = journal.load_account_code(authority)?;
if let Some(bytecode) = &authority_acc.info.code {
if !bytecode.is_empty() && !bytecode.is_eip7702() {
continue;
}
}
if authorization.nonce() != authority_acc.info.nonce {
continue;
}
if !(authority_acc.is_empty() && authority_acc.is_loaded_as_not_existing_not_touched()) {
refunded_accounts += 1;
}
let address = authorization.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)
};
authority_acc.info.code_hash = hash;
authority_acc.info.code = Some(bytecode);
authority_acc.info.nonce = authority_acc.info.nonce.saturating_add(1);
authority_acc.mark_touch();
}
let refunded_gas =
refunded_accounts * (eip7702::PER_EMPTY_ACCOUNT_COST - eip7702::PER_AUTH_BASE_COST);
Ok(refunded_gas)
}