diff --git a/crates/primitives/src/env.rs b/crates/primitives/src/env.rs index 15a8002f13..fa29959a48 100644 --- a/crates/primitives/src/env.rs +++ b/crates/primitives/src/env.rs @@ -5,249 +5,236 @@ use crate::{ }; use core::cmp::{min, Ordering}; +/// EVM environment configuration. #[derive(Clone, Debug, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Env { + /// Configuration of the EVM itself. pub cfg: CfgEnv, + /// Configuration of the block the transaction is in. pub block: BlockEnv, + /// Configuration of the transaction that is being executed. pub tx: TxEnv, } -/// The block environment. -#[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct BlockEnv { - /// The number of ancestor blocks of this block (block height). - pub number: U256, - /// Coinbase or miner or address that created and signed the block. - /// - /// This is the receiver address of all the gas spent in the block. - pub coinbase: Address, - - /// The timestamp of the block in seconds since the UNIX epoch. - pub timestamp: U256, - /// The gas limit of the block. - pub gas_limit: U256, - /// The base fee per gas, added in the London upgrade with [EIP-1559]. - /// - /// [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 - pub basefee: U256, - /// The difficulty of the block. - /// - /// Unused after the Paris (AKA the merge) upgrade, and replaced by `prevrandao`. - pub difficulty: U256, - /// The output of the randomness beacon provided by the beacon chain. - /// - /// Replaces `difficulty` after the Paris (AKA the merge) upgrade with [EIP-4399]. - /// - /// NOTE: `prevrandao` can be found in a block in place of `mix_hash`. - /// - /// [EIP-4399]: https://eips.ethereum.org/EIPS/eip-4399 - pub prevrandao: Option, - /// Excess blob gas and blob gasprice. - /// See also [`calc_excess_blob_gas`](crate::calc_excess_blob_gas) - /// and [`calc_blob_gasprice`](crate::calc_blob_gasprice). - /// - /// Incorporated as part of the Cancun upgrade via [EIP-4844]. - /// - /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844 - pub blob_excess_gas_and_price: Option, -} - -/// Structure holding block blob excess gas and it calculates blob fee. -/// -/// Incorporated as part of the Cancun upgrade via [EIP-4844]. -/// -/// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844 -#[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct BlobExcessGasAndPrice { - pub excess_blob_gas: u64, - pub blob_gasprice: u128, -} - -impl BlobExcessGasAndPrice { - /// Creates a new instance by calculating the blob gas price with [`calc_blob_gasprice`]. - pub fn new(excess_blob_gas: u64) -> Self { - let blob_gasprice = calc_blob_gasprice(excess_blob_gas); - Self { - excess_blob_gas, - blob_gasprice, +impl Env { + /// Calculates the effective gas price of the transaction. + #[inline] + pub fn effective_gas_price(&self) -> U256 { + if let Some(priority_fee) = self.tx.gas_priority_fee { + min(self.tx.gas_price, self.block.basefee + priority_fee) + } else { + self.tx.gas_price } } -} - -#[cfg(feature = "optimism")] -#[derive(Clone, Debug, Default, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct OptimismFields { - pub source_hash: Option, - pub mint: Option, - pub is_system_transaction: Option, - /// An enveloped EIP-2718 typed transaction. This is used - /// to compute the L1 tx cost using the L1 block info, as - /// opposed to requiring downstream apps to compute the cost - /// externally. - /// This field is optional to allow the [TxEnv] to be constructed - /// for non-optimism chains when the `optimism` feature is enabled, - /// but the [CfgEnv] `optimism` field is set to false. - pub enveloped_tx: Option, -} -impl BlockEnv { - /// Takes `blob_excess_gas` saves it inside env - /// and calculates `blob_fee` with [`BlobGasAndFee`]. - pub fn set_blob_excess_gas_and_price(&mut self, excess_blob_gas: u64) { - self.blob_excess_gas_and_price = Some(BlobExcessGasAndPrice::new(excess_blob_gas)); - } - /// See [EIP-4844] and [`crate::calc_blob_gasprice`]. + /// Calculates the [EIP-4844] `data_fee` of the transaction. /// /// Returns `None` if `Cancun` is not enabled. This is enforced in [`Env::validate_block_env`]. /// /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844 #[inline] - pub fn get_blob_gasprice(&self) -> Option { - self.blob_excess_gas_and_price - .as_ref() - .map(|a| a.blob_gasprice) + pub fn calc_data_fee(&self) -> Option { + self.block.get_blob_gasprice().map(|blob_gas_price| { + U256::from(blob_gas_price).saturating_mul(U256::from(self.tx.get_total_blob_gas())) + }) } - /// Return `blob_excess_gas` header field. See [EIP-4844]. - /// - /// Returns `None` if `Cancun` is not enabled. This is enforced in [`Env::validate_block_env`]. - /// - /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844 + /// Validate the block environment. #[inline] - pub fn get_blob_excess_gas(&self) -> Option { - self.blob_excess_gas_and_price - .as_ref() - .map(|a| a.excess_blob_gas) + pub fn validate_block_env(&self) -> Result<(), InvalidHeader> { + // `prevrandao` is required for the merge + if SPEC::enabled(SpecId::MERGE) && self.block.prevrandao.is_none() { + return Err(InvalidHeader::PrevrandaoNotSet); + } + // `excess_blob_gas` is required for Cancun + if SPEC::enabled(SpecId::CANCUN) && self.block.blob_excess_gas_and_price.is_none() { + return Err(InvalidHeader::ExcessBlobGasNotSet); + } + Ok(()) } -} - -/// The transaction environment. -#[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct TxEnv { - /// Caller aka Author aka transaction signer. - pub caller: Address, - /// The gas limit of the transaction. - pub gas_limit: u64, - /// The gas price of the transaction. - pub gas_price: U256, - /// The destination of the transaction. - pub transact_to: TransactTo, - /// The value sent to `transact_to`. - pub value: U256, - /// The data of the transaction. - pub data: Bytes, - /// The nonce of the transaction. If set to `None`, no checks are performed. - pub nonce: Option, - - /// The chain ID of the transaction. If set to `None`, no checks are performed. - /// - /// Incorporated as part of the Spurious Dragon upgrade via [EIP-155]. - /// - /// [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 - pub chain_id: Option, - /// A list of addresses and storage keys that the transaction plans to access. - /// - /// Added in [EIP-2930]. + /// Validate transaction data that is set inside ENV and return error if something is wrong. /// - /// [EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 - pub access_list: Vec<(Address, Vec)>, + /// Return initial spend gas (Gas needed to execute transaction). + #[inline] + pub fn validate_tx(&self) -> Result<(), InvalidTransaction> { + #[cfg(feature = "optimism")] + if self.cfg.optimism { + // Do not allow for a system transaction to be processed if Regolith is enabled. + if self.tx.optimism.is_system_transaction.unwrap_or(false) + && SPEC::enabled(SpecId::REGOLITH) + { + return Err(InvalidTransaction::DepositSystemTxPostRegolith); + } - /// The priority fee per gas. - /// - /// Incorporated as part of the London upgrade via [EIP-1559]. - /// - /// [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 - pub gas_priority_fee: Option, + // Do not perform any extra validation for deposit transactions, they are pre-verified on L1. + if self.tx.optimism.source_hash.is_some() { + return Ok(()); + } + } - /// The list of blob versioned hashes. Per EIP there should be at least - /// one blob present if [`Self::max_fee_per_blob_gas`] is `Some`. - /// - /// Incorporated as part of the Cancun upgrade via [EIP-4844]. - /// - /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844 - pub blob_hashes: Vec, + let gas_limit = self.tx.gas_limit; + let effective_gas_price = self.effective_gas_price(); + let is_create = self.tx.transact_to.is_create(); - /// The max fee per blob gas. - /// - /// Incorporated as part of the Cancun upgrade via [EIP-4844]. - /// - /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844 - pub max_fee_per_blob_gas: Option, + // BASEFEE tx check + if SPEC::enabled(SpecId::LONDON) { + if let Some(priority_fee) = self.tx.gas_priority_fee { + if priority_fee > self.tx.gas_price { + // or gas_max_fee for eip1559 + return Err(InvalidTransaction::PriorityFeeGreaterThanMaxFee); + } + } + let basefee = self.block.basefee; - #[cfg_attr(feature = "serde", serde(flatten))] - #[cfg(feature = "optimism")] - pub optimism: OptimismFields, -} + // check minimal cost against basefee + if !self.cfg.is_base_fee_check_disabled() && effective_gas_price < basefee { + return Err(InvalidTransaction::GasPriceLessThanBasefee); + } + } -impl TxEnv { - /// See [EIP-4844] and [`Env::calc_data_fee`]. - /// - /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844 - #[inline] - pub fn get_total_blob_gas(&self) -> u64 { - GAS_PER_BLOB * self.blob_hashes.len() as u64 - } -} + // Check if gas_limit is more than block_gas_limit + if !self.cfg.is_block_gas_limit_disabled() && U256::from(gas_limit) > self.block.gas_limit { + return Err(InvalidTransaction::CallerGasLimitMoreThanBlock); + } -/// Transaction destination. -#[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum TransactTo { - /// Simple call to an address. - Call(Address), - /// Contract creation. - Create(CreateScheme), -} + // EIP-3860: Limit and meter initcode + if SPEC::enabled(SpecId::SHANGHAI) && is_create { + let max_initcode_size = self + .cfg + .limit_contract_code_size + .map(|limit| limit.saturating_mul(2)) + .unwrap_or(MAX_INITCODE_SIZE); + if self.tx.data.len() > max_initcode_size { + return Err(InvalidTransaction::CreateInitcodeSizeLimit); + } + } -impl TransactTo { - /// Calls the given address. - #[inline] - pub fn call(address: Address) -> Self { - Self::Call(address) - } + // Check if the transaction's chain id is correct + if let Some(tx_chain_id) = self.tx.chain_id { + if tx_chain_id != self.cfg.chain_id { + return Err(InvalidTransaction::InvalidChainId); + } + } - /// Creates a contract. - #[inline] - pub fn create() -> Self { - Self::Create(CreateScheme::Create) - } + // Check that access list is empty for transactions before BERLIN + if !SPEC::enabled(SpecId::BERLIN) && !self.tx.access_list.is_empty() { + return Err(InvalidTransaction::AccessListNotSupported); + } - /// Creates a contract with the given salt using `CREATE2`. - #[inline] - pub fn create2(salt: U256) -> Self { - Self::Create(CreateScheme::Create2 { salt }) - } + // - For CANCUN and later, check that the gas price is not more than the tx max + // - For before CANCUN, check that `blob_hashes` and `max_fee_per_blob_gas` are empty / not set + if SPEC::enabled(SpecId::CANCUN) { + // Presence of max_fee_per_blob_gas means that this is blob transaction. + if let Some(max) = self.tx.max_fee_per_blob_gas { + // ensure that the user was willing to at least pay the current blob gasprice + let price = self.block.get_blob_gasprice().expect("already checked"); + if U256::from(price) > max { + return Err(InvalidTransaction::BlobGasPriceGreaterThanMax); + } - /// Returns `true` if the transaction is `Call`. - #[inline] - pub fn is_call(&self) -> bool { - matches!(self, Self::Call(_)) + // there must be at least one blob + // assert len(tx.blob_versioned_hashes) > 0 + if self.tx.blob_hashes.is_empty() { + return Err(InvalidTransaction::EmptyBlobs); + } + + // The field `to` deviates slightly from the semantics with the exception + // that it MUST NOT be nil and therefore must always represent + // a 20-byte address. This means that blob transactions cannot + // have the form of a create transaction. + if self.tx.transact_to.is_create() { + return Err(InvalidTransaction::BlobCreateTransaction); + } + + // all versioned blob hashes must start with VERSIONED_HASH_VERSION_KZG + for blob in self.tx.blob_hashes.iter() { + if blob[0] != VERSIONED_HASH_VERSION_KZG { + return Err(InvalidTransaction::BlobVersionNotSupported); + } + } + + // ensure the total blob gas spent is at most equal to the limit + // assert blob_gas_used <= MAX_BLOB_GAS_PER_BLOCK + if self.tx.blob_hashes.len() > MAX_BLOB_NUMBER_PER_BLOCK as usize { + return Err(InvalidTransaction::TooManyBlobs); + } + } + } else { + if !self.tx.blob_hashes.is_empty() { + return Err(InvalidTransaction::BlobVersionedHashesNotSupported); + } + if self.tx.max_fee_per_blob_gas.is_some() { + return Err(InvalidTransaction::MaxFeePerBlobGasNotSupported); + } + } + + Ok(()) } - /// Returns `true` if the transaction is `Create` or `Create2`. + /// Validate transaction against state. #[inline] - pub fn is_create(&self) -> bool { - matches!(self, Self::Create(_)) - } -} + pub fn validate_tx_against_state( + &self, + account: &mut Account, + ) -> Result<(), InvalidTransaction> { + // EIP-3607: Reject transactions from senders with deployed code + // This EIP is introduced after london but there was no collision in past + // so we can leave it enabled always + if !self.cfg.is_eip3607_disabled() && account.info.code_hash != KECCAK_EMPTY { + return Err(InvalidTransaction::RejectCallerWithCode); + } -/// Create scheme. -#[derive(Clone, Copy, Eq, PartialEq, Debug)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum CreateScheme { - /// Legacy create scheme of `CREATE`. - Create, - /// Create scheme of `CREATE2`. - Create2 { - /// Salt. - salt: U256, - }, + // On Optimism, deposit transactions do not have verification on the nonce + // nor the balance of the account. + #[cfg(feature = "optimism")] + if self.cfg.optimism && self.tx.optimism.source_hash.is_some() { + return Ok(()); + } + + // Check that the transaction's nonce is correct + if let Some(tx) = self.tx.nonce { + let state = account.info.nonce; + match tx.cmp(&state) { + Ordering::Greater => { + return Err(InvalidTransaction::NonceTooHigh { tx, state }); + } + Ordering::Less => { + return Err(InvalidTransaction::NonceTooLow { tx, state }); + } + _ => {} + } + } + + let mut balance_check = U256::from(self.tx.gas_limit) + .checked_mul(self.tx.gas_price) + .and_then(|gas_cost| gas_cost.checked_add(self.tx.value)) + .ok_or(InvalidTransaction::OverflowPaymentInTransaction)?; + + if SpecId::enabled(self.cfg.spec_id, SpecId::CANCUN) { + let data_fee = self.calc_data_fee().expect("already checked"); + balance_check = balance_check + .checked_add(U256::from(data_fee)) + .ok_or(InvalidTransaction::OverflowPaymentInTransaction)?; + } + + // Check if account has enough balance for gas_limit*gas_price and value transfer. + // Transfer will be done inside `*_inner` functions. + if balance_check > account.info.balance { + if self.cfg.is_balance_check_disabled() { + // Add transaction cost to balance to ensure execution doesn't fail. + account.info.balance = balance_check; + } else { + return Err(InvalidTransaction::LackOfFundForMaxFee { + fee: self.tx.gas_limit, + balance: account.info.balance, + }); + } + } + + Ok(()) + } } /// EVM configuration. @@ -370,19 +357,6 @@ impl CfgEnv { } } -/// What bytecode analysis to perform. -#[derive(Clone, Default, Debug, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum AnalysisKind { - /// Do not perform bytecode analysis. - Raw, - /// Check the bytecode for validity. - Check, - /// Perform bytecode analysis. - #[default] - Analyse, -} - impl Default for CfgEnv { fn default() -> Self { Self { @@ -410,6 +384,78 @@ impl Default for CfgEnv { } } +/// The block environment. +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct BlockEnv { + /// The number of ancestor blocks of this block (block height). + pub number: U256, + /// Coinbase or miner or address that created and signed the block. + /// + /// This is the receiver address of all the gas spent in the block. + pub coinbase: Address, + + /// The timestamp of the block in seconds since the UNIX epoch. + pub timestamp: U256, + /// The gas limit of the block. + pub gas_limit: U256, + /// The base fee per gas, added in the London upgrade with [EIP-1559]. + /// + /// [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 + pub basefee: U256, + /// The difficulty of the block. + /// + /// Unused after the Paris (AKA the merge) upgrade, and replaced by `prevrandao`. + pub difficulty: U256, + /// The output of the randomness beacon provided by the beacon chain. + /// + /// Replaces `difficulty` after the Paris (AKA the merge) upgrade with [EIP-4399]. + /// + /// NOTE: `prevrandao` can be found in a block in place of `mix_hash`. + /// + /// [EIP-4399]: https://eips.ethereum.org/EIPS/eip-4399 + pub prevrandao: Option, + /// Excess blob gas and blob gasprice. + /// See also [`calc_excess_blob_gas`](crate::calc_excess_blob_gas) + /// and [`calc_blob_gasprice`](crate::calc_blob_gasprice). + /// + /// Incorporated as part of the Cancun upgrade via [EIP-4844]. + /// + /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844 + pub blob_excess_gas_and_price: Option, +} + +impl BlockEnv { + /// Takes `blob_excess_gas` saves it inside env + /// and calculates `blob_fee` with [`BlobGasAndFee`]. + pub fn set_blob_excess_gas_and_price(&mut self, excess_blob_gas: u64) { + self.blob_excess_gas_and_price = Some(BlobExcessGasAndPrice::new(excess_blob_gas)); + } + /// See [EIP-4844] and [`crate::calc_blob_gasprice`]. + /// + /// Returns `None` if `Cancun` is not enabled. This is enforced in [`Env::validate_block_env`]. + /// + /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844 + #[inline] + pub fn get_blob_gasprice(&self) -> Option { + self.blob_excess_gas_and_price + .as_ref() + .map(|a| a.blob_gasprice) + } + + /// Return `blob_excess_gas` header field. See [EIP-4844]. + /// + /// Returns `None` if `Cancun` is not enabled. This is enforced in [`Env::validate_block_env`]. + /// + /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844 + #[inline] + pub fn get_blob_excess_gas(&self) -> Option { + self.blob_excess_gas_and_price + .as_ref() + .map(|a| a.excess_blob_gas) + } +} + impl Default for BlockEnv { fn default() -> Self { Self { @@ -425,245 +471,207 @@ impl Default for BlockEnv { } } -impl Default for TxEnv { - fn default() -> Self { - Self { - caller: Address::ZERO, - gas_limit: u64::MAX, - gas_price: U256::ZERO, - gas_priority_fee: None, - transact_to: TransactTo::Call(Address::ZERO), // will do nothing - value: U256::ZERO, - data: Bytes::new(), - chain_id: None, - nonce: None, - access_list: Vec::new(), - blob_hashes: Vec::new(), - max_fee_per_blob_gas: None, - #[cfg(feature = "optimism")] - optimism: OptimismFields::default(), - } - } -} - -impl Env { - /// Calculates the effective gas price of the transaction. - #[inline] - pub fn effective_gas_price(&self) -> U256 { - if let Some(priority_fee) = self.tx.gas_priority_fee { - min(self.tx.gas_price, self.block.basefee + priority_fee) - } else { - self.tx.gas_price - } - } +/// The transaction environment. +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct TxEnv { + /// Caller aka Author aka transaction signer. + pub caller: Address, + /// The gas limit of the transaction. + pub gas_limit: u64, + /// The gas price of the transaction. + pub gas_price: U256, + /// The destination of the transaction. + pub transact_to: TransactTo, + /// The value sent to `transact_to`. + pub value: U256, + /// The data of the transaction. + pub data: Bytes, + /// The nonce of the transaction. If set to `None`, no checks are performed. + pub nonce: Option, - /// Calculates the [EIP-4844] `data_fee` of the transaction. + /// The chain ID of the transaction. If set to `None`, no checks are performed. /// - /// Returns `None` if `Cancun` is not enabled. This is enforced in [`Env::validate_block_env`]. + /// Incorporated as part of the Spurious Dragon upgrade via [EIP-155]. /// - /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844 - #[inline] - pub fn calc_data_fee(&self) -> Option { - self.block.get_blob_gasprice().map(|blob_gas_price| { - U256::from(blob_gas_price).saturating_mul(U256::from(self.tx.get_total_blob_gas())) - }) - } - - /// Validate the block environment. - #[inline] - pub fn validate_block_env(&self) -> Result<(), InvalidHeader> { - // `prevrandao` is required for the merge - if SPEC::enabled(SpecId::MERGE) && self.block.prevrandao.is_none() { - return Err(InvalidHeader::PrevrandaoNotSet); - } - // `excess_blob_gas` is required for Cancun - if SPEC::enabled(SpecId::CANCUN) && self.block.blob_excess_gas_and_price.is_none() { - return Err(InvalidHeader::ExcessBlobGasNotSet); - } - Ok(()) - } + /// [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + pub chain_id: Option, - /// Validate transaction data that is set inside ENV and return error if something is wrong. + /// A list of addresses and storage keys that the transaction plans to access. /// - /// Return initial spend gas (Gas needed to execute transaction). - #[inline] - pub fn validate_tx(&self) -> Result<(), InvalidTransaction> { - #[cfg(feature = "optimism")] - if self.cfg.optimism { - // Do not allow for a system transaction to be processed if Regolith is enabled. - if self.tx.optimism.is_system_transaction.unwrap_or(false) - && SPEC::enabled(SpecId::REGOLITH) - { - return Err(InvalidTransaction::DepositSystemTxPostRegolith); - } + /// Added in [EIP-2930]. + /// + /// [EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 + pub access_list: Vec<(Address, Vec)>, - // Do not perform any extra validation for deposit transactions, they are pre-verified on L1. - if self.tx.optimism.source_hash.is_some() { - return Ok(()); - } - } + /// The priority fee per gas. + /// + /// Incorporated as part of the London upgrade via [EIP-1559]. + /// + /// [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 + pub gas_priority_fee: Option, - let gas_limit = self.tx.gas_limit; - let effective_gas_price = self.effective_gas_price(); - let is_create = self.tx.transact_to.is_create(); + /// The list of blob versioned hashes. Per EIP there should be at least + /// one blob present if [`Self::max_fee_per_blob_gas`] is `Some`. + /// + /// Incorporated as part of the Cancun upgrade via [EIP-4844]. + /// + /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844 + pub blob_hashes: Vec, - // BASEFEE tx check - if SPEC::enabled(SpecId::LONDON) { - if let Some(priority_fee) = self.tx.gas_priority_fee { - if priority_fee > self.tx.gas_price { - // or gas_max_fee for eip1559 - return Err(InvalidTransaction::PriorityFeeGreaterThanMaxFee); - } - } - let basefee = self.block.basefee; + /// The max fee per blob gas. + /// + /// Incorporated as part of the Cancun upgrade via [EIP-4844]. + /// + /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844 + pub max_fee_per_blob_gas: Option, - // check minimal cost against basefee - if !self.cfg.is_base_fee_check_disabled() && effective_gas_price < basefee { - return Err(InvalidTransaction::GasPriceLessThanBasefee); - } - } + #[cfg_attr(feature = "serde", serde(flatten))] + #[cfg(feature = "optimism")] + pub optimism: OptimismFields, +} - // Check if gas_limit is more than block_gas_limit - if !self.cfg.is_block_gas_limit_disabled() && U256::from(gas_limit) > self.block.gas_limit { - return Err(InvalidTransaction::CallerGasLimitMoreThanBlock); - } +impl TxEnv { + /// See [EIP-4844] and [`Env::calc_data_fee`]. + /// + /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844 + #[inline] + pub fn get_total_blob_gas(&self) -> u64 { + GAS_PER_BLOB * self.blob_hashes.len() as u64 + } +} - // EIP-3860: Limit and meter initcode - if SPEC::enabled(SpecId::SHANGHAI) && is_create { - let max_initcode_size = self - .cfg - .limit_contract_code_size - .map(|limit| limit.saturating_mul(2)) - .unwrap_or(MAX_INITCODE_SIZE); - if self.tx.data.len() > max_initcode_size { - return Err(InvalidTransaction::CreateInitcodeSizeLimit); - } +impl Default for TxEnv { + fn default() -> Self { + Self { + caller: Address::ZERO, + gas_limit: u64::MAX, + gas_price: U256::ZERO, + gas_priority_fee: None, + transact_to: TransactTo::Call(Address::ZERO), // will do nothing + value: U256::ZERO, + data: Bytes::new(), + chain_id: None, + nonce: None, + access_list: Vec::new(), + blob_hashes: Vec::new(), + max_fee_per_blob_gas: None, + #[cfg(feature = "optimism")] + optimism: OptimismFields::default(), } + } +} - // Check if the transaction's chain id is correct - if let Some(tx_chain_id) = self.tx.chain_id { - if tx_chain_id != self.cfg.chain_id { - return Err(InvalidTransaction::InvalidChainId); - } - } +/// Structure holding block blob excess gas and it calculates blob fee. +/// +/// Incorporated as part of the Cancun upgrade via [EIP-4844]. +/// +/// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844 +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct BlobExcessGasAndPrice { + /// The excess blob gas of the block. + pub excess_blob_gas: u64, + /// The calculated blob gas price based on the `excess_blob_gas`, See [calc_blob_gasprice] + pub blob_gasprice: u128, +} - // Check that access list is empty for transactions before BERLIN - if !SPEC::enabled(SpecId::BERLIN) && !self.tx.access_list.is_empty() { - return Err(InvalidTransaction::AccessListNotSupported); +impl BlobExcessGasAndPrice { + /// Creates a new instance by calculating the blob gas price with [`calc_blob_gasprice`]. + pub fn new(excess_blob_gas: u64) -> Self { + let blob_gasprice = calc_blob_gasprice(excess_blob_gas); + Self { + excess_blob_gas, + blob_gasprice, } + } +} - // - For CANCUN and later, check that the gas price is not more than the tx max - // - For before CANCUN, check that `blob_hashes` and `max_fee_per_blob_gas` are empty / not set - if SPEC::enabled(SpecId::CANCUN) { - // Presence of max_fee_per_blob_gas means that this is blob transaction. - if let Some(max) = self.tx.max_fee_per_blob_gas { - // ensure that the user was willing to at least pay the current blob gasprice - let price = self.block.get_blob_gasprice().expect("already checked"); - if U256::from(price) > max { - return Err(InvalidTransaction::BlobGasPriceGreaterThanMax); - } - - // there must be at least one blob - // assert len(tx.blob_versioned_hashes) > 0 - if self.tx.blob_hashes.is_empty() { - return Err(InvalidTransaction::EmptyBlobs); - } - - // The field `to` deviates slightly from the semantics with the exception - // that it MUST NOT be nil and therefore must always represent - // a 20-byte address. This means that blob transactions cannot - // have the form of a create transaction. - if self.tx.transact_to.is_create() { - return Err(InvalidTransaction::BlobCreateTransaction); - } - - // all versioned blob hashes must start with VERSIONED_HASH_VERSION_KZG - for blob in self.tx.blob_hashes.iter() { - if blob[0] != VERSIONED_HASH_VERSION_KZG { - return Err(InvalidTransaction::BlobVersionNotSupported); - } - } +/// Additional [TxEnv] fields for optimism. +#[cfg(feature = "optimism")] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct OptimismFields { + pub source_hash: Option, + pub mint: Option, + /// Whether or not the transaction is a system transaction + pub is_system_transaction: Option, + /// An enveloped EIP-2718 typed transaction. This is used + /// to compute the L1 tx cost using the L1 block info, as + /// opposed to requiring downstream apps to compute the cost + /// externally. + /// This field is optional to allow the [TxEnv] to be constructed + /// for non-optimism chains when the `optimism` feature is enabled, + /// but the [CfgEnv] `optimism` field is set to false. + pub enveloped_tx: Option, +} - // ensure the total blob gas spent is at most equal to the limit - // assert blob_gas_used <= MAX_BLOB_GAS_PER_BLOCK - if self.tx.blob_hashes.len() > MAX_BLOB_NUMBER_PER_BLOCK as usize { - return Err(InvalidTransaction::TooManyBlobs); - } - } - } else { - if !self.tx.blob_hashes.is_empty() { - return Err(InvalidTransaction::BlobVersionedHashesNotSupported); - } - if self.tx.max_fee_per_blob_gas.is_some() { - return Err(InvalidTransaction::MaxFeePerBlobGasNotSupported); - } - } +/// Transaction destination. +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum TransactTo { + /// Simple call to an address. + Call(Address), + /// Contract creation. + Create(CreateScheme), +} - Ok(()) +impl TransactTo { + /// Calls the given address. + #[inline] + pub fn call(address: Address) -> Self { + Self::Call(address) } - /// Validate transaction against state. + /// Creates a contract. #[inline] - pub fn validate_tx_against_state( - &self, - account: &mut Account, - ) -> Result<(), InvalidTransaction> { - // EIP-3607: Reject transactions from senders with deployed code - // This EIP is introduced after london but there was no collision in past - // so we can leave it enabled always - if !self.cfg.is_eip3607_disabled() && account.info.code_hash != KECCAK_EMPTY { - return Err(InvalidTransaction::RejectCallerWithCode); - } - - // On Optimism, deposit transactions do not have verification on the nonce - // nor the balance of the account. - #[cfg(feature = "optimism")] - if self.cfg.optimism && self.tx.optimism.source_hash.is_some() { - return Ok(()); - } + pub fn create() -> Self { + Self::Create(CreateScheme::Create) + } - // Check that the transaction's nonce is correct - if let Some(tx) = self.tx.nonce { - let state = account.info.nonce; - match tx.cmp(&state) { - Ordering::Greater => { - return Err(InvalidTransaction::NonceTooHigh { tx, state }); - } - Ordering::Less => { - return Err(InvalidTransaction::NonceTooLow { tx, state }); - } - _ => {} - } - } + /// Creates a contract with the given salt using `CREATE2`. + #[inline] + pub fn create2(salt: U256) -> Self { + Self::Create(CreateScheme::Create2 { salt }) + } - let mut balance_check = U256::from(self.tx.gas_limit) - .checked_mul(self.tx.gas_price) - .and_then(|gas_cost| gas_cost.checked_add(self.tx.value)) - .ok_or(InvalidTransaction::OverflowPaymentInTransaction)?; + /// Returns `true` if the transaction is `Call`. + #[inline] + pub fn is_call(&self) -> bool { + matches!(self, Self::Call(_)) + } - if SpecId::enabled(self.cfg.spec_id, SpecId::CANCUN) { - let data_fee = self.calc_data_fee().expect("already checked"); - balance_check = balance_check - .checked_add(U256::from(data_fee)) - .ok_or(InvalidTransaction::OverflowPaymentInTransaction)?; - } + /// Returns `true` if the transaction is `Create` or `Create2`. + #[inline] + pub fn is_create(&self) -> bool { + matches!(self, Self::Create(_)) + } +} - // Check if account has enough balance for gas_limit*gas_price and value transfer. - // Transfer will be done inside `*_inner` functions. - if balance_check > account.info.balance { - if self.cfg.is_balance_check_disabled() { - // Add transaction cost to balance to ensure execution doesn't fail. - account.info.balance = balance_check; - } else { - return Err(InvalidTransaction::LackOfFundForMaxFee { - fee: self.tx.gas_limit, - balance: account.info.balance, - }); - } - } +/// Create scheme. +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum CreateScheme { + /// Legacy create scheme of `CREATE`. + Create, + /// Create scheme of `CREATE2`. + Create2 { + /// Salt. + salt: U256, + }, +} - Ok(()) - } +/// What bytecode analysis to perform. +#[derive(Clone, Default, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum AnalysisKind { + /// Do not perform bytecode analysis. + Raw, + /// Check the bytecode for validity. + Check, + /// Perform bytecode analysis. + #[default] + Analyse, } #[cfg(test)]