use context_interface::{
result::{InvalidHeader, InvalidTransaction},
transaction::{Transaction, TransactionType},
Block, Cfg, ContextTr,
};
use core::cmp;
use interpreter::gas::{self, InitialAndFloorGas};
use primitives::{eip4844, hardfork::SpecId, B256};
pub fn validate_env<CTX: ContextTr, ERROR: From<InvalidHeader> + From<InvalidTransaction>>(
context: CTX,
) -> Result<(), 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::<CTX, InvalidTransaction>(context, spec).map_err(Into::into)
}
pub fn validate_priority_fee_tx(
max_fee: u128,
max_priority_fee: u128,
base_fee: Option<u128>,
) -> 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(max_fee, base_fee.saturating_add(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,
max_blobs: Option<u64>,
) -> 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 let Some(max_blobs) = max_blobs {
if blobs.len() > max_blobs as usize {
return Err(InvalidTransaction::TooManyBlobs {
have: blobs.len(),
max: max_blobs as usize,
});
}
}
Ok(())
}
pub fn validate_tx_env<CTX: ContextTr, Error>(
context: CTX,
spec_id: SpecId,
) -> Result<(), InvalidTransaction> {
let tx_type = context.tx().tx_type();
let tx = context.tx();
let base_fee = if context.cfg().is_base_fee_check_disabled() {
None
} else {
Some(context.block().basefee() as u128)
};
let tx_type = TransactionType::from(tx_type);
if context.cfg().tx_chain_id_check() {
if let Some(chain_id) = tx.chain_id() {
if chain_id != context.cfg().chain_id() {
return Err(InvalidTransaction::InvalidChainId);
}
} else if !tx_type.is_legacy() && !tx_type.is_custom() {
return Err(InvalidTransaction::MissingChainId);
}
}
let cap = context.cfg().tx_gas_limit_cap();
if tx.gas_limit() > cap {
return Err(InvalidTransaction::TxGasLimitGreaterThanCap {
gas_limit: tx.gas_limit(),
cap,
});
}
match tx_type {
TransactionType::Legacy => {
if let Some(base_fee) = base_fee {
if tx.gas_price() < base_fee {
return Err(InvalidTransaction::GasPriceLessThanBasefee);
}
}
}
TransactionType::Eip2930 => {
if !spec_id.is_enabled_in(SpecId::BERLIN) {
return Err(InvalidTransaction::Eip2930NotSupported);
}
if let Some(base_fee) = base_fee {
if tx.gas_price() < base_fee {
return Err(InvalidTransaction::GasPriceLessThanBasefee);
}
}
}
TransactionType::Eip1559 => {
if !spec_id.is_enabled_in(SpecId::LONDON) {
return Err(InvalidTransaction::Eip1559NotSupported);
}
validate_priority_fee_tx(
tx.max_fee_per_gas(),
tx.max_priority_fee_per_gas().unwrap_or_default(),
base_fee,
)?;
}
TransactionType::Eip4844 => {
if !spec_id.is_enabled_in(SpecId::CANCUN) {
return Err(InvalidTransaction::Eip4844NotSupported);
}
validate_priority_fee_tx(
tx.max_fee_per_gas(),
tx.max_priority_fee_per_gas().unwrap_or_default(),
base_fee,
)?;
validate_eip4844_tx(
tx.blob_versioned_hashes(),
tx.max_fee_per_blob_gas(),
context.block().blob_gasprice().unwrap_or_default(),
context.cfg().blob_max_count(),
)?;
}
TransactionType::Eip7702 => {
if !spec_id.is_enabled_in(SpecId::PRAGUE) {
return Err(InvalidTransaction::Eip7702NotSupported);
}
validate_priority_fee_tx(
tx.max_fee_per_gas(),
tx.max_priority_fee_per_gas().unwrap_or_default(),
base_fee,
)?;
let auth_list_len = tx.authorization_list_len();
if auth_list_len == 0 {
return Err(InvalidTransaction::EmptyAuthorizationList);
}
}
TransactionType::Custom => {
}
};
if !context.cfg().is_block_gas_limit_disabled() && tx.gas_limit() > context.block().gas_limit()
{
return Err(InvalidTransaction::CallerGasLimitMoreThanBlock);
}
if spec_id.is_enabled_in(SpecId::SHANGHAI) && tx.kind().is_create() {
let max_initcode_size = context.cfg().max_code_size().saturating_mul(2);
if context.tx().input().len() > max_initcode_size {
return Err(InvalidTransaction::CreateInitCodeSizeLimit);
}
}
Ok(())
}
pub fn validate_initial_tx_gas(
tx: impl Transaction,
spec: SpecId,
) -> Result<InitialAndFloorGas, InvalidTransaction> {
let gas = gas::calculate_initial_tx_gas_for_tx(&tx, spec);
if gas.initial_gas > tx.gas_limit() {
return Err(InvalidTransaction::CallGasCostMoreThanGasLimit {
gas_limit: tx.gas_limit(),
initial_gas: gas.initial_gas,
});
}
if spec.is_enabled_in(SpecId::PRAGUE) && gas.floor_gas > tx.gas_limit() {
return Err(InvalidTransaction::GasFloorMoreThanGasLimit {
gas_floor: gas.floor_gas,
gas_limit: tx.gas_limit(),
});
};
Ok(gas)
}
#[cfg(test)]
mod tests {
use crate::{ExecuteCommitEvm, MainBuilder, MainContext};
use bytecode::opcode;
use context::{
result::{EVMError, ExecutionResult, HaltReason, InvalidTransaction, Output},
Context, TxEnv,
};
use database::{CacheDB, EmptyDB};
use primitives::{address, Address, Bytes, TxKind, MAX_INITCODE_SIZE};
fn deploy_contract(
bytecode: Bytes,
) -> Result<ExecutionResult, EVMError<core::convert::Infallible>> {
let ctx = Context::mainnet().with_db(CacheDB::<EmptyDB>::default());
let mut evm = ctx.build_mainnet();
evm.transact_commit(TxEnv {
kind: TxKind::Create,
data: bytecode.clone(),
..Default::default()
})
}
#[test]
fn test_eip3860_initcode_size_limit_failure() {
let large_bytecode = vec![opcode::STOP; MAX_INITCODE_SIZE + 1];
let bytecode: Bytes = large_bytecode.into();
let result = deploy_contract(bytecode);
assert!(matches!(
result,
Err(EVMError::Transaction(
InvalidTransaction::CreateInitCodeSizeLimit
))
));
}
#[test]
fn test_eip3860_initcode_size_limit_success() {
let large_bytecode = vec![opcode::STOP; MAX_INITCODE_SIZE];
let bytecode: Bytes = large_bytecode.into();
let result = deploy_contract(bytecode);
assert!(matches!(result, Ok(ExecutionResult::Success { .. })));
}
#[test]
fn test_eip170_code_size_limit_failure() {
let init_code = vec![
0x62, 0x00, 0x60, 0x01, 0x60, 0x00, 0xf3, ];
let bytecode: Bytes = init_code.into();
let result = deploy_contract(bytecode);
assert!(matches!(
result,
Ok(ExecutionResult::Halt {
reason: HaltReason::CreateContractSizeLimit,
..
},)
));
}
#[test]
fn test_eip170_code_size_limit_success() {
let init_code = vec![
0x62, 0x00, 0x60, 0x00, 0x60, 0x00, 0xf3, ];
let bytecode: Bytes = init_code.into();
let result = deploy_contract(bytecode);
assert!(matches!(result, Ok(ExecutionResult::Success { .. },)));
}
#[test]
fn test_eip170_create_opcode_size_limit_failure() {
let factory_code = vec![
0x60, 0x01, 0x60, 0x00, 0x52, 0x62, 0x00, 0x60, 0x01, 0x60, 0x00, 0x60, 0x00, 0xf0, 0x60, 0x00, 0x52, 0x60, 0x20, 0x60, 0x00, 0xf3, ];
let factory_bytecode: Bytes = factory_code.into();
let factory_result =
deploy_contract(factory_bytecode).expect("factory contract deployment failed");
let factory_address = match &factory_result {
ExecutionResult::Success { output, .. } => match output {
Output::Create(bytes, _) | Output::Call(bytes) => Address::from_slice(&bytes[..20]),
},
_ => panic!("factory contract deployment failed"),
};
let tx_caller = address!("0x0000000000000000000000000000000000100000");
let call_result = Context::mainnet()
.with_db(CacheDB::<EmptyDB>::default())
.build_mainnet()
.transact_commit(TxEnv {
caller: tx_caller,
kind: TxKind::Call(factory_address),
data: Bytes::new(),
..Default::default()
})
.expect("call factory contract failed");
match &call_result {
ExecutionResult::Success { output, .. } => match output {
Output::Call(bytes) => {
if !bytes.is_empty() {
assert!(
bytes.iter().all(|&b| b == 0),
"When CREATE operation failed, it should return all zero address"
);
}
}
_ => panic!("unexpected output type"),
},
_ => panic!("execution result is not Success"),
}
}
#[test]
fn test_eip170_create_opcode_size_limit_success() {
let factory_code = vec![
0x60, 0x01, 0x60, 0x00, 0x52, 0x62, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xf0, 0x60, 0x00, 0x52, 0x60, 0x20, 0x60, 0x00, 0xf3, ];
let factory_bytecode: Bytes = factory_code.into();
let factory_result =
deploy_contract(factory_bytecode).expect("factory contract deployment failed");
let factory_address = match &factory_result {
ExecutionResult::Success { output, .. } => match output {
Output::Create(bytes, _) | Output::Call(bytes) => Address::from_slice(&bytes[..20]),
},
_ => panic!("factory contract deployment failed"),
};
let tx_caller = address!("0x0000000000000000000000000000000000100000");
let call_result = Context::mainnet()
.with_db(CacheDB::<EmptyDB>::default())
.build_mainnet()
.transact_commit(TxEnv {
caller: tx_caller,
kind: TxKind::Call(factory_address),
data: Bytes::new(),
..Default::default()
})
.expect("call factory contract failed");
match &call_result {
ExecutionResult::Success { output, .. } => {
match output {
Output::Call(bytes) => {
if !bytes.is_empty() {
assert!(bytes.iter().any(|&b| b != 0), "create sub contract failed");
}
}
_ => panic!("unexpected output type"),
}
}
_ => panic!("execution result is not Success"),
}
}
}