refactor(examples): Use OpEvm from op-alloy-evm instead of op-revm for CustomEvm in custom_node example (#16417)

This commit is contained in:
Roman Hodulák
2025-05-22 18:14:55 +02:00
committed by GitHub
parent b347d9d97b
commit 5483a8ed97
2 changed files with 128 additions and 182 deletions

View File

@@ -1,21 +1,18 @@
use crate::evm::{CustomEvmTransaction, CustomTxEnv};
use alloy_evm::{precompiles::PrecompilesMap, Database, Evm, EvmEnv, EvmFactory};
use alloy_primitives::{Address, Bytes, TxKind, U256};
use op_alloy_consensus::OpTxType;
use alloy_op_evm::{OpEvm, OpEvmFactory};
use alloy_primitives::{Address, Bytes};
use op_revm::{
precompiles::OpPrecompiles, transaction::deposit::DepositTransactionParts, DefaultOp,
L1BlockInfo, OpBuilder, OpHaltReason, OpSpecId, OpTransaction, OpTransactionError,
precompiles::OpPrecompiles, L1BlockInfo, OpContext, OpHaltReason, OpSpecId, OpTransaction,
OpTransactionError,
};
use reth_ethereum::evm::revm::{
context::{result::ResultAndState, BlockEnv, CfgEnv, TxEnv},
handler::{instructions::EthInstructions, PrecompileProvider},
interpreter::{interpreter::EthInterpreter, InterpreterResult},
context::{result::ResultAndState, BlockEnv, CfgEnv},
handler::PrecompileProvider,
interpreter::InterpreterResult,
Context, Inspector, Journal,
};
use revm::{
context_interface::result::EVMError, handler::EvmTr, inspector::NoOpInspector, ExecuteEvm,
InspectEvm,
};
use revm::{context_interface::result::EVMError, inspector::NoOpInspector};
use std::error::Error;
/// EVM context contains data that EVM needs for execution of [`CustomEvmTransaction`].
@@ -23,16 +20,20 @@ pub type CustomContext<DB> =
Context<BlockEnv, OpTransaction<CustomTxEnv>, CfgEnv<OpSpecId>, DB, Journal<DB>, L1BlockInfo>;
pub struct CustomEvm<DB: Database, I, P = OpPrecompiles> {
inner:
op_revm::OpEvm<CustomContext<DB>, I, EthInstructions<EthInterpreter, CustomContext<DB>>, P>,
inspect: bool,
inner: OpEvm<DB, I, P>,
}
impl<DB: Database, I, P> CustomEvm<DB, I, P> {
pub fn new(op: OpEvm<DB, I, P>) -> Self {
Self { inner: op }
}
}
impl<DB, I, P> Evm for CustomEvm<DB, I, P>
where
DB: Database,
I: Inspector<CustomContext<DB>>,
P: PrecompileProvider<CustomContext<DB>, Output = InterpreterResult>,
I: Inspector<OpContext<DB>>,
P: PrecompileProvider<OpContext<DB>, Output = InterpreterResult>,
{
type DB = DB;
type Tx = CustomEvmTransaction;
@@ -43,22 +44,20 @@ where
type Inspector = I;
fn block(&self) -> &BlockEnv {
&self.inner.ctx_ref().block
self.inner.block()
}
fn chain_id(&self) -> u64 {
self.inner.ctx_ref().cfg.chain_id
self.inner.chain_id()
}
fn transact_raw(
&mut self,
tx: Self::Tx,
) -> Result<ResultAndState<Self::HaltReason>, Self::Error> {
if self.inspect {
self.inner.set_tx(tx.0);
self.inner.inspect_replay()
} else {
self.inner.transact(tx.0)
match tx {
CustomEvmTransaction::Op(tx) => self.inner.transact_raw(tx),
CustomEvmTransaction::Payment(..) => todo!(),
}
}
@@ -68,116 +67,43 @@ where
contract: Address,
data: Bytes,
) -> Result<ResultAndState<Self::HaltReason>, Self::Error> {
let tx = CustomEvmTransaction(OpTransaction {
base: CustomTxEnv(TxEnv {
caller,
kind: TxKind::Call(contract),
// Explicitly set nonce to 0 so revm does not do any nonce checks
nonce: 0,
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: 0,
// 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: Default::default(),
// blob fields can be None for this tx
blob_hashes: Vec::new(),
max_fee_per_blob_gas: 0,
tx_type: OpTxType::Deposit as u8,
authorization_list: Default::default(),
}),
// The L1 fee is not charged for the EIP-4788 transaction, submit zero bytes for the
// enveloped tx size.
enveloped_tx: Some(Bytes::default()),
deposit: Default::default(),
});
let mut gas_limit = tx.0.base.0.gas_limit;
let mut basefee = 0;
let mut disable_nonce_check = true;
// ensure the block gas limit is >= the tx
core::mem::swap(&mut self.inner.ctx().block.gas_limit, &mut gas_limit);
// disable the base fee check for this call by setting the base fee to zero
core::mem::swap(&mut self.inner.ctx().block.basefee, &mut basefee);
// disable the nonce check
core::mem::swap(&mut self.inner.ctx().cfg.disable_nonce_check, &mut disable_nonce_check);
let mut res = self.transact(tx);
// swap back to the previous gas limit
core::mem::swap(&mut self.inner.ctx().block.gas_limit, &mut gas_limit);
// swap back to the previous base fee
core::mem::swap(&mut self.inner.ctx().block.basefee, &mut basefee);
// swap back to the previous nonce check flag
core::mem::swap(&mut self.inner.ctx().cfg.disable_nonce_check, &mut disable_nonce_check);
// NOTE: We assume that only the contract storage is modified. Revm currently marks the
// caller and block beneficiary accounts as "touched" when we do the above transact calls,
// and includes them in the result.
//
// We're doing this state cleanup to make sure that changeset only includes the changed
// contract storage.
if let Ok(res) = &mut res {
res.state.retain(|addr, _| *addr == contract);
}
res
self.inner.transact_system_call(caller, contract, data)
}
fn db_mut(&mut self) -> &mut Self::DB {
&mut self.inner.ctx().journaled_state.database
self.inner.db_mut()
}
fn finish(self) -> (Self::DB, EvmEnv<Self::Spec>) {
let Context { block: block_env, cfg: cfg_env, journaled_state, .. } = self.inner.0.ctx;
(journaled_state.database, EvmEnv { block_env, cfg_env })
self.inner.finish()
}
fn set_inspector_enabled(&mut self, enabled: bool) {
self.inspect = enabled;
self.inner.set_inspector_enabled(enabled)
}
fn precompiles(&self) -> &Self::Precompiles {
&self.inner.0.precompiles
self.inner.precompiles()
}
fn precompiles_mut(&mut self) -> &mut Self::Precompiles {
&mut self.inner.0.precompiles
self.inner.precompiles_mut()
}
fn inspector(&self) -> &Self::Inspector {
&self.inner.0.inspector
self.inner.inspector()
}
fn inspector_mut(&mut self) -> &mut Self::Inspector {
&mut self.inner.0.inspector
self.inner.inspector_mut()
}
}
pub struct CustomEvmFactory;
impl CustomEvmFactory {
fn default_tx() -> CustomEvmTransaction {
CustomEvmTransaction(OpTransaction {
base: CustomTxEnv::default(),
enveloped_tx: Some(vec![0x00].into()),
deposit: DepositTransactionParts::default(),
})
}
}
pub struct CustomEvmFactory(OpEvmFactory);
impl EvmFactory for CustomEvmFactory {
type Evm<DB: Database, I: Inspector<CustomContext<DB>>> = CustomEvm<DB, I, Self::Precompiles>;
type Context<DB: Database> = CustomContext<DB>;
type Evm<DB: Database, I: Inspector<OpContext<DB>>> = CustomEvm<DB, I, Self::Precompiles>;
type Context<DB: Database> = OpContext<DB>;
type Tx = CustomEvmTransaction;
type Error<DBError: Error + Send + Sync + 'static> = EVMError<DBError, OpTransactionError>;
type HaltReason = OpHaltReason;
@@ -189,19 +115,7 @@ impl EvmFactory for CustomEvmFactory {
db: DB,
input: EvmEnv<Self::Spec>,
) -> Self::Evm<DB, NoOpInspector> {
let spec_id = input.cfg_env.spec;
CustomEvm {
inner: Context::op()
.with_tx(Self::default_tx().0)
.with_db(db)
.with_block(input.block_env)
.with_cfg(input.cfg_env)
.build_op_with_inspector(NoOpInspector {})
.with_precompiles(PrecompilesMap::from_static(
OpPrecompiles::new_with_spec(spec_id).precompiles(),
)),
inspect: false,
}
CustomEvm::new(self.0.create_evm(db, input))
}
fn create_evm_with_inspector<DB: Database, I: Inspector<Self::Context<DB>>>(
@@ -210,18 +124,6 @@ impl EvmFactory for CustomEvmFactory {
input: EvmEnv<Self::Spec>,
inspector: I,
) -> Self::Evm<DB, I> {
let spec_id = input.cfg_env.spec;
CustomEvm {
inner: Context::op()
.with_tx(Self::default_tx().0)
.with_db(db)
.with_block(input.block_env)
.with_cfg(input.cfg_env)
.build_op_with_inspector(inspector)
.with_precompiles(PrecompilesMap::from_static(
OpPrecompiles::new_with_spec(spec_id).precompiles(),
)),
inspect: true,
}
CustomEvm::new(self.0.create_evm_with_inspector(db, input, inspector))
}
}

View File

@@ -1,9 +1,5 @@
use crate::primitives::{CustomTransaction, CustomTransactionEnvelope, TxPayment};
use alloy_eips::{
eip2718::{EIP2930_TX_TYPE_ID, LEGACY_TX_TYPE_ID},
eip2930::AccessList,
Typed2718,
};
use alloy_eips::{eip2930::AccessList, Typed2718};
use alloy_evm::{FromRecoveredTx, FromTxWithEncoded, IntoTxEnv};
use alloy_primitives::{Address, Bytes, TxKind, B256, U256};
use op_revm::OpTransaction;
@@ -14,7 +10,10 @@ use reth_ethereum::evm::{primitives::TransactionEnv, revm::context::TxEnv};
///
/// [`Evm`]: alloy_evm::Evm
#[derive(Clone, Debug)]
pub struct CustomEvmTransaction(pub OpTransaction<CustomTxEnv>);
pub enum CustomEvmTransaction {
Op(OpTransaction<TxEnv>),
Payment(CustomTxEnv),
}
/// A transaction environment is a set of information related to an Ethereum transaction that can be
/// fed to [`Evm`] for execution.
@@ -34,63 +33,108 @@ impl revm::context::Transaction for CustomEvmTransaction {
Self: 'a;
fn tx_type(&self) -> u8 {
self.0.tx_type()
match self {
CustomEvmTransaction::Op(tx) => tx.tx_type(),
CustomEvmTransaction::Payment(tx) => tx.tx_type(),
}
}
fn caller(&self) -> Address {
self.0.caller()
match self {
CustomEvmTransaction::Op(tx) => tx.caller(),
CustomEvmTransaction::Payment(tx) => tx.caller(),
}
}
fn gas_limit(&self) -> u64 {
self.0.gas_limit()
match self {
CustomEvmTransaction::Op(tx) => tx.gas_limit(),
CustomEvmTransaction::Payment(tx) => tx.gas_limit(),
}
}
fn value(&self) -> U256 {
self.0.value()
match self {
CustomEvmTransaction::Op(tx) => tx.value(),
CustomEvmTransaction::Payment(tx) => tx.value(),
}
}
fn input(&self) -> &Bytes {
self.0.input()
match self {
CustomEvmTransaction::Op(tx) => tx.input(),
CustomEvmTransaction::Payment(tx) => tx.input(),
}
}
fn nonce(&self) -> u64 {
revm::context::Transaction::nonce(&self.0)
match self {
CustomEvmTransaction::Op(tx) => revm::context::Transaction::nonce(tx),
CustomEvmTransaction::Payment(tx) => revm::context::Transaction::nonce(tx),
}
}
fn kind(&self) -> TxKind {
self.0.kind()
match self {
CustomEvmTransaction::Op(tx) => tx.kind(),
CustomEvmTransaction::Payment(tx) => tx.kind(),
}
}
fn chain_id(&self) -> Option<u64> {
self.0.chain_id()
match self {
CustomEvmTransaction::Op(tx) => tx.chain_id(),
CustomEvmTransaction::Payment(tx) => tx.chain_id(),
}
}
fn gas_price(&self) -> u128 {
self.0.gas_price()
match self {
CustomEvmTransaction::Op(tx) => tx.gas_price(),
CustomEvmTransaction::Payment(tx) => tx.gas_price(),
}
}
fn access_list(&self) -> Option<impl Iterator<Item = Self::AccessListItem<'_>>> {
self.0.access_list()
Some(match self {
CustomEvmTransaction::Op(tx) => tx.base.access_list.iter(),
CustomEvmTransaction::Payment(tx) => tx.0.access_list.iter(),
})
}
fn blob_versioned_hashes(&self) -> &[B256] {
self.0.blob_versioned_hashes()
match self {
CustomEvmTransaction::Op(tx) => tx.blob_versioned_hashes(),
CustomEvmTransaction::Payment(tx) => tx.blob_versioned_hashes(),
}
}
fn max_fee_per_blob_gas(&self) -> u128 {
self.0.max_fee_per_blob_gas()
match self {
CustomEvmTransaction::Op(tx) => tx.max_fee_per_blob_gas(),
CustomEvmTransaction::Payment(tx) => tx.max_fee_per_blob_gas(),
}
}
fn authorization_list_len(&self) -> usize {
self.0.authorization_list_len()
match self {
CustomEvmTransaction::Op(tx) => tx.authorization_list_len(),
CustomEvmTransaction::Payment(tx) => tx.authorization_list_len(),
}
}
fn authorization_list(&self) -> impl Iterator<Item = Self::Authorization<'_>> {
self.0.authorization_list()
match self {
CustomEvmTransaction::Op(tx) => tx.base.authorization_list.iter(),
CustomEvmTransaction::Payment(tx) => tx.0.authorization_list.iter(),
}
}
fn max_priority_fee_per_gas(&self) -> Option<u128> {
self.0.max_priority_fee_per_gas()
match self {
CustomEvmTransaction::Op(tx) => tx.max_priority_fee_per_gas(),
CustomEvmTransaction::Payment(tx) => tx.max_priority_fee_per_gas(),
}
}
}
@@ -167,43 +211,49 @@ impl revm::context::Transaction for CustomTxEnv {
impl TransactionEnv for CustomTxEnv {
fn set_gas_limit(&mut self, gas_limit: u64) {
self.0.gas_limit = gas_limit;
self.0.set_gas_limit(gas_limit);
}
fn nonce(&self) -> u64 {
self.0.nonce
self.0.nonce()
}
fn set_nonce(&mut self, nonce: u64) {
self.0.nonce = nonce;
self.0.set_nonce(nonce);
}
fn set_access_list(&mut self, access_list: AccessList) {
self.0.access_list = access_list;
if self.0.tx_type == LEGACY_TX_TYPE_ID {
// if this was previously marked as legacy tx, this must be upgraded to eip2930 with an
// accesslist
self.0.tx_type = EIP2930_TX_TYPE_ID;
}
self.0.set_access_list(access_list);
}
}
impl TransactionEnv for CustomEvmTransaction {
fn set_gas_limit(&mut self, gas_limit: u64) {
self.0.base.set_gas_limit(gas_limit)
match self {
CustomEvmTransaction::Op(tx) => tx.set_gas_limit(gas_limit),
CustomEvmTransaction::Payment(tx) => tx.set_gas_limit(gas_limit),
}
}
fn nonce(&self) -> u64 {
self.0.base.nonce()
match self {
CustomEvmTransaction::Op(tx) => tx.nonce(),
CustomEvmTransaction::Payment(tx) => tx.nonce(),
}
}
fn set_nonce(&mut self, nonce: u64) {
self.0.base.set_nonce(nonce)
match self {
CustomEvmTransaction::Op(tx) => tx.set_nonce(nonce),
CustomEvmTransaction::Payment(tx) => tx.set_nonce(nonce),
}
}
fn set_access_list(&mut self, access_list: AccessList) {
self.0.base.set_access_list(access_list)
match self {
CustomEvmTransaction::Op(tx) => tx.set_access_list(access_list),
CustomEvmTransaction::Payment(tx) => tx.set_access_list(access_list),
}
}
}
@@ -269,33 +319,27 @@ impl FromTxWithEncoded<CustomTransactionEnvelope> for TxEnv {
impl FromRecoveredTx<CustomTransaction> for CustomEvmTransaction {
fn from_recovered_tx(tx: &CustomTransaction, sender: Address) -> Self {
Self(match tx {
match tx {
CustomTransaction::BuiltIn(tx) => {
let tx = OpTransaction::<TxEnv>::from_recovered_tx(tx, sender);
let base = CustomTxEnv(tx.base);
OpTransaction { base, enveloped_tx: tx.enveloped_tx, deposit: tx.deposit }
Self::Op(OpTransaction::from_recovered_tx(tx, sender))
}
CustomTransaction::Other(tx) => {
OpTransaction::new(CustomTxEnv(TxEnv::from_recovered_tx(tx, sender)))
Self::Payment(CustomTxEnv(TxEnv::from_recovered_tx(tx, sender)))
}
})
}
}
}
impl FromTxWithEncoded<CustomTransaction> for CustomEvmTransaction {
fn from_encoded_tx(tx: &CustomTransaction, sender: Address, encoded: Bytes) -> Self {
Self(match tx {
match tx {
CustomTransaction::BuiltIn(tx) => {
let tx = OpTransaction::<TxEnv>::from_encoded_tx(tx, sender, encoded);
let base = CustomTxEnv(tx.base);
OpTransaction { base, enveloped_tx: tx.enveloped_tx, deposit: tx.deposit }
Self::Op(OpTransaction::from_encoded_tx(tx, sender, encoded))
}
CustomTransaction::Other(tx) => {
OpTransaction::new(CustomTxEnv(TxEnv::from_encoded_tx(tx, sender, encoded)))
Self::Payment(CustomTxEnv(TxEnv::from_encoded_tx(tx, sender, encoded)))
}
})
}
}
}