mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
662 lines
22 KiB
Rust
662 lines
22 KiB
Rust
//! utilities for working with revm
|
|
|
|
use crate::eth::error::{EthApiError, EthResult, RpcInvalidTransactionError};
|
|
#[cfg(feature = "optimism")]
|
|
use reth_primitives::revm::env::fill_op_tx_env;
|
|
#[cfg(not(feature = "optimism"))]
|
|
use reth_primitives::revm::env::fill_tx_env;
|
|
use reth_primitives::{
|
|
revm::env::fill_tx_env_with_recovered, Address, TransactionSigned,
|
|
TransactionSignedEcRecovered, TxHash, TxKind, B256, U256,
|
|
};
|
|
use reth_rpc_types::{
|
|
state::{AccountOverride, StateOverride},
|
|
BlockOverrides, TransactionRequest,
|
|
};
|
|
#[cfg(feature = "optimism")]
|
|
use revm::primitives::{Bytes, OptimismFields};
|
|
use revm::{
|
|
db::CacheDB,
|
|
precompile::{PrecompileSpecId, Precompiles},
|
|
primitives::{
|
|
db::DatabaseRef, BlockEnv, Bytecode, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, SpecId,
|
|
TransactTo, TxEnv,
|
|
},
|
|
Database,
|
|
};
|
|
use std::cmp::min;
|
|
use tracing::trace;
|
|
|
|
/// Helper type that bundles various overrides for EVM Execution.
|
|
///
|
|
/// By `Default`, no overrides are included.
|
|
#[derive(Debug, Clone, Default)]
|
|
pub struct EvmOverrides {
|
|
/// Applies overrides to the state before execution.
|
|
pub state: Option<StateOverride>,
|
|
/// Applies overrides to the block before execution.
|
|
///
|
|
/// This is a `Box` because less common and only available in debug trace endpoints.
|
|
pub block: Option<Box<BlockOverrides>>,
|
|
}
|
|
|
|
impl EvmOverrides {
|
|
/// Creates a new instance with the given overrides
|
|
pub fn new(state: Option<StateOverride>, block: Option<Box<BlockOverrides>>) -> Self {
|
|
Self { state, block }
|
|
}
|
|
|
|
/// Creates a new instance with the given state overrides.
|
|
pub fn state(state: Option<StateOverride>) -> Self {
|
|
Self { state, block: None }
|
|
}
|
|
|
|
/// Returns `true` if the overrides contain state overrides.
|
|
pub fn has_state(&self) -> bool {
|
|
self.state.is_some()
|
|
}
|
|
}
|
|
|
|
impl From<Option<StateOverride>> for EvmOverrides {
|
|
fn from(state: Option<StateOverride>) -> Self {
|
|
Self::state(state)
|
|
}
|
|
}
|
|
|
|
/// Helper type to work with different transaction types when configuring the EVM env.
|
|
///
|
|
/// This makes it easier to handle errors.
|
|
pub trait FillableTransaction {
|
|
/// Returns the hash of the transaction.
|
|
fn hash(&self) -> TxHash;
|
|
|
|
/// Fill the transaction environment with the given transaction.
|
|
fn try_fill_tx_env(&self, tx_env: &mut TxEnv) -> EthResult<()>;
|
|
}
|
|
|
|
impl FillableTransaction for TransactionSignedEcRecovered {
|
|
fn hash(&self) -> TxHash {
|
|
self.hash
|
|
}
|
|
|
|
fn try_fill_tx_env(&self, tx_env: &mut TxEnv) -> EthResult<()> {
|
|
#[cfg(not(feature = "optimism"))]
|
|
fill_tx_env_with_recovered(tx_env, self);
|
|
|
|
#[cfg(feature = "optimism")]
|
|
{
|
|
let mut envelope_buf = Vec::with_capacity(self.length_without_header());
|
|
self.encode_enveloped(&mut envelope_buf);
|
|
fill_tx_env_with_recovered(tx_env, self, envelope_buf.into());
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
impl FillableTransaction for TransactionSigned {
|
|
fn hash(&self) -> TxHash {
|
|
self.hash
|
|
}
|
|
|
|
fn try_fill_tx_env(&self, tx_env: &mut TxEnv) -> EthResult<()> {
|
|
let signer =
|
|
self.recover_signer().ok_or_else(|| EthApiError::InvalidTransactionSignature)?;
|
|
#[cfg(not(feature = "optimism"))]
|
|
fill_tx_env(tx_env, self, signer);
|
|
|
|
#[cfg(feature = "optimism")]
|
|
{
|
|
let mut envelope_buf = Vec::with_capacity(self.length_without_header());
|
|
self.encode_enveloped(&mut envelope_buf);
|
|
fill_op_tx_env(tx_env, self, signer, envelope_buf.into());
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Returns the addresses of the precompiles corresponding to the SpecId.
|
|
#[inline]
|
|
pub(crate) fn get_precompiles(spec_id: SpecId) -> impl IntoIterator<Item = Address> {
|
|
let spec = PrecompileSpecId::from_spec_id(spec_id);
|
|
Precompiles::new(spec).addresses().copied().map(Address::from)
|
|
}
|
|
|
|
/// Prepares the [EnvWithHandlerCfg] for execution.
|
|
///
|
|
/// Does not commit any changes to the underlying database.
|
|
///
|
|
/// EVM settings:
|
|
/// - `disable_block_gas_limit` is set to `true`
|
|
/// - `disable_eip3607` is set to `true`
|
|
/// - `disable_base_fee` is set to `true`
|
|
/// - `nonce` is set to `None`
|
|
pub(crate) fn prepare_call_env<DB>(
|
|
mut cfg: CfgEnvWithHandlerCfg,
|
|
mut block: BlockEnv,
|
|
request: TransactionRequest,
|
|
gas_limit: u64,
|
|
db: &mut CacheDB<DB>,
|
|
overrides: EvmOverrides,
|
|
) -> EthResult<EnvWithHandlerCfg>
|
|
where
|
|
DB: DatabaseRef,
|
|
EthApiError: From<<DB as DatabaseRef>::Error>,
|
|
{
|
|
// we want to disable this in eth_call, since this is common practice used by other node
|
|
// impls and providers <https://github.com/foundry-rs/foundry/issues/4388>
|
|
cfg.disable_block_gas_limit = true;
|
|
|
|
// Disabled because eth_call is sometimes used with eoa senders
|
|
// See <https://github.com/paradigmxyz/reth/issues/1959>
|
|
cfg.disable_eip3607 = true;
|
|
|
|
// The basefee should be ignored for eth_call
|
|
// See:
|
|
// <https://github.com/ethereum/go-ethereum/blob/ee8e83fa5f6cb261dad2ed0a7bbcde4930c41e6c/internal/ethapi/api.go#L985>
|
|
cfg.disable_base_fee = true;
|
|
|
|
// apply block overrides, we need to apply them first so that they take effect when we we create
|
|
// the evm env via `build_call_evm_env`, e.g. basefee
|
|
if let Some(mut block_overrides) = overrides.block {
|
|
if let Some(block_hashes) = block_overrides.block_hash.take() {
|
|
// override block hashes
|
|
db.block_hashes
|
|
.extend(block_hashes.into_iter().map(|(num, hash)| (U256::from(num), hash)))
|
|
}
|
|
apply_block_overrides(*block_overrides, &mut block);
|
|
}
|
|
|
|
let request_gas = request.gas;
|
|
let mut env = build_call_evm_env(cfg, block, request)?;
|
|
// set nonce to None so that the next nonce is used when transacting the call
|
|
env.tx.nonce = None;
|
|
|
|
// apply state overrides
|
|
if let Some(state_overrides) = overrides.state {
|
|
apply_state_overrides(state_overrides, db)?;
|
|
}
|
|
|
|
if request_gas.is_none() {
|
|
// No gas limit was provided in the request, so we need to cap the transaction gas limit
|
|
if env.tx.gas_price > U256::ZERO {
|
|
// If gas price is specified, cap transaction gas limit with caller allowance
|
|
trace!(target: "rpc::eth::call", ?env, "Applying gas limit cap with caller allowance");
|
|
cap_tx_gas_limit_with_caller_allowance(db, &mut env.tx)?;
|
|
} else {
|
|
// If no gas price is specified, use maximum allowed gas limit. The reason for this is
|
|
// that both Erigon and Geth use pre-configured gas cap even if it's possible
|
|
// to derive the gas limit from the block:
|
|
// <https://github.com/ledgerwatch/erigon/blob/eae2d9a79cb70dbe30b3a6b79c436872e4605458/cmd/rpcdaemon/commands/trace_adhoc.go#L956
|
|
// https://github.com/ledgerwatch/erigon/blob/eae2d9a79cb70dbe30b3a6b79c436872e4605458/eth/ethconfig/config.go#L94>
|
|
trace!(target: "rpc::eth::call", ?env, "Applying gas limit cap as the maximum gas limit");
|
|
env.tx.gas_limit = gas_limit;
|
|
}
|
|
}
|
|
|
|
Ok(env)
|
|
}
|
|
|
|
/// Creates a new [EnvWithHandlerCfg] to be used for executing the [TransactionRequest] in
|
|
/// `eth_call`.
|
|
///
|
|
/// Note: this does _not_ access the Database to check the sender.
|
|
pub(crate) fn build_call_evm_env(
|
|
cfg: CfgEnvWithHandlerCfg,
|
|
block: BlockEnv,
|
|
request: TransactionRequest,
|
|
) -> EthResult<EnvWithHandlerCfg> {
|
|
let tx = create_txn_env(&block, request)?;
|
|
Ok(EnvWithHandlerCfg::new_with_cfg_env(cfg, block, tx))
|
|
}
|
|
|
|
/// Configures a new [TxEnv] for the [TransactionRequest]
|
|
///
|
|
/// All [TxEnv] fields are derived from the given [TransactionRequest], if fields are `None`, they
|
|
/// fall back to the [BlockEnv]'s settings.
|
|
pub(crate) fn create_txn_env(
|
|
block_env: &BlockEnv,
|
|
request: TransactionRequest,
|
|
) -> EthResult<TxEnv> {
|
|
// Ensure that if versioned hashes are set, they're not empty
|
|
if request.blob_versioned_hashes.as_ref().map_or(false, |hashes| hashes.is_empty()) {
|
|
return Err(RpcInvalidTransactionError::BlobTransactionMissingBlobHashes.into())
|
|
}
|
|
|
|
let TransactionRequest {
|
|
from,
|
|
to,
|
|
gas_price,
|
|
max_fee_per_gas,
|
|
max_priority_fee_per_gas,
|
|
gas,
|
|
value,
|
|
input,
|
|
nonce,
|
|
access_list,
|
|
chain_id,
|
|
blob_versioned_hashes,
|
|
max_fee_per_blob_gas,
|
|
..
|
|
} = request;
|
|
|
|
let CallFees { max_priority_fee_per_gas, gas_price, max_fee_per_blob_gas } =
|
|
CallFees::ensure_fees(
|
|
gas_price.map(U256::from),
|
|
max_fee_per_gas.map(U256::from),
|
|
max_priority_fee_per_gas.map(U256::from),
|
|
block_env.basefee,
|
|
blob_versioned_hashes.as_deref(),
|
|
max_fee_per_blob_gas.map(U256::from),
|
|
block_env.get_blob_gasprice().map(U256::from),
|
|
)?;
|
|
|
|
let gas_limit = gas.unwrap_or_else(|| block_env.gas_limit.min(U256::from(u64::MAX)).to());
|
|
let transact_to = match to {
|
|
Some(TxKind::Call(to)) => TransactTo::call(to),
|
|
_ => TransactTo::create(),
|
|
};
|
|
let env = TxEnv {
|
|
gas_limit: gas_limit.try_into().map_err(|_| RpcInvalidTransactionError::GasUintOverflow)?,
|
|
nonce,
|
|
caller: from.unwrap_or_default(),
|
|
gas_price,
|
|
gas_priority_fee: max_priority_fee_per_gas,
|
|
transact_to,
|
|
value: value.unwrap_or_default(),
|
|
data: input.try_into_unique_input()?.unwrap_or_default(),
|
|
chain_id,
|
|
access_list: access_list
|
|
.map(reth_rpc_types::AccessList::into_flattened)
|
|
.unwrap_or_default(),
|
|
// EIP-4844 fields
|
|
blob_hashes: blob_versioned_hashes.unwrap_or_default(),
|
|
max_fee_per_blob_gas,
|
|
#[cfg(feature = "optimism")]
|
|
optimism: OptimismFields { enveloped_tx: Some(Bytes::new()), ..Default::default() },
|
|
};
|
|
|
|
Ok(env)
|
|
}
|
|
|
|
/// Caps the configured [TxEnv] `gas_limit` with the allowance of the caller.
|
|
pub(crate) fn cap_tx_gas_limit_with_caller_allowance<DB>(
|
|
db: &mut DB,
|
|
env: &mut TxEnv,
|
|
) -> EthResult<()>
|
|
where
|
|
DB: Database,
|
|
EthApiError: From<<DB as Database>::Error>,
|
|
{
|
|
if let Ok(gas_limit) = caller_gas_allowance(db, env)?.try_into() {
|
|
env.gas_limit = gas_limit;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Calculates the caller gas allowance.
|
|
///
|
|
/// `allowance = (account.balance - tx.value) / tx.gas_price`
|
|
///
|
|
/// Returns an error if the caller has insufficient funds.
|
|
/// Caution: This assumes non-zero `env.gas_price`. Otherwise, zero allowance will be returned.
|
|
pub(crate) fn caller_gas_allowance<DB>(db: &mut DB, env: &TxEnv) -> EthResult<U256>
|
|
where
|
|
DB: Database,
|
|
EthApiError: From<<DB as Database>::Error>,
|
|
{
|
|
Ok(db
|
|
// Get the caller account.
|
|
.basic(env.caller)?
|
|
// Get the caller balance.
|
|
.map(|acc| acc.balance)
|
|
.unwrap_or_default()
|
|
// Subtract transferred value from the caller balance.
|
|
.checked_sub(env.value)
|
|
// Return error if the caller has insufficient funds.
|
|
.ok_or_else(|| RpcInvalidTransactionError::InsufficientFunds)?
|
|
// Calculate the amount of gas the caller can afford with the specified gas price.
|
|
.checked_div(env.gas_price)
|
|
// This will be 0 if gas price is 0. It is fine, because we check it before.
|
|
.unwrap_or_default())
|
|
}
|
|
|
|
/// Helper type for representing the fees of a [TransactionRequest]
|
|
pub(crate) struct CallFees {
|
|
/// EIP-1559 priority fee
|
|
max_priority_fee_per_gas: Option<U256>,
|
|
/// Unified gas price setting
|
|
///
|
|
/// Will be the configured `basefee` if unset in the request
|
|
///
|
|
/// `gasPrice` for legacy,
|
|
/// `maxFeePerGas` for EIP-1559
|
|
gas_price: U256,
|
|
/// Max Fee per Blob gas for EIP-4844 transactions
|
|
max_fee_per_blob_gas: Option<U256>,
|
|
}
|
|
|
|
// === impl CallFees ===
|
|
|
|
impl CallFees {
|
|
/// Ensures the fields of a [TransactionRequest] are not conflicting.
|
|
///
|
|
/// # EIP-4844 transactions
|
|
///
|
|
/// Blob transactions have an additional fee parameter `maxFeePerBlobGas`.
|
|
/// If the `maxFeePerBlobGas` or `blobVersionedHashes` are set we treat it as an EIP-4844
|
|
/// transaction.
|
|
///
|
|
/// Note: Due to the `Default` impl of [BlockEnv] (Some(0)) this assumes the `block_blob_fee` is
|
|
/// always `Some`
|
|
fn ensure_fees(
|
|
call_gas_price: Option<U256>,
|
|
call_max_fee: Option<U256>,
|
|
call_priority_fee: Option<U256>,
|
|
block_base_fee: U256,
|
|
blob_versioned_hashes: Option<&[B256]>,
|
|
max_fee_per_blob_gas: Option<U256>,
|
|
block_blob_fee: Option<U256>,
|
|
) -> EthResult<CallFees> {
|
|
/// Get the effective gas price of a transaction as specfified in EIP-1559 with relevant
|
|
/// checks.
|
|
fn get_effective_gas_price(
|
|
max_fee_per_gas: Option<U256>,
|
|
max_priority_fee_per_gas: Option<U256>,
|
|
block_base_fee: U256,
|
|
) -> EthResult<U256> {
|
|
match max_fee_per_gas {
|
|
Some(max_fee) => {
|
|
if max_fee < block_base_fee {
|
|
// `base_fee_per_gas` is greater than the `max_fee_per_gas`
|
|
return Err(RpcInvalidTransactionError::FeeCapTooLow.into())
|
|
}
|
|
if max_fee < max_priority_fee_per_gas.unwrap_or(U256::ZERO) {
|
|
return Err(
|
|
// `max_priority_fee_per_gas` is greater than the `max_fee_per_gas`
|
|
RpcInvalidTransactionError::TipAboveFeeCap.into(),
|
|
)
|
|
}
|
|
Ok(min(
|
|
max_fee,
|
|
block_base_fee
|
|
.checked_add(max_priority_fee_per_gas.unwrap_or(U256::ZERO))
|
|
.ok_or_else(|| {
|
|
EthApiError::from(RpcInvalidTransactionError::TipVeryHigh)
|
|
})?,
|
|
))
|
|
}
|
|
None => Ok(block_base_fee
|
|
.checked_add(max_priority_fee_per_gas.unwrap_or(U256::ZERO))
|
|
.ok_or_else(|| EthApiError::from(RpcInvalidTransactionError::TipVeryHigh))?),
|
|
}
|
|
}
|
|
|
|
let has_blob_hashes =
|
|
blob_versioned_hashes.as_ref().map(|blobs| !blobs.is_empty()).unwrap_or(false);
|
|
|
|
match (call_gas_price, call_max_fee, call_priority_fee, max_fee_per_blob_gas) {
|
|
(gas_price, None, None, None) => {
|
|
// either legacy transaction or no fee fields are specified
|
|
// when no fields are specified, set gas price to zero
|
|
let gas_price = gas_price.unwrap_or(U256::ZERO);
|
|
Ok(CallFees {
|
|
gas_price,
|
|
max_priority_fee_per_gas: None,
|
|
max_fee_per_blob_gas: has_blob_hashes.then_some(block_blob_fee).flatten(),
|
|
})
|
|
}
|
|
(None, max_fee_per_gas, max_priority_fee_per_gas, None) => {
|
|
// request for eip-1559 transaction
|
|
let effective_gas_price = get_effective_gas_price(
|
|
max_fee_per_gas,
|
|
max_priority_fee_per_gas,
|
|
block_base_fee,
|
|
)?;
|
|
let max_fee_per_blob_gas = has_blob_hashes.then_some(block_blob_fee).flatten();
|
|
|
|
Ok(CallFees {
|
|
gas_price: effective_gas_price,
|
|
max_priority_fee_per_gas,
|
|
max_fee_per_blob_gas,
|
|
})
|
|
}
|
|
(None, max_fee_per_gas, max_priority_fee_per_gas, Some(max_fee_per_blob_gas)) => {
|
|
// request for eip-4844 transaction
|
|
let effective_gas_price = get_effective_gas_price(
|
|
max_fee_per_gas,
|
|
max_priority_fee_per_gas,
|
|
block_base_fee,
|
|
)?;
|
|
// Ensure blob_hashes are present
|
|
if !has_blob_hashes {
|
|
// Blob transaction but no blob hashes
|
|
return Err(RpcInvalidTransactionError::BlobTransactionMissingBlobHashes.into())
|
|
}
|
|
|
|
Ok(CallFees {
|
|
gas_price: effective_gas_price,
|
|
max_priority_fee_per_gas,
|
|
max_fee_per_blob_gas: Some(max_fee_per_blob_gas),
|
|
})
|
|
}
|
|
_ => {
|
|
// this fallback covers incompatible combinations of fields
|
|
Err(EthApiError::ConflictingFeeFieldsInRequest)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Applies the given block overrides to the env
|
|
fn apply_block_overrides(overrides: BlockOverrides, env: &mut BlockEnv) {
|
|
let BlockOverrides {
|
|
number,
|
|
difficulty,
|
|
time,
|
|
gas_limit,
|
|
coinbase,
|
|
random,
|
|
base_fee,
|
|
block_hash: _,
|
|
} = overrides;
|
|
|
|
if let Some(number) = number {
|
|
env.number = number;
|
|
}
|
|
if let Some(difficulty) = difficulty {
|
|
env.difficulty = difficulty;
|
|
}
|
|
if let Some(time) = time {
|
|
env.timestamp = U256::from(time);
|
|
}
|
|
if let Some(gas_limit) = gas_limit {
|
|
env.gas_limit = U256::from(gas_limit);
|
|
}
|
|
if let Some(coinbase) = coinbase {
|
|
env.coinbase = coinbase;
|
|
}
|
|
if let Some(random) = random {
|
|
env.prevrandao = Some(random);
|
|
}
|
|
if let Some(base_fee) = base_fee {
|
|
env.basefee = base_fee;
|
|
}
|
|
}
|
|
|
|
/// Applies the given state overrides (a set of [AccountOverride]) to the [CacheDB].
|
|
pub(crate) fn apply_state_overrides<DB>(
|
|
overrides: StateOverride,
|
|
db: &mut CacheDB<DB>,
|
|
) -> EthResult<()>
|
|
where
|
|
DB: DatabaseRef,
|
|
EthApiError: From<<DB as DatabaseRef>::Error>,
|
|
{
|
|
for (account, account_overrides) in overrides {
|
|
apply_account_override(account, account_overrides, db)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Applies a single [AccountOverride] to the [CacheDB].
|
|
fn apply_account_override<DB>(
|
|
account: Address,
|
|
account_override: AccountOverride,
|
|
db: &mut CacheDB<DB>,
|
|
) -> EthResult<()>
|
|
where
|
|
DB: DatabaseRef,
|
|
EthApiError: From<<DB as DatabaseRef>::Error>,
|
|
{
|
|
// we need to fetch the account via the `DatabaseRef` to not update the state of the account,
|
|
// which is modified via `Database::basic_ref`
|
|
let mut account_info = DatabaseRef::basic_ref(db, account)?.unwrap_or_default();
|
|
|
|
if let Some(nonce) = account_override.nonce {
|
|
account_info.nonce = nonce.to();
|
|
}
|
|
if let Some(code) = account_override.code {
|
|
account_info.code = Some(Bytecode::new_raw(code));
|
|
}
|
|
if let Some(balance) = account_override.balance {
|
|
account_info.balance = balance;
|
|
}
|
|
|
|
db.insert_account_info(account, account_info);
|
|
|
|
// We ensure that not both state and state_diff are set.
|
|
// If state is set, we must mark the account as "NewlyCreated", so that the old storage
|
|
// isn't read from
|
|
match (account_override.state, account_override.state_diff) {
|
|
(Some(_), Some(_)) => return Err(EthApiError::BothStateAndStateDiffInOverride(account)),
|
|
(None, None) => {
|
|
// nothing to do
|
|
}
|
|
(Some(new_account_state), None) => {
|
|
db.replace_account_storage(
|
|
account,
|
|
new_account_state
|
|
.into_iter()
|
|
.map(|(slot, value)| (U256::from_be_bytes(slot.0), value))
|
|
.collect(),
|
|
)?;
|
|
}
|
|
(None, Some(account_state_diff)) => {
|
|
for (slot, value) in account_state_diff {
|
|
db.insert_account_storage(account, U256::from_be_bytes(slot.0), value)?;
|
|
}
|
|
}
|
|
};
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use reth_primitives::constants::GWEI_TO_WEI;
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_ensure_0_fallback() {
|
|
let CallFees { gas_price, .. } =
|
|
CallFees::ensure_fees(None, None, None, U256::from(99), None, None, Some(U256::ZERO))
|
|
.unwrap();
|
|
assert!(gas_price.is_zero());
|
|
}
|
|
|
|
#[test]
|
|
fn test_blob_fees() {
|
|
let CallFees { gas_price, max_fee_per_blob_gas, .. } =
|
|
CallFees::ensure_fees(None, None, None, U256::from(99), None, None, Some(U256::ZERO))
|
|
.unwrap();
|
|
assert!(gas_price.is_zero());
|
|
assert_eq!(max_fee_per_blob_gas, None);
|
|
|
|
let CallFees { gas_price, max_fee_per_blob_gas, .. } = CallFees::ensure_fees(
|
|
None,
|
|
None,
|
|
None,
|
|
U256::from(99),
|
|
Some(&[B256::from(U256::ZERO)]),
|
|
None,
|
|
Some(U256::from(99)),
|
|
)
|
|
.unwrap();
|
|
assert!(gas_price.is_zero());
|
|
assert_eq!(max_fee_per_blob_gas, Some(U256::from(99)));
|
|
}
|
|
|
|
#[test]
|
|
fn test_eip_1559_fees() {
|
|
let CallFees { gas_price, .. } = CallFees::ensure_fees(
|
|
None,
|
|
Some(U256::from(25 * GWEI_TO_WEI)),
|
|
Some(U256::from(15 * GWEI_TO_WEI)),
|
|
U256::from(15 * GWEI_TO_WEI),
|
|
None,
|
|
None,
|
|
Some(U256::ZERO),
|
|
)
|
|
.unwrap();
|
|
assert_eq!(gas_price, U256::from(25 * GWEI_TO_WEI));
|
|
|
|
let CallFees { gas_price, .. } = CallFees::ensure_fees(
|
|
None,
|
|
Some(U256::from(25 * GWEI_TO_WEI)),
|
|
Some(U256::from(5 * GWEI_TO_WEI)),
|
|
U256::from(15 * GWEI_TO_WEI),
|
|
None,
|
|
None,
|
|
Some(U256::ZERO),
|
|
)
|
|
.unwrap();
|
|
assert_eq!(gas_price, U256::from(20 * GWEI_TO_WEI));
|
|
|
|
let CallFees { gas_price, .. } = CallFees::ensure_fees(
|
|
None,
|
|
Some(U256::from(30 * GWEI_TO_WEI)),
|
|
Some(U256::from(30 * GWEI_TO_WEI)),
|
|
U256::from(15 * GWEI_TO_WEI),
|
|
None,
|
|
None,
|
|
Some(U256::ZERO),
|
|
)
|
|
.unwrap();
|
|
assert_eq!(gas_price, U256::from(30 * GWEI_TO_WEI));
|
|
|
|
let call_fees = CallFees::ensure_fees(
|
|
None,
|
|
Some(U256::from(30 * GWEI_TO_WEI)),
|
|
Some(U256::from(31 * GWEI_TO_WEI)),
|
|
U256::from(15 * GWEI_TO_WEI),
|
|
None,
|
|
None,
|
|
Some(U256::ZERO),
|
|
);
|
|
assert!(call_fees.is_err());
|
|
|
|
let call_fees = CallFees::ensure_fees(
|
|
None,
|
|
Some(U256::from(5 * GWEI_TO_WEI)),
|
|
Some(U256::from(GWEI_TO_WEI)),
|
|
U256::from(15 * GWEI_TO_WEI),
|
|
None,
|
|
None,
|
|
Some(U256::ZERO),
|
|
);
|
|
assert!(call_fees.is_err());
|
|
|
|
let call_fees = CallFees::ensure_fees(
|
|
None,
|
|
Some(U256::MAX),
|
|
Some(U256::MAX),
|
|
U256::from(5 * GWEI_TO_WEI),
|
|
None,
|
|
None,
|
|
Some(U256::ZERO),
|
|
);
|
|
assert!(call_fees.is_err());
|
|
}
|
|
}
|