use crate::{
precompile::PrecompileSpecId,
primitives::{
db::Database,
eip7702, Account, Bytecode, EVMError, Env, Spec,
SpecId::{CANCUN, PRAGUE, SHANGHAI},
TxKind, BLOCKHASH_STORAGE_ADDRESS, KECCAK_EMPTY, U256,
},
Context, ContextPrecompiles,
};
#[inline]
pub fn load_precompiles<SPEC: Spec, DB: Database>() -> ContextPrecompiles<DB> {
ContextPrecompiles::new(PrecompileSpecId::from_spec_id(SPEC::SPEC_ID))
}
#[inline]
pub fn load_accounts<SPEC: Spec, EXT, DB: Database>(
context: &mut Context<EXT, DB>,
) -> Result<(), EVMError<DB::Error>> {
context.evm.journaled_state.set_spec_id(SPEC::SPEC_ID);
if SPEC::enabled(SHANGHAI) {
let coinbase = context.evm.inner.env.block.coinbase;
context
.evm
.journaled_state
.warm_preloaded_addresses
.insert(coinbase);
}
if SPEC::enabled(PRAGUE) {
context
.evm
.journaled_state
.warm_preloaded_addresses
.insert(BLOCKHASH_STORAGE_ADDRESS);
}
context.evm.load_access_list()?;
Ok(())
}
#[inline]
pub fn deduct_caller_inner<SPEC: Spec>(caller_account: &mut Account, env: &Env) {
let mut gas_cost = U256::from(env.tx.gas_limit).saturating_mul(env.effective_gas_price());
if SPEC::enabled(CANCUN) {
let data_fee = env.calc_data_fee().expect("already checked");
gas_cost = gas_cost.saturating_add(data_fee);
}
caller_account.info.balance = caller_account.info.balance.saturating_sub(gas_cost);
if matches!(env.tx.transact_to, TxKind::Call(_)) {
caller_account.info.nonce = caller_account.info.nonce.saturating_add(1);
}
caller_account.mark_touch();
}
#[inline]
pub fn deduct_caller<SPEC: Spec, EXT, DB: Database>(
context: &mut Context<EXT, DB>,
) -> Result<(), EVMError<DB::Error>> {
let caller_account = context
.evm
.inner
.journaled_state
.load_account(context.evm.inner.env.tx.caller, &mut context.evm.inner.db)?;
deduct_caller_inner::<SPEC>(caller_account.data, &context.evm.inner.env);
Ok(())
}
#[inline]
pub fn apply_eip7702_auth_list<SPEC: Spec, EXT, DB: Database>(
context: &mut Context<EXT, DB>,
) -> Result<u64, EVMError<DB::Error>> {
if !SPEC::enabled(PRAGUE) {
return Ok(0);
}
let Some(authorization_list) = context.evm.inner.env.tx.authorization_list.as_ref() else {
return Ok(0);
};
let mut refunded_accounts = 0;
for authorization in authorization_list.recovered_iter() {
let chain_id = *authorization.chain_id();
if !chain_id.is_zero() && chain_id != U256::from(context.evm.inner.env.cfg.chain_id) {
continue;
}
if authorization.nonce() == u64::MAX {
continue;
}
let Some(authority) = authorization.authority() else {
continue;
};
let mut authority_acc = context
.evm
.inner
.journaled_state
.load_code(authority, &mut context.evm.inner.db)?;
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() {
refunded_accounts += 1;
}
let (bytecode, hash) = if authorization.address.is_zero() {
(Bytecode::default(), KECCAK_EMPTY)
} else {
let bytecode = Bytecode::new_eip7702(authorization.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)
}