From 139c0c45627e7787ba460b361a3e55c4d984cb5b Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 13 Mar 2023 14:02:52 +0100 Subject: [PATCH] refactor(rpc): move revm utils to module (#1732) --- crates/rpc/rpc/src/eth/api/call.rs | 192 ++------------------------- crates/rpc/rpc/src/eth/mod.rs | 1 + crates/rpc/rpc/src/eth/revm_utils.rs | 174 ++++++++++++++++++++++++ 3 files changed, 187 insertions(+), 180 deletions(-) create mode 100644 crates/rpc/rpc/src/eth/revm_utils.rs diff --git a/crates/rpc/rpc/src/eth/api/call.rs b/crates/rpc/rpc/src/eth/api/call.rs index dc6e4146ab..cf92895e3c 100644 --- a/crates/rpc/rpc/src/eth/api/call.rs +++ b/crates/rpc/rpc/src/eth/api/call.rs @@ -1,16 +1,14 @@ //! Contains RPC handler implementations specific to endpoints that call/execute within evm. -#![allow(unused)] // TODO rm later - use crate::{ - eth::error::{EthApiError, EthResult, InvalidTransactionError, RevertError}, + eth::{ + error::{EthApiError, EthResult, InvalidTransactionError, RevertError}, + revm_utils::{build_call_evm_env, get_precompiles, inspect, transact}, + }, EthApi, }; use ethers_core::utils::get_contract_address; -use reth_primitives::{ - AccessList, AccessListItem, AccessListWithGasUsed, Address, BlockId, BlockNumberOrTag, Bytes, - TransactionKind, H256, U128, U256, -}; +use reth_primitives::{AccessList, Address, BlockId, BlockNumberOrTag, U256}; use reth_provider::{BlockProvider, EvmEnvProvider, StateProvider, StateProviderFactory}; use reth_revm::{ access_list::AccessListInspector, @@ -22,12 +20,10 @@ use reth_rpc_types::{ }; use revm::{ db::{CacheDB, DatabaseRef}, - precompile::{Precompiles, SpecId as PrecompilesSpecId}, primitives::{ - ruint::Uint, BlockEnv, Bytecode, CfgEnv, Env, ExecutionResult, Halt, ResultAndState, - SpecId, TransactTo, TxEnv, + BlockEnv, Bytecode, CfgEnv, Env, ExecutionResult, Halt, ResultAndState, TransactTo, }, - Database, Inspector, + Database, }; // Gas per transaction not creating a contract. @@ -80,7 +76,7 @@ where &self, mut cfg: CfgEnv, block: BlockEnv, - mut request: CallRequest, + request: CallRequest, state: S, state_overrides: Option, ) -> EthResult<(ResultAndState, Env)> @@ -91,7 +87,7 @@ where // impls and providers cfg.disable_block_gas_limit = true; - let mut env = build_call_evm_env(cfg, block, request)?; + let env = build_call_evm_env(cfg, block, request)?; let mut db = SubState::new(State::new(state)); // apply state overrides @@ -120,7 +116,7 @@ where &self, cfg: CfgEnv, block: BlockEnv, - mut request: CallRequest, + request: CallRequest, state: S, ) -> EthResult where @@ -146,7 +142,7 @@ where let no_code_callee = code.map(|code| code.is_empty()).unwrap_or(true); if no_code_callee { // simple transfer, check if caller has sufficient funds - let mut available_funds = + let available_funds = db.basic(env.tx.caller)?.map(|acc| acc.balance).unwrap_or_default(); if env.tx.value > available_funds { return Err(InvalidTransactionError::InsufficientFundsForTransfer.into()) @@ -298,7 +294,7 @@ where // impls and providers cfg.disable_block_gas_limit = true; - let mut env = build_call_evm_env(cfg, block, request.clone())?; + let env = build_call_evm_env(cfg, block, request.clone())?; let mut db = SubState::new(State::new(state)); let from = request.from.unwrap_or_default(); @@ -329,170 +325,6 @@ where } } -/// Returns the addresses of the precompiles corresponding to the SpecId. -fn get_precompiles(spec_id: &SpecId) -> Vec { - let spec = match spec_id { - SpecId::FRONTIER | SpecId::FRONTIER_THAWING => return vec![], - SpecId::HOMESTEAD | SpecId::DAO_FORK | SpecId::TANGERINE | SpecId::SPURIOUS_DRAGON => { - PrecompilesSpecId::HOMESTEAD - } - SpecId::BYZANTIUM | SpecId::CONSTANTINOPLE | SpecId::PETERSBURG => { - PrecompilesSpecId::BYZANTIUM - } - SpecId::ISTANBUL | SpecId::MUIR_GLACIER => PrecompilesSpecId::ISTANBUL, - SpecId::BERLIN | - SpecId::LONDON | - SpecId::ARROW_GLACIER | - SpecId::GRAY_GLACIER | - SpecId::MERGE | - SpecId::SHANGHAI | - SpecId::CANCUN => PrecompilesSpecId::BERLIN, - SpecId::LATEST => PrecompilesSpecId::LATEST, - }; - Precompiles::new(spec).addresses().into_iter().map(Address::from).collect() -} - -/// Executes the [Env] against the given [Database] without committing state changes. -pub(crate) fn transact(db: S, env: Env) -> EthResult<(ResultAndState, Env)> -where - S: Database, - ::Error: Into, -{ - let mut evm = revm::EVM::with_env(env); - evm.database(db); - let res = evm.transact()?; - Ok((res, evm.env)) -} - -/// Executes the [Env] against the given [Database] without committing state changes. -pub(crate) fn inspect(db: S, env: Env, inspector: I) -> EthResult<(ResultAndState, Env)> -where - S: Database, - ::Error: Into, - I: Inspector, -{ - let mut evm = revm::EVM::with_env(env); - evm.database(db); - let res = evm.inspect(inspector)?; - Ok((res, evm.env)) -} - -/// Creates a new [Env] to be used for executing the [CallRequest] in `eth_call` -pub(crate) fn build_call_evm_env( - mut cfg: CfgEnv, - block: BlockEnv, - request: CallRequest, -) -> EthResult { - let tx = create_txn_env(&block, request)?; - Ok(Env { cfg, block, tx }) -} - -/// Configures a new [TxEnv] for the [CallRequest] -fn create_txn_env(block_env: &BlockEnv, request: CallRequest) -> EthResult { - let CallRequest { - from, - to, - gas_price, - max_fee_per_gas, - max_priority_fee_per_gas, - gas, - value, - data, - nonce, - access_list, - chain_id, - } = request; - - let CallFees { max_priority_fee_per_gas, gas_price } = - CallFees::ensure_fees(gas_price, max_fee_per_gas, max_priority_fee_per_gas)?; - - let gas_limit = gas.unwrap_or(block_env.gas_limit.min(U256::from(u64::MAX))); - - let env = TxEnv { - gas_limit: gas_limit.try_into().map_err(|_| InvalidTransactionError::GasUintOverflow)?, - nonce: nonce - .map(|n| n.try_into().map_err(|n| InvalidTransactionError::NonceTooHigh)) - .transpose()?, - caller: from.unwrap_or_default(), - gas_price, - gas_priority_fee: max_priority_fee_per_gas, - transact_to: to.map(TransactTo::Call).unwrap_or_else(TransactTo::create), - value: value.unwrap_or_default(), - data: data.map(|data| data.0).unwrap_or_default(), - chain_id: chain_id.map(|c| c.as_u64()), - access_list: access_list.map(AccessList::flattened).unwrap_or_default(), - }; - - Ok(env) -} - -/// Helper type for representing the fees of a [CallRequest] -struct CallFees { - /// EIP-1559 priority fee - max_priority_fee_per_gas: Option, - /// Unified gas price setting - /// - /// Will be `0` if unset in request - /// - /// `gasPrice` for legacy, - /// `maxFeePerGas` for EIP-1559 - gas_price: U256, -} - -// === impl CallFees === - -impl CallFees { - /// Ensures the fields of a [CallRequest] are not conflicting - fn ensure_fees( - call_gas_price: Option, - call_max_fee: Option, - call_priority_fee: Option, - ) -> EthResult { - match (call_gas_price, call_max_fee, call_priority_fee) { - (gas_price, None, None) => { - // request for a legacy transaction - // set everything to zero - let gas_price = gas_price.unwrap_or_default(); - Ok(CallFees { gas_price: U256::from(gas_price), max_priority_fee_per_gas: None }) - } - (None, max_fee_per_gas, max_priority_fee_per_gas) => { - // request for eip-1559 transaction - let max_fee = max_fee_per_gas.unwrap_or_default(); - - if let Some(max_priority) = max_priority_fee_per_gas { - if max_priority > max_fee { - // Fail early - return Err( - // `max_priority_fee_per_gas` is greater than the `max_fee_per_gas` - InvalidTransactionError::TipAboveFeeCap.into(), - ) - } - } - Ok(CallFees { - gas_price: U256::from(max_fee), - max_priority_fee_per_gas: max_priority_fee_per_gas.map(U256::from), - }) - } - (Some(gas_price), Some(max_fee_per_gas), Some(max_priority_fee_per_gas)) => { - Err(EthApiError::ConflictingRequestGasPriceAndTipSet { - gas_price, - max_fee_per_gas, - max_priority_fee_per_gas, - }) - } - (Some(gas_price), Some(max_fee_per_gas), None) => { - Err(EthApiError::ConflictingRequestGasPrice { gas_price, max_fee_per_gas }) - } - (Some(gas_price), None, Some(max_priority_fee_per_gas)) => { - Err(EthApiError::RequestLegacyGasPriceAndTipSet { - gas_price, - max_priority_fee_per_gas, - }) - } - } - } -} - /// Applies the given state overrides (a set of [AccountOverride]) to the [CacheDB]. fn apply_state_overrides(overrides: StateOverride, db: &mut CacheDB) -> EthResult<()> where diff --git a/crates/rpc/rpc/src/eth/mod.rs b/crates/rpc/rpc/src/eth/mod.rs index c4eea7869d..da465c50a5 100644 --- a/crates/rpc/rpc/src/eth/mod.rs +++ b/crates/rpc/rpc/src/eth/mod.rs @@ -6,6 +6,7 @@ pub(crate) mod error; mod filter; mod id_provider; mod pubsub; +pub(crate) mod revm_utils; mod signer; pub use api::{EthApi, EthApiSpec}; diff --git a/crates/rpc/rpc/src/eth/revm_utils.rs b/crates/rpc/rpc/src/eth/revm_utils.rs new file mode 100644 index 0000000000..63b7ac9b8f --- /dev/null +++ b/crates/rpc/rpc/src/eth/revm_utils.rs @@ -0,0 +1,174 @@ +//! utilities for working with revm + +use crate::eth::error::{EthApiError, EthResult, InvalidTransactionError}; +use reth_primitives::{AccessList, Address, U128, U256}; +use reth_rpc_types::CallRequest; +use revm::{ + precompile::{Precompiles, SpecId as PrecompilesSpecId}, + primitives::{BlockEnv, CfgEnv, Env, ResultAndState, SpecId, TransactTo, TxEnv}, + Database, Inspector, +}; + +/// Returns the addresses of the precompiles corresponding to the SpecId. +pub(crate) fn get_precompiles(spec_id: &SpecId) -> Vec { + let spec = match spec_id { + SpecId::FRONTIER | SpecId::FRONTIER_THAWING => return vec![], + SpecId::HOMESTEAD | SpecId::DAO_FORK | SpecId::TANGERINE | SpecId::SPURIOUS_DRAGON => { + PrecompilesSpecId::HOMESTEAD + } + SpecId::BYZANTIUM | SpecId::CONSTANTINOPLE | SpecId::PETERSBURG => { + PrecompilesSpecId::BYZANTIUM + } + SpecId::ISTANBUL | SpecId::MUIR_GLACIER => PrecompilesSpecId::ISTANBUL, + SpecId::BERLIN | + SpecId::LONDON | + SpecId::ARROW_GLACIER | + SpecId::GRAY_GLACIER | + SpecId::MERGE | + SpecId::SHANGHAI | + SpecId::CANCUN => PrecompilesSpecId::BERLIN, + SpecId::LATEST => PrecompilesSpecId::LATEST, + }; + Precompiles::new(spec).addresses().into_iter().map(Address::from).collect() +} + +/// Executes the [Env] against the given [Database] without committing state changes. +pub(crate) fn transact(db: S, env: Env) -> EthResult<(ResultAndState, Env)> +where + S: Database, + ::Error: Into, +{ + let mut evm = revm::EVM::with_env(env); + evm.database(db); + let res = evm.transact()?; + Ok((res, evm.env)) +} + +/// Executes the [Env] against the given [Database] without committing state changes. +pub(crate) fn inspect(db: S, env: Env, inspector: I) -> EthResult<(ResultAndState, Env)> +where + S: Database, + ::Error: Into, + I: Inspector, +{ + let mut evm = revm::EVM::with_env(env); + evm.database(db); + let res = evm.inspect(inspector)?; + Ok((res, evm.env)) +} + +/// Creates a new [Env] to be used for executing the [CallRequest] in `eth_call` +pub(crate) fn build_call_evm_env( + cfg: CfgEnv, + block: BlockEnv, + request: CallRequest, +) -> EthResult { + let tx = create_txn_env(&block, request)?; + Ok(Env { cfg, block, tx }) +} + +/// Configures a new [TxEnv] for the [CallRequest] +pub(crate) fn create_txn_env(block_env: &BlockEnv, request: CallRequest) -> EthResult { + let CallRequest { + from, + to, + gas_price, + max_fee_per_gas, + max_priority_fee_per_gas, + gas, + value, + data, + nonce, + access_list, + chain_id, + } = request; + + let CallFees { max_priority_fee_per_gas, gas_price } = + CallFees::ensure_fees(gas_price, max_fee_per_gas, max_priority_fee_per_gas)?; + + let gas_limit = gas.unwrap_or(block_env.gas_limit.min(U256::from(u64::MAX))); + + let env = TxEnv { + gas_limit: gas_limit.try_into().map_err(|_| InvalidTransactionError::GasUintOverflow)?, + nonce: nonce + .map(|n| n.try_into().map_err(|_| InvalidTransactionError::NonceTooHigh)) + .transpose()?, + caller: from.unwrap_or_default(), + gas_price, + gas_priority_fee: max_priority_fee_per_gas, + transact_to: to.map(TransactTo::Call).unwrap_or_else(TransactTo::create), + value: value.unwrap_or_default(), + data: data.map(|data| data.0).unwrap_or_default(), + chain_id: chain_id.map(|c| c.as_u64()), + access_list: access_list.map(AccessList::flattened).unwrap_or_default(), + }; + + Ok(env) +} + +/// Helper type for representing the fees of a [CallRequest] +pub(crate) struct CallFees { + /// EIP-1559 priority fee + max_priority_fee_per_gas: Option, + /// Unified gas price setting + /// + /// Will be `0` if unset in request + /// + /// `gasPrice` for legacy, + /// `maxFeePerGas` for EIP-1559 + gas_price: U256, +} + +// === impl CallFees === + +impl CallFees { + /// Ensures the fields of a [CallRequest] are not conflicting + fn ensure_fees( + call_gas_price: Option, + call_max_fee: Option, + call_priority_fee: Option, + ) -> EthResult { + match (call_gas_price, call_max_fee, call_priority_fee) { + (gas_price, None, None) => { + // request for a legacy transaction + // set everything to zero + let gas_price = gas_price.unwrap_or_default(); + Ok(CallFees { gas_price: U256::from(gas_price), max_priority_fee_per_gas: None }) + } + (None, max_fee_per_gas, max_priority_fee_per_gas) => { + // request for eip-1559 transaction + let max_fee = max_fee_per_gas.unwrap_or_default(); + + if let Some(max_priority) = max_priority_fee_per_gas { + if max_priority > max_fee { + // Fail early + return Err( + // `max_priority_fee_per_gas` is greater than the `max_fee_per_gas` + InvalidTransactionError::TipAboveFeeCap.into(), + ) + } + } + Ok(CallFees { + gas_price: U256::from(max_fee), + max_priority_fee_per_gas: max_priority_fee_per_gas.map(U256::from), + }) + } + (Some(gas_price), Some(max_fee_per_gas), Some(max_priority_fee_per_gas)) => { + Err(EthApiError::ConflictingRequestGasPriceAndTipSet { + gas_price, + max_fee_per_gas, + max_priority_fee_per_gas, + }) + } + (Some(gas_price), Some(max_fee_per_gas), None) => { + Err(EthApiError::ConflictingRequestGasPrice { gas_price, max_fee_per_gas }) + } + (Some(gas_price), None, Some(max_priority_fee_per_gas)) => { + Err(EthApiError::RequestLegacyGasPriceAndTipSet { + gas_price, + max_priority_fee_per_gas, + }) + } + } + } +}