Skip to content

Commit

Permalink
Merge pull request #6 from ithacaxyz/klkvr/move-evm-traits
Browse files Browse the repository at this point in the history
feat: move `Evm` trait from reth
  • Loading branch information
klkvr authored Jan 29, 2025
2 parents 0e53125 + 3dd45c1 commit cce13b9
Show file tree
Hide file tree
Showing 12 changed files with 427 additions and 111 deletions.
6 changes: 1 addition & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ jobs:
cache-on-failure: true
- name: cargo hack
run: |
cargo hack build --workspace --ignore-unknown-features --features ws --target wasm32-unknown-unknown
cargo hack build --workspace --ignore-unknown-features --features ws --target wasm32-unknown-unknown --no-default-features
wasm-wasi:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -207,10 +207,6 @@ jobs:
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- name: fetch deps
run: |
# Eagerly pull dependencies
time cargo metadata --format-version=1 --locked > /dev/null
- name: run zepter
run: |
cargo install zepter -f --locked
Expand Down
9 changes: 5 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ rustdoc-args = ["--cfg", "docsrs"]

[workspace.dependencies]
alloy-evm = { version = "0.1.0", path = "crates/evm", default-features = false }
alloy-op-evm = { version = "0.1.0", path = "crates/op-evm", default-features = false }

# alloy
alloy-eip2124 = { version = "0.1", default-features = false }
Expand All @@ -45,10 +46,10 @@ alloy-consensus = { version = "0.8", default-features = false }
alloy-primitives = { version = "0.8", default-features = false }

# revm
revm = { version = "18", default-features = false }
revm-interpreter = { version = "14", default-features = false }
revm-primitives = { version = "14", default-features = false }
revm = { version = "19", default-features = false }
revm-interpreter = { version = "15", default-features = false }
revm-primitives = { version = "15", default-features = false }

# misc
derive_more = { version = "1", default-features = false }
derive_more = { version = "1", default-features = false, features = ["full"] }
serde = { version = "1", default-features = false, features = ["derive"] }
8 changes: 6 additions & 2 deletions crates/evm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@ repository.workspace = true
workspace = true

[dependencies]
alloy-consensus.workspace = true
alloy-primitives.workspace = true
derive_more.workspace = true
revm.workspace = true


[features]
default = ["std"]
std = [
"alloy-consensus/std"
"alloy-primitives/std",
"derive_more/std",
"revm/std"
]
49 changes: 49 additions & 0 deletions crates/evm/src/env.rs
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 }
}
}
126 changes: 126 additions & 0 deletions crates/evm/src/eth.rs
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()
}
}
126 changes: 44 additions & 82 deletions crates/evm/src/evm.rs
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>;
}
Loading

0 comments on commit cce13b9

Please sign in to comment.