mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
refactor(rpc): move revm utils to module (#1732)
This commit is contained in:
@ -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
|
||||
|
||||
@ -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};
|
||||
|
||||
174
crates/rpc/rpc/src/eth/revm_utils.rs
Normal file
174
crates/rpc/rpc/src/eth/revm_utils.rs
Normal 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,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user