revm_handler/
pre_execution.rsuse bytecode::Bytecode;
use context_interface::{
journaled_state::Journal,
result::InvalidTransaction,
transaction::{Transaction, TransactionType},
Block, BlockGetter, Cfg, CfgGetter, Database, DatabaseGetter, JournalDBError, JournalGetter,
PerformantContextAccess, TransactionGetter,
};
use handler_interface::PreExecutionHandler;
use primitives::{Address, BLOCKHASH_STORAGE_ADDRESS, KECCAK_EMPTY, U256};
use specification::{eip7702, hardfork::SpecId};
use std::{boxed::Box, vec::Vec};
#[derive(Default)]
pub struct EthPreExecution<CTX, ERROR> {
pub _phantom: core::marker::PhantomData<(CTX, ERROR)>,
}
impl<CTX, ERROR> EthPreExecution<CTX, ERROR> {
pub fn new() -> Self {
Self {
_phantom: core::marker::PhantomData,
}
}
pub fn new_boxed() -> Box<Self> {
Box::new(Self::new())
}
}
impl<CTX, ERROR> PreExecutionHandler for EthPreExecution<CTX, ERROR>
where
CTX: EthPreExecutionContext,
ERROR: EthPreExecutionError<CTX>,
{
type Context = CTX;
type Error = ERROR;
fn load_accounts(&self, context: &mut Self::Context) -> Result<(), Self::Error> {
let spec = context.cfg().spec().into();
context.journal().set_spec_id(spec);
if spec.is_enabled_in(SpecId::SHANGHAI) {
let coinbase = context.block().beneficiary();
context.journal().warm_account(coinbase);
}
if spec.is_enabled_in(SpecId::PRAGUE) {
context.journal().warm_account(BLOCKHASH_STORAGE_ADDRESS);
}
context.load_access_list()?;
Ok(())
}
fn apply_eip7702_auth_list(&self, context: &mut Self::Context) -> Result<u64, Self::Error> {
let spec = context.cfg().spec().into();
if spec.is_enabled_in(SpecId::PRAGUE) {
apply_eip7702_auth_list::<CTX, ERROR>(context)
} else {
Ok(0)
}
}
#[inline]
fn deduct_caller(&self, context: &mut Self::Context) -> Result<(), Self::Error> {
let basefee = context.block().basefee();
let blob_price = context.block().blob_gasprice().unwrap_or_default();
let effective_gas_price = context.tx().effective_gas_price(basefee as u128);
let mut gas_cost = (context.tx().gas_limit() as u128).saturating_mul(effective_gas_price);
if context.tx().tx_type() == TransactionType::Eip4844 {
let blob_gas = context.tx().total_blob_gas() as u128;
gas_cost = gas_cost.saturating_add(blob_price.saturating_mul(blob_gas));
}
let is_call = context.tx().kind().is_call();
let caller = context.tx().caller();
let caller_account = context.journal().load_account(caller)?.data;
caller_account.info.balance = caller_account
.info
.balance
.saturating_sub(U256::from(gas_cost));
if is_call {
caller_account.info.nonce = caller_account.info.nonce.saturating_add(1);
}
caller_account.mark_touch();
Ok(())
}
}
#[inline]
pub fn apply_eip7702_auth_list<
CTX: TransactionGetter + JournalGetter + CfgGetter,
ERROR: From<InvalidTransaction> + From<JournalDBError<CTX>>,
>(
context: &mut CTX,
) -> Result<u64, ERROR> {
let tx = context.tx();
if tx.tx_type() != TransactionType::Eip7702 {
return Ok(0);
}
struct Authorization {
authority: Option<Address>,
address: Address,
nonce: u64,
chain_id: U256,
}
let authorization_list = tx
.authorization_list()
.map(|a| Authorization {
authority: a.0,
chain_id: a.1,
nonce: a.2,
address: a.3,
})
.collect::<Vec<_>>();
let chain_id = context.cfg().chain_id();
let mut refunded_accounts = 0;
for authorization in 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 = context.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() {
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)
}
pub trait EthPreExecutionContext:
TransactionGetter
+ BlockGetter
+ JournalGetter
+ CfgGetter
+ PerformantContextAccess<Error = <<Self as DatabaseGetter>::Database as Database>::Error>
{
}
impl<
CTX: TransactionGetter
+ BlockGetter
+ JournalGetter
+ CfgGetter
+ PerformantContextAccess<Error = <<CTX as DatabaseGetter>::Database as Database>::Error>,
> EthPreExecutionContext for CTX
{
}
pub trait EthPreExecutionError<CTX: JournalGetter>:
From<InvalidTransaction> + From<JournalDBError<CTX>>
{
}
impl<CTX: JournalGetter, T: From<InvalidTransaction> + From<JournalDBError<CTX>>>
EthPreExecutionError<CTX> for T
{
}