use context_interface::{
journaled_state::JournaledState,
result::{InvalidHeader, InvalidTransaction},
transaction::{
eip7702::Authorization, Eip1559CommonTxFields, Eip2930Tx, Eip4844Tx, Eip7702Tx, LegacyTx,
Transaction, TransactionType,
},
Block, BlockGetter, Cfg, CfgGetter, JournalStateGetter, JournalStateGetterDBError,
TransactionGetter,
};
use core::cmp::{self, Ordering};
use handler_interface::ValidationHandler;
use interpreter::gas;
use primitives::{B256, U256};
use specification::{eip4844, hardfork::SpecId};
use state::Account;
use std::boxed::Box;
pub struct EthValidation<CTX, ERROR> {
pub _phantom: core::marker::PhantomData<fn() -> (CTX, ERROR)>,
}
impl<CTX, ERROR> Default for EthValidation<CTX, ERROR> {
fn default() -> Self {
Self {
_phantom: core::marker::PhantomData,
}
}
}
impl<CTX, ERROR> EthValidation<CTX, ERROR> {
pub fn new() -> Self {
Self {
_phantom: core::marker::PhantomData,
}
}
pub fn new_boxed() -> Box<Self> {
Box::new(Self::new())
}
}
impl<CTX, ERROR> ValidationHandler for EthValidation<CTX, ERROR>
where
CTX: EthValidationContext,
ERROR: From<InvalidTransaction> + From<InvalidHeader> + From<JournalStateGetterDBError<CTX>>,
{
type Context = CTX;
type Error = ERROR;
fn validate_env(&self, context: &Self::Context) -> Result<(), Self::Error> {
let spec = context.cfg().spec().into();
if spec.is_enabled_in(SpecId::MERGE) && context.block().prevrandao().is_none() {
return Err(InvalidHeader::PrevrandaoNotSet.into());
}
if spec.is_enabled_in(SpecId::CANCUN)
&& context.block().blob_excess_gas_and_price().is_none()
{
return Err(InvalidHeader::ExcessBlobGasNotSet.into());
}
validate_tx_env::<&Self::Context, InvalidTransaction>(context, spec).map_err(Into::into)
}
fn validate_tx_against_state(&self, context: &mut Self::Context) -> Result<(), Self::Error> {
let tx_caller = context.tx().common_fields().caller();
let account = &mut context.journal().load_account_code(tx_caller)?;
let account = account.data.clone();
validate_tx_against_account::<CTX, ERROR>(&account, context)
}
fn validate_initial_tx_gas(&self, context: &Self::Context) -> Result<u64, Self::Error> {
let spec = context.cfg().spec().into();
validate_initial_tx_gas::<&Self::Context, InvalidTransaction>(context, spec)
.map_err(Into::into)
}
}
pub fn validate_priority_fee_tx(
max_fee: u128,
max_priority_fee: u128,
base_fee: Option<U256>,
) -> Result<(), InvalidTransaction> {
if max_priority_fee > max_fee {
return Err(InvalidTransaction::PriorityFeeGreaterThanMaxFee);
}
if let Some(base_fee) = base_fee {
let effective_gas_price = cmp::min(
U256::from(max_fee),
base_fee.saturating_add(U256::from(max_priority_fee)),
);
if effective_gas_price < base_fee {
return Err(InvalidTransaction::GasPriceLessThanBasefee);
}
}
Ok(())
}
pub fn validate_eip4844_tx(
blobs: &[B256],
max_blob_fee: u128,
block_blob_gas_price: u128,
) -> Result<(), InvalidTransaction> {
if block_blob_gas_price > max_blob_fee {
return Err(InvalidTransaction::BlobGasPriceGreaterThanMax);
}
if blobs.is_empty() {
return Err(InvalidTransaction::EmptyBlobs);
}
for blob in blobs {
if blob[0] != eip4844::VERSIONED_HASH_VERSION_KZG {
return Err(InvalidTransaction::BlobVersionNotSupported);
}
}
if blobs.len() > eip4844::MAX_BLOB_NUMBER_PER_BLOCK as usize {
return Err(InvalidTransaction::TooManyBlobs {
have: blobs.len(),
max: eip4844::MAX_BLOB_NUMBER_PER_BLOCK as usize,
});
}
Ok(())
}
pub fn validate_tx_env<CTX: TransactionGetter + BlockGetter + CfgGetter, Error>(
context: CTX,
spec_id: SpecId,
) -> Result<(), Error>
where
Error: From<InvalidTransaction>,
{
let common_field = context.tx().common_fields();
let tx_type = context.tx().tx_type().into();
let base_fee = if context.cfg().is_base_fee_check_disabled() {
None
} else {
Some(*context.block().basefee())
};
match tx_type {
TransactionType::Legacy => {
let tx = context.tx().legacy();
if let Some(chain_id) = tx.chain_id() {
if chain_id != context.cfg().chain_id() {
return Err(InvalidTransaction::InvalidChainId.into());
}
}
if let Some(base_fee) = base_fee {
if U256::from(tx.gas_price()) < base_fee {
return Err(InvalidTransaction::GasPriceLessThanBasefee.into());
}
}
}
TransactionType::Eip2930 => {
if !spec_id.is_enabled_in(SpecId::BERLIN) {
return Err(InvalidTransaction::Eip2930NotSupported.into());
}
let tx = context.tx().eip2930();
if context.cfg().chain_id() != tx.chain_id() {
return Err(InvalidTransaction::InvalidChainId.into());
}
if let Some(base_fee) = base_fee {
if U256::from(tx.gas_price()) < base_fee {
return Err(InvalidTransaction::GasPriceLessThanBasefee.into());
}
}
}
TransactionType::Eip1559 => {
if !spec_id.is_enabled_in(SpecId::LONDON) {
return Err(InvalidTransaction::Eip1559NotSupported.into());
}
let tx = context.tx().eip1559();
if context.cfg().chain_id() != tx.chain_id() {
return Err(InvalidTransaction::InvalidChainId.into());
}
validate_priority_fee_tx(
tx.max_fee_per_gas(),
tx.max_priority_fee_per_gas(),
base_fee,
)?;
}
TransactionType::Eip4844 => {
if !spec_id.is_enabled_in(SpecId::CANCUN) {
return Err(InvalidTransaction::Eip4844NotSupported.into());
}
let tx = context.tx().eip4844();
if context.cfg().chain_id() != tx.chain_id() {
return Err(InvalidTransaction::InvalidChainId.into());
}
validate_priority_fee_tx(
tx.max_fee_per_gas(),
tx.max_priority_fee_per_gas(),
base_fee,
)?;
validate_eip4844_tx(
tx.blob_versioned_hashes(),
tx.max_fee_per_blob_gas(),
context.block().blob_gasprice().unwrap_or_default(),
)?;
}
TransactionType::Eip7702 => {
if !spec_id.is_enabled_in(SpecId::PRAGUE) {
return Err(InvalidTransaction::Eip7702NotSupported.into());
}
let tx = context.tx().eip7702();
if context.cfg().chain_id() != tx.chain_id() {
return Err(InvalidTransaction::InvalidChainId.into());
}
validate_priority_fee_tx(
tx.max_fee_per_gas(),
tx.max_priority_fee_per_gas(),
base_fee,
)?;
let auth_list_len = tx.authorization_list_len();
if auth_list_len == 0 {
return Err(InvalidTransaction::EmptyAuthorizationList.into());
}
for auth in tx.authorization_list_iter() {
if auth.is_invalid() {
return Err(InvalidTransaction::Eip7702NotSupported.into());
}
}
}
TransactionType::Custom => {
}
};
if !context.cfg().is_block_gas_limit_disabled()
&& U256::from(common_field.gas_limit()) > *context.block().gas_limit()
{
return Err(InvalidTransaction::CallerGasLimitMoreThanBlock.into());
}
if spec_id.is_enabled_in(SpecId::SHANGHAI) && context.tx().kind().is_create() {
let max_initcode_size = context.cfg().max_code_size().saturating_mul(2);
if context.tx().common_fields().input().len() > max_initcode_size {
return Err(InvalidTransaction::CreateInitCodeSizeLimit.into());
}
}
Ok(())
}
#[inline]
pub fn validate_tx_against_account<CTX: TransactionGetter + CfgGetter, ERROR>(
account: &Account,
context: &CTX,
) -> Result<(), ERROR>
where
ERROR: From<InvalidTransaction>,
{
let tx_type = context.tx().tx_type().into();
if !context.cfg().is_eip3607_disabled() {
let bytecode = &account.info.code.as_ref().unwrap();
if !bytecode.is_empty() && !bytecode.is_eip7702() {
return Err(InvalidTransaction::RejectCallerWithCode.into());
}
}
if !context.cfg().is_nonce_check_disabled() {
let tx = context.tx().common_fields().nonce();
let state = account.info.nonce;
match tx.cmp(&state) {
Ordering::Greater => {
return Err(InvalidTransaction::NonceTooHigh { tx, state }.into());
}
Ordering::Less => {
return Err(InvalidTransaction::NonceTooLow { tx, state }.into());
}
_ => {}
}
}
let mut balance_check = U256::from(context.tx().common_fields().gas_limit())
.checked_mul(U256::from(context.tx().max_fee()))
.and_then(|gas_cost| gas_cost.checked_add(context.tx().common_fields().value()))
.ok_or(InvalidTransaction::OverflowPaymentInTransaction)?;
if tx_type == TransactionType::Eip4844 {
let tx = context.tx().eip4844();
let data_fee = tx.calc_max_data_fee();
balance_check = balance_check
.checked_add(data_fee)
.ok_or(InvalidTransaction::OverflowPaymentInTransaction)?;
}
if balance_check > account.info.balance && !context.cfg().is_balance_check_disabled() {
return Err(InvalidTransaction::LackOfFundForMaxFee {
fee: Box::new(balance_check),
balance: Box::new(account.info.balance),
}
.into());
}
Ok(())
}
pub fn validate_initial_tx_gas<TxGetter: TransactionGetter, Error>(
env: TxGetter,
spec_id: SpecId,
) -> Result<u64, Error>
where
Error: From<InvalidTransaction>,
{
let tx_type = env.tx().tx_type().into();
let authorization_list_num = if tx_type == TransactionType::Eip7702 {
env.tx().eip7702().authorization_list_len() as u64
} else {
0
};
let common_fields = env.tx().common_fields();
let is_create = env.tx().kind().is_create();
let input = common_fields.input();
let access_list = env.tx().access_list();
let initial_gas_spend = gas::validate_initial_tx_gas(
spec_id,
input,
is_create,
access_list,
authorization_list_num,
);
if initial_gas_spend > common_fields.gas_limit() {
return Err(InvalidTransaction::CallGasCostMoreThanGasLimit.into());
}
Ok(initial_gas_spend)
}
pub trait EthValidationContext:
TransactionGetter + BlockGetter + JournalStateGetter + CfgGetter
{
}
impl<T: TransactionGetter + BlockGetter + JournalStateGetter + CfgGetter> EthValidationContext
for T
{
}
pub trait EthValidationError<CTX: JournalStateGetter>:
From<InvalidTransaction> + From<InvalidHeader> + From<JournalStateGetterDBError<CTX>>
{
}
impl<
CTX: JournalStateGetter,
T: From<InvalidTransaction> + From<InvalidHeader> + From<JournalStateGetterDBError<CTX>>,
> EthValidationError<CTX> for T
{
}