Skip to content

Commit 3943695

Browse files
committed
feat(examples): Add CustomEvm for the execution of CustomTransaction in the custom_node example
1 parent 2629b49 commit 3943695

File tree

3 files changed

+173
-2
lines changed

3 files changed

+173
-2
lines changed
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
use crate::evm::{CustomEvmTransaction, CustomTxEnv};
2+
use alloy_evm::{Database, Evm, EvmEnv};
3+
use alloy_primitives::{Address, Bytes, TxKind, U256};
4+
use op_alloy_consensus::OpTxType;
5+
use op_revm::{
6+
precompiles::OpPrecompiles, L1BlockInfo, OpHaltReason, OpSpecId, OpTransactionError,
7+
};
8+
use reth_ethereum::evm::revm::{
9+
context::{result::ResultAndState, BlockEnv, CfgEnv, TxEnv},
10+
handler::{instructions::EthInstructions, PrecompileProvider},
11+
interpreter::{interpreter::EthInterpreter, InterpreterResult},
12+
Context, Inspector, Journal,
13+
};
14+
use revm::{context_interface::result::EVMError, handler::EvmTr, ExecuteEvm, InspectEvm};
15+
16+
/// EVM context contains data that EVM needs for execution of [`CustomEvmTransaction`].
17+
pub type CustomContext<DB> =
18+
Context<BlockEnv, CustomEvmTransaction, CfgEnv<OpSpecId>, DB, Journal<DB>, L1BlockInfo>;
19+
20+
pub struct CustomEvm<DB: Database, I, P = OpPrecompiles> {
21+
inner:
22+
op_revm::OpEvm<CustomContext<DB>, I, EthInstructions<EthInterpreter, CustomContext<DB>>, P>,
23+
inspect: bool,
24+
}
25+
26+
impl<DB, I, P> Evm for CustomEvm<DB, I, P>
27+
where
28+
DB: Database,
29+
I: Inspector<CustomContext<DB>>,
30+
P: PrecompileProvider<CustomContext<DB>, Output = InterpreterResult>,
31+
{
32+
type DB = DB;
33+
type Tx = CustomEvmTransaction;
34+
type Error = EVMError<DB::Error, OpTransactionError>;
35+
type HaltReason = OpHaltReason;
36+
type Spec = OpSpecId;
37+
type Precompiles = P;
38+
type Inspector = I;
39+
40+
fn block(&self) -> &BlockEnv {
41+
&self.inner.ctx_ref().block
42+
}
43+
44+
fn chain_id(&self) -> u64 {
45+
self.inner.ctx_ref().cfg.chain_id
46+
}
47+
48+
fn transact_raw(
49+
&mut self,
50+
tx: Self::Tx,
51+
) -> Result<ResultAndState<Self::HaltReason>, Self::Error> {
52+
if self.inspect {
53+
self.inner.set_tx(tx);
54+
self.inner.inspect_replay()
55+
} else {
56+
self.inner.transact(tx)
57+
}
58+
}
59+
60+
fn transact_system_call(
61+
&mut self,
62+
caller: Address,
63+
contract: Address,
64+
data: Bytes,
65+
) -> Result<ResultAndState<Self::HaltReason>, Self::Error> {
66+
let tx = CustomEvmTransaction {
67+
base: CustomTxEnv(TxEnv {
68+
caller,
69+
kind: TxKind::Call(contract),
70+
// Explicitly set nonce to 0 so revm does not do any nonce checks
71+
nonce: 0,
72+
gas_limit: 30_000_000,
73+
value: U256::ZERO,
74+
data,
75+
// Setting the gas price to zero enforces that no value is transferred as part of
76+
// the call, and that the call will not count against the block's
77+
// gas limit
78+
gas_price: 0,
79+
// The chain ID check is not relevant here and is disabled if set to None
80+
chain_id: None,
81+
// Setting the gas priority fee to None ensures the effective gas price is derived
82+
// from the `gas_price` field, which we need to be zero
83+
gas_priority_fee: None,
84+
access_list: Default::default(),
85+
// blob fields can be None for this tx
86+
blob_hashes: Vec::new(),
87+
max_fee_per_blob_gas: 0,
88+
tx_type: OpTxType::Deposit as u8,
89+
authorization_list: Default::default(),
90+
}),
91+
// The L1 fee is not charged for the EIP-4788 transaction, submit zero bytes for the
92+
// enveloped tx size.
93+
enveloped_tx: Some(Bytes::default()),
94+
deposit: Default::default(),
95+
};
96+
97+
let mut gas_limit = tx.base.0.gas_limit;
98+
let mut basefee = 0;
99+
let mut disable_nonce_check = true;
100+
101+
// ensure the block gas limit is >= the tx
102+
core::mem::swap(&mut self.inner.ctx().block.gas_limit, &mut gas_limit);
103+
// disable the base fee check for this call by setting the base fee to zero
104+
core::mem::swap(&mut self.inner.ctx().block.basefee, &mut basefee);
105+
// disable the nonce check
106+
core::mem::swap(&mut self.inner.ctx().cfg.disable_nonce_check, &mut disable_nonce_check);
107+
108+
let mut res = self.transact(tx);
109+
110+
// swap back to the previous gas limit
111+
core::mem::swap(&mut self.inner.ctx().block.gas_limit, &mut gas_limit);
112+
// swap back to the previous base fee
113+
core::mem::swap(&mut self.inner.ctx().block.basefee, &mut basefee);
114+
// swap back to the previous nonce check flag
115+
core::mem::swap(&mut self.inner.ctx().cfg.disable_nonce_check, &mut disable_nonce_check);
116+
117+
// NOTE: We assume that only the contract storage is modified. Revm currently marks the
118+
// caller and block beneficiary accounts as "touched" when we do the above transact calls,
119+
// and includes them in the result.
120+
//
121+
// We're doing this state cleanup to make sure that changeset only includes the changed
122+
// contract storage.
123+
if let Ok(res) = &mut res {
124+
res.state.retain(|addr, _| *addr == contract);
125+
}
126+
127+
res
128+
}
129+
130+
fn db_mut(&mut self) -> &mut Self::DB {
131+
&mut self.inner.ctx().journaled_state.database
132+
}
133+
134+
fn finish(self) -> (Self::DB, EvmEnv<Self::Spec>) {
135+
let Context { block: block_env, cfg: cfg_env, journaled_state, .. } = self.inner.0.ctx;
136+
137+
(journaled_state.database, EvmEnv { block_env, cfg_env })
138+
}
139+
140+
fn set_inspector_enabled(&mut self, enabled: bool) {
141+
self.inspect = enabled;
142+
}
143+
144+
fn precompiles(&self) -> &Self::Precompiles {
145+
&self.inner.0.precompiles
146+
}
147+
148+
fn precompiles_mut(&mut self) -> &mut Self::Precompiles {
149+
&mut self.inner.0.precompiles
150+
}
151+
152+
fn inspector(&self) -> &Self::Inspector {
153+
&self.inner.0.inspector
154+
}
155+
156+
fn inspector_mut(&mut self) -> &mut Self::Inspector {
157+
&mut self.inner.0.inspector
158+
}
159+
}

examples/custom-node/src/evm/env.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,17 @@ use alloy_primitives::{Address, Bytes, TxKind, B256, U256};
55
use op_revm::OpTransaction;
66
use reth_ethereum::evm::revm::context::TxEnv;
77

8-
pub struct CustomTxEnv(TxEnv);
8+
/// An Optimism extended Ethereum transaction that can be fed to [`Evm`] because it contains
9+
/// [`CustomTxEnv`].
10+
///
11+
/// [`Evm`]: alloy_evm::Evm
12+
pub type CustomEvmTransaction = OpTransaction<CustomTxEnv>;
13+
14+
/// A transaction environment is a set of information related to an Ethereum transaction that can be
15+
/// fed to [`Evm`] for execution.
16+
///
17+
/// [`Evm`]: alloy_evm::Evm
18+
pub struct CustomTxEnv(pub TxEnv);
919

1020
impl revm::context::Transaction for CustomTxEnv {
1121
type AccessListItem<'a>
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
mod alloy;
12
mod assembler;
23
mod config;
34
mod env;
45
mod executor;
56

7+
pub use alloy::{CustomContext, CustomEvm};
68
pub use assembler::CustomBlockAssembler;
79
pub use config::CustomEvmConfig;
8-
pub use env::CustomTxEnv;
10+
pub use env::{CustomEvmTransaction, CustomTxEnv};
911
pub use executor::CustomBlockExecutor;

0 commit comments

Comments
 (0)