diff --git a/crates/rpc/rpc/src/eth/api/call.rs b/crates/rpc/rpc/src/eth/api/call.rs index 63d8d1d3d..f7039b3ea 100644 --- a/crates/rpc/rpc/src/eth/api/call.rs +++ b/crates/rpc/rpc/src/eth/api/call.rs @@ -4,8 +4,8 @@ use crate::{ eth::{ error::{ensure_success, EthApiError, EthResult, RevertError, RpcInvalidTransactionError}, revm_utils::{ - build_call_evm_env, cap_tx_gas_limit_with_caller_allowance, get_precompiles, inspect, - transact, EvmOverrides, + build_call_evm_env, caller_gas_allowance, cap_tx_gas_limit_with_caller_allowance, + get_precompiles, inspect, transact, EvmOverrides, }, EthTransactions, }, @@ -122,19 +122,8 @@ where } // check funds of the sender - let gas_price = env.tx.gas_price; - if gas_price > U256::ZERO { - let mut available_funds = - db.basic(env.tx.caller)?.map(|acc| acc.balance).unwrap_or_default(); - if env.tx.value > available_funds { - return Err(RpcInvalidTransactionError::InsufficientFunds.into()) - } - // subtract transferred value from available funds - // SAFETY: value < available_funds, checked above - available_funds -= env.tx.value; - // amount of gas the sender can afford with the `gas_price` - // SAFETY: gas_price not zero - let allowance = available_funds.checked_div(gas_price).unwrap_or_default(); + if env.tx.gas_price > U256::ZERO { + let allowance = caller_gas_allowance(&mut db, &env.tx)?; if highest_gas_limit > allowance { // cap the highest gas limit by max gas caller can afford with given gas price diff --git a/crates/rpc/rpc/src/eth/revm_utils.rs b/crates/rpc/rpc/src/eth/revm_utils.rs index b0a66e661..54e996870 100644 --- a/crates/rpc/rpc/src/eth/revm_utils.rs +++ b/crates/rpc/rpc/src/eth/revm_utils.rs @@ -317,31 +317,45 @@ pub(crate) fn create_txn_env(block_env: &BlockEnv, request: CallRequest) -> EthR } /// Caps the configured [TxEnv] `gas_limit` with the allowance of the caller. -/// -/// Returns an error if the caller has insufficient funds -pub(crate) fn cap_tx_gas_limit_with_caller_allowance( - mut db: DB, - env: &mut TxEnv, -) -> EthResult<()> +pub(crate) fn cap_tx_gas_limit_with_caller_allowance(db: DB, env: &mut TxEnv) -> EthResult<()> where DB: Database, EthApiError: From<::Error>, { - let mut allowance = db.basic(env.caller)?.map(|acc| acc.balance).unwrap_or_default(); - - // subtract transferred value - allowance = allowance - .checked_sub(env.value) - .ok_or_else(|| RpcInvalidTransactionError::InsufficientFunds)?; - - // cap the gas limit - if let Ok(gas_limit) = allowance.checked_div(env.gas_price).unwrap_or_default().try_into() { + 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(mut db: DB, env: &TxEnv) -> EthResult +where + DB: Database, + EthApiError: From<::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 [CallRequest] pub(crate) struct CallFees { /// EIP-1559 priority fee