-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6 from ithacaxyz/klkvr/move-evm-traits
feat: move `Evm` trait from reth
- Loading branch information
Showing
12 changed files
with
427 additions
and
111 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
//! Configuration types for EVM environment. | ||
use revm::primitives::{BlockEnv, CfgEnv, CfgEnvWithHandlerCfg, SpecId}; | ||
|
||
/// Container type that holds both the configuration and block environment for EVM execution. | ||
#[derive(Debug, Clone, Default)] | ||
pub struct EvmEnv<Spec = SpecId> { | ||
/// The configuration environment with handler settings | ||
pub cfg_env: CfgEnv, | ||
/// The block environment containing block-specific data | ||
pub block_env: BlockEnv, | ||
/// The spec id of the chain. Specifies which hardfork is currently active, `Spec` type will | ||
/// most likely be an enum over hardforks. | ||
pub spec: Spec, | ||
} | ||
|
||
impl<Spec> EvmEnv<Spec> { | ||
/// Create a new `EvmEnv` from its components. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `cfg_env_with_handler_cfg` - The configuration environment with handler settings | ||
/// * `block` - The block environment containing block-specific data | ||
pub const fn new(cfg_env: CfgEnv, block_env: BlockEnv, spec: Spec) -> Self { | ||
Self { cfg_env, spec, block_env } | ||
} | ||
|
||
/// Returns a reference to the block environment. | ||
pub const fn block_env(&self) -> &BlockEnv { | ||
&self.block_env | ||
} | ||
|
||
/// Returns a reference to the configuration environment. | ||
pub const fn cfg_env(&self) -> &CfgEnv { | ||
&self.cfg_env | ||
} | ||
|
||
/// Returns the spec id of the chain | ||
pub const fn spec_id(&self) -> &Spec { | ||
&self.spec | ||
} | ||
} | ||
|
||
impl From<(CfgEnvWithHandlerCfg, BlockEnv)> for EvmEnv { | ||
fn from((cfg_env_with_handler_cfg, block_env): (CfgEnvWithHandlerCfg, BlockEnv)) -> Self { | ||
let CfgEnvWithHandlerCfg { cfg_env, handler_cfg } = cfg_env_with_handler_cfg; | ||
Self { cfg_env, spec: handler_cfg.spec_id, block_env } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
//! Ethereum EVM implementation. | ||
use crate::{env::EvmEnv, evm::EvmFactory, Evm}; | ||
use alloc::vec::Vec; | ||
use alloy_primitives::{Address, Bytes, TxKind, U256}; | ||
use core::fmt::Debug; | ||
use revm::{ | ||
inspector_handle_register, | ||
primitives::{BlockEnv, CfgEnvWithHandlerCfg, EVMError, HandlerCfg, ResultAndState, TxEnv}, | ||
Database, GetInspector, | ||
}; | ||
|
||
/// Ethereum EVM implementation. | ||
#[derive(derive_more::Debug, derive_more::Deref, derive_more::DerefMut, derive_more::From)] | ||
#[debug(bound(DB::Error: Debug))] | ||
pub struct EthEvm<'a, EXT, DB: Database>(revm::Evm<'a, EXT, DB>); | ||
|
||
impl<EXT, DB: Database> Evm for EthEvm<'_, EXT, DB> { | ||
type DB = DB; | ||
type Tx = TxEnv; | ||
type Error = EVMError<DB::Error>; | ||
|
||
fn block(&self) -> &BlockEnv { | ||
self.0.block() | ||
} | ||
|
||
fn transact(&mut self, tx: Self::Tx) -> Result<ResultAndState, Self::Error> { | ||
*self.tx_mut() = tx; | ||
self.0.transact() | ||
} | ||
|
||
fn transact_system_call( | ||
&mut self, | ||
caller: Address, | ||
contract: Address, | ||
data: Bytes, | ||
) -> Result<ResultAndState, Self::Error> { | ||
#[allow(clippy::needless_update)] // side-effect of optimism fields | ||
let tx_env = TxEnv { | ||
caller, | ||
transact_to: TxKind::Call(contract), | ||
// Explicitly set nonce to None so revm does not do any nonce checks | ||
nonce: None, | ||
gas_limit: 30_000_000, | ||
value: U256::ZERO, | ||
data, | ||
// Setting the gas price to zero enforces that no value is transferred as part of the | ||
// call, and that the call will not count against the block's gas limit | ||
gas_price: U256::ZERO, | ||
// The chain ID check is not relevant here and is disabled if set to None | ||
chain_id: None, | ||
// Setting the gas priority fee to None ensures the effective gas price is derived from | ||
// the `gas_price` field, which we need to be zero | ||
gas_priority_fee: None, | ||
access_list: Vec::new(), | ||
// blob fields can be None for this tx | ||
blob_hashes: Vec::new(), | ||
max_fee_per_blob_gas: None, | ||
// TODO remove this once this crate is no longer built with optimism | ||
..Default::default() | ||
}; | ||
|
||
*self.tx_mut() = tx_env; | ||
|
||
let prev_block_env = self.block().clone(); | ||
|
||
// ensure the block gas limit is >= the tx | ||
self.block_mut().gas_limit = U256::from(self.tx().gas_limit); | ||
|
||
// disable the base fee check for this call by setting the base fee to zero | ||
self.block_mut().basefee = U256::ZERO; | ||
|
||
let res = self.0.transact(); | ||
|
||
// re-set the block env | ||
*self.block_mut() = prev_block_env; | ||
|
||
res | ||
} | ||
|
||
fn db_mut(&mut self) -> &mut Self::DB { | ||
&mut self.context.evm.db | ||
} | ||
} | ||
|
||
/// Factory producing [`EthEvm`]. | ||
#[derive(Debug, Default, Clone, Copy)] | ||
#[non_exhaustive] | ||
pub struct EthEvmFactory; | ||
|
||
impl EvmFactory<EvmEnv> for EthEvmFactory { | ||
type Evm<'a, DB: Database + 'a, I: 'a> = EthEvm<'a, I, DB>; | ||
|
||
fn create_evm<'a, DB: Database + 'a>(&self, db: DB, input: EvmEnv) -> Self::Evm<'a, DB, ()> { | ||
let cfg_env_with_handler_cfg = CfgEnvWithHandlerCfg { | ||
cfg_env: input.cfg_env, | ||
handler_cfg: HandlerCfg::new(input.spec), | ||
}; | ||
revm::Evm::builder() | ||
.with_db(db) | ||
.with_cfg_env_with_handler_cfg(cfg_env_with_handler_cfg) | ||
.with_block_env(input.block_env) | ||
.build() | ||
.into() | ||
} | ||
|
||
fn create_evm_with_inspector<'a, DB: Database + 'a, I: GetInspector<DB> + 'a>( | ||
&self, | ||
db: DB, | ||
input: EvmEnv, | ||
inspector: I, | ||
) -> Self::Evm<'a, DB, I> { | ||
let cfg_env_with_handler_cfg = CfgEnvWithHandlerCfg { | ||
cfg_env: input.cfg_env, | ||
handler_cfg: HandlerCfg::new(input.spec), | ||
}; | ||
revm::Evm::builder() | ||
.with_db(db) | ||
.with_external_context(inspector) | ||
.with_cfg_env_with_handler_cfg(cfg_env_with_handler_cfg) | ||
.with_block_env(input.block_env) | ||
.append_handler_register(inspector_handle_register) | ||
.build() | ||
.into() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,108 +1,70 @@ | ||
//! Abstraction over EVM. | ||
use alloy_primitives::{Address, Bytes}; | ||
use revm::{ | ||
primitives::{BlockEnv, ResultAndState}, | ||
Database, DatabaseCommit, GetInspector, | ||
}; | ||
|
||
/// An instance of an ethereum virtual machine. | ||
/// | ||
/// An EVM is commonly initialized with the corresponding block context and state and it's only | ||
/// purpose is to execute transactions. | ||
/// | ||
/// Executing a transaction will return the outcome of the transaction. | ||
pub trait Evm { | ||
/// Database type held by the EVM. | ||
type DB: Database; | ||
/// The transaction object that the EVM will execute. | ||
type Tx; | ||
/// The outcome of an executed transaction. | ||
// TODO: this doesn't quite fit `revm's` `ResultAndState` because the state is always separate | ||
// and only the state is committed. so perhaps we need `Kind` and `State`, although if we | ||
// make `commit(&Self::Outcome)` then this should still work. | ||
type Outcome; | ||
/// The error type that the EVM can return, in case the transaction execution failed, for | ||
/// example if the transaction was invalid. | ||
type Error; | ||
|
||
/// Reference to [`BlockEnv`]. | ||
fn block(&self) -> &BlockEnv; | ||
|
||
/// Executes a transaction and returns the outcome. | ||
fn transact(&mut self, tx: Self::Tx) -> Result<Self::Outcome, Self::Error>; | ||
fn transact(&mut self, tx: Self::Tx) -> Result<ResultAndState, Self::Error>; | ||
|
||
/// Executes a system call. | ||
fn transact_system_call( | ||
&mut self, | ||
caller: Address, | ||
contract: Address, | ||
data: Bytes, | ||
) -> Result<ResultAndState, Self::Error>; | ||
|
||
fn transact_commit(&mut self, tx: Self::Tx) -> Result<(), Self::Error>; | ||
/// Returns a mutable reference to the underlying database. | ||
fn db_mut(&mut self) -> &mut Self::DB; | ||
|
||
/// Executes a transaction and commits the state changes to the underlying database. | ||
fn transact_commit(&mut self, tx_env: Self::Tx) -> Result<ResultAndState, Self::Error> | ||
where | ||
Self::DB: DatabaseCommit, | ||
{ | ||
let result = self.transact(tx_env)?; | ||
self.db_mut().commit(result.state.clone()); | ||
|
||
fn commit(&mut self, state: &Self::Outcome) -> Result<(), Self::Error>; | ||
Ok(result) | ||
} | ||
} | ||
|
||
/// A type responsible for creating instances of an ethereum virtual machine given a certain input. | ||
pub trait EvmFactory { | ||
/// General purpose input, that can be used to configure the EVM. | ||
/// | ||
/// TODO: this would encapsulate the blockenv, settings, and database, this can also easily | ||
/// restricted on the callsite if we expect this to be a generic helper struct with certain | ||
/// settings | ||
type Input; | ||
pub trait EvmFactory<Input> { | ||
/// The EVM type that this factory creates. | ||
// TODO: this doesn't quite work because this would force use to use an enum approach for trace | ||
// evm for example, unless we | ||
type Evm: Evm; | ||
type Evm<'a, DB: Database + 'a, I: 'a>: Evm<DB = DB>; | ||
|
||
/// Creates a new instance of an EVM. | ||
fn create_evm(&self, input: Self::Input) -> Self::Evm; | ||
} | ||
|
||
/// An evm that can be configured with a certain tracer. | ||
/// | ||
/// TODO: this has the benefit that we can arbitrarily restrict the `EvmFactory::Evm` type to | ||
/// support certain tracers, accesslist, etc... | ||
pub trait TraceEvm<Tracer>: Evm { | ||
/// Traces a transaction and returns the outcome. | ||
/// | ||
/// This expects a mutable reference to the tracer, so the caller retains ownership of the | ||
/// tracer while the evm populates it when it transacts the transaction. | ||
fn trace(&mut self, tx: Self::Tx, tracer: &mut Tracer) -> Result<Self::Outcome, Self::Error>; | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
fn create_evm<'a, DB: Database + 'a>(&self, db: DB, input: Input) -> Self::Evm<'a, DB, ()>; | ||
|
||
// represents revm::Inspector types from the `revm_inspectors` repository | ||
struct AccessListInspector; | ||
struct TracingInspector; | ||
|
||
/// Encapsulates all the tx settings | ||
struct EthTxEnv; | ||
|
||
trait Primitives { | ||
type Header: Default; | ||
type Transaction: Default; | ||
} | ||
|
||
struct StateProviderBox; | ||
|
||
struct EthApi<E, Prim> { | ||
factory: E, | ||
primitives: Prim, | ||
} | ||
|
||
/// General purpose block input. | ||
struct BlockInput<H, S> { | ||
header: H, | ||
state: S, | ||
settings: (), | ||
} | ||
|
||
impl<E, Prim> EthApi<E, Prim> | ||
where | ||
Prim: Primitives, | ||
// TODO: this could probably be simplified with a helper `Eth` trait | ||
E: EvmFactory< | ||
Input: From<BlockInput<Prim::Header, StateProviderBox>>, | ||
Evm: TraceEvm<AccessListInspector> + TraceEvm<TracingInspector>, | ||
>, | ||
<<E as EvmFactory>::Evm as Evm>::Tx: From<Prim::Transaction>, | ||
{ | ||
fn create_access_list(&self) { | ||
let input = BlockInput { | ||
header: Prim::Header::default(), | ||
state: StateProviderBox, | ||
settings: (), | ||
}; | ||
|
||
let mut tracer = AccessListInspector; | ||
let mut evm = self.factory.create_evm(input.into()); | ||
let out = evm.trace(Prim::Transaction::default().into(), &mut tracer); | ||
} | ||
} | ||
/// Creates a new instance of an EVM with an inspector. | ||
fn create_evm_with_inspector<'a, DB: Database + 'a, I: GetInspector<DB> + 'a>( | ||
&self, | ||
db: DB, | ||
input: Input, | ||
inspector: I, | ||
) -> Self::Evm<'a, DB, I>; | ||
} |
Oops, something went wrong.