refactor(rpc): move revm utils to module (#1732)

This commit is contained in:
Matthias Seitz
2023-03-13 14:02:52 +01:00
committed by GitHub
parent e33e93c9a0
commit 139c0c4562
3 changed files with 187 additions and 180 deletions

View File

@ -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<StateOverride>,
) -> EthResult<(ResultAndState, Env)>
@ -91,7 +87,7 @@ where
// impls and providers <https://github.com/foundry-rs/foundry/issues/4388>
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<U256>
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 <https://github.com/foundry-rs/foundry/issues/4388>
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<reth_primitives::H160> {
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<S>(db: S, env: Env) -> EthResult<(ResultAndState, Env)>
where
S: Database,
<S as Database>::Error: Into<EthApiError>,
{
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<S, I>(db: S, env: Env, inspector: I) -> EthResult<(ResultAndState, Env)>
where
S: Database,
<S as Database>::Error: Into<EthApiError>,
I: Inspector<S>,
{
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<Env> {
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<TxEnv> {
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<U256>,
/// 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<U128>,
call_max_fee: Option<U128>,
call_priority_fee: Option<U128>,
) -> EthResult<CallFees> {
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<DB>(overrides: StateOverride, db: &mut CacheDB<DB>) -> EthResult<()>
where

View File

@ -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};

View File

@ -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<reth_primitives::H160> {
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<S>(db: S, env: Env) -> EthResult<(ResultAndState, Env)>
where
S: Database,
<S as Database>::Error: Into<EthApiError>,
{
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<S, I>(db: S, env: Env, inspector: I) -> EthResult<(ResultAndState, Env)>
where
S: Database,
<S as Database>::Error: Into<EthApiError>,
I: Inspector<S>,
{
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<Env> {
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<TxEnv> {
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<U256>,
/// 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<U128>,
call_max_fee: Option<U128>,
call_priority_fee: Option<U128>,
) -> EthResult<CallFees> {
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,
})
}
}
}
}