Skip to content

Commit 0df7fb1

Browse files
alexfertelmattsse
andauthored
feat(anvil): add support for injecting precompiles (#7589)
* feat(anvil): add support for injecting precompiles * test: check precompiles get injected * feat(docs): add a few doc comments * feat(docs): document with_extra_precompiles * ref: localize changes to the anvil crate * ref: rename with_extra_precompiles -> with_precompile_factory * lint(fmt): fix formatting * ref: fix invalid comment * ref: remove unnecessary generic bound * ref: revert formatting change * ref: extract evm creation to a method * fix: inject precompiles to the executor * lint(fmt): fix formatting * chore: add doc * nit --------- Co-authored-by: Matthias Seitz <[email protected]>
1 parent 1610c13 commit 0df7fb1

File tree

5 files changed

+161
-15
lines changed

5 files changed

+161
-15
lines changed

crates/anvil/src/config.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@ use crate::{
1111
fees::{INITIAL_BASE_FEE, INITIAL_GAS_PRICE},
1212
pool::transactions::TransactionOrder,
1313
},
14-
mem,
15-
mem::in_memory_db::MemDb,
16-
FeeManager, Hardfork,
14+
mem::{self, in_memory_db::MemDb},
15+
FeeManager, Hardfork, PrecompileFactory,
1716
};
1817
use alloy_genesis::Genesis;
1918
use alloy_network::AnyNetwork;
@@ -176,6 +175,8 @@ pub struct NodeConfig {
176175
pub slots_in_an_epoch: u64,
177176
/// The memory limit per EVM execution in bytes.
178177
pub memory_limit: Option<u64>,
178+
/// Factory used by `anvil` to extend the EVM's precompiles.
179+
pub precompile_factory: Option<Arc<dyn PrecompileFactory>>,
179180
}
180181

181182
impl NodeConfig {
@@ -422,6 +423,7 @@ impl Default for NodeConfig {
422423
enable_optimism: false,
423424
slots_in_an_epoch: 32,
424425
memory_limit: None,
426+
precompile_factory: None,
425427
}
426428
}
427429
}
@@ -834,6 +836,13 @@ impl NodeConfig {
834836
self
835837
}
836838

839+
/// Injects precompiles to `anvil`'s EVM.
840+
#[must_use]
841+
pub fn with_precompile_factory(mut self, factory: impl PrecompileFactory + 'static) -> Self {
842+
self.precompile_factory = Some(Arc::new(factory));
843+
self
844+
}
845+
837846
/// Configures everything related to env, backend and database and returns the
838847
/// [Backend](mem::Backend)
839848
///

crates/anvil/src/eth/backend/executor.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ use crate::{
44
error::InvalidTransactionError,
55
pool::transactions::PoolTransaction,
66
},
7+
inject_precompiles,
78
mem::inspector::Inspector,
9+
PrecompileFactory,
810
};
911
use alloy_consensus::{Header, Receipt, ReceiptWithBloom};
1012
use alloy_primitives::{Bloom, BloomInput, Log, B256};
@@ -96,6 +98,8 @@ pub struct TransactionExecutor<'a, Db: ?Sized, Validator: TransactionValidator>
9698
/// Cumulative gas used by all executed transactions
9799
pub gas_used: u128,
98100
pub enable_steps_tracing: bool,
101+
/// Precompiles to inject to the EVM.
102+
pub precompile_factory: Option<Arc<dyn PrecompileFactory>>,
99103
}
100104

101105
impl<'a, DB: Db + ?Sized, Validator: TransactionValidator> TransactionExecutor<'a, DB, Validator> {
@@ -265,6 +269,9 @@ impl<'a, 'b, DB: Db + ?Sized, Validator: TransactionValidator> Iterator
265269
let exec_result = {
266270
let mut evm =
267271
foundry_evm::utils::new_evm_with_inspector(&mut *self.db, env, &mut inspector);
272+
if let Some(ref factory) = self.precompile_factory {
273+
inject_precompiles(&mut evm, factory.precompiles());
274+
}
268275

269276
trace!(target: "backend", "[{:?}] executing", transaction.hash());
270277
// transact and commit the transaction

crates/anvil/src/eth/backend/mem/mod.rs

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use crate::{
2323
pool::transactions::PoolTransaction,
2424
util::get_precompiles_for,
2525
},
26+
inject_precompiles,
2627
mem::{
2728
inspector::Inspector,
2829
storage::{BlockchainStorage, InMemoryBlockStates, MinedBlockOutcome},
@@ -31,7 +32,7 @@ use crate::{
3132
db::DatabaseRef,
3233
primitives::{AccountInfo, U256 as rU256},
3334
},
34-
NodeConfig,
35+
NodeConfig, PrecompileFactory,
3536
};
3637
use alloy_consensus::{Header, Receipt, ReceiptWithBloom};
3738
use alloy_primitives::{keccak256, Address, Bytes, TxHash, B256, U256, U64};
@@ -78,7 +79,10 @@ use foundry_evm::{
7879
};
7980
use futures::channel::mpsc::{unbounded, UnboundedSender};
8081
use parking_lot::{Mutex, RwLock};
81-
use revm::primitives::{HashMap, ResultAndState};
82+
use revm::{
83+
db::WrapDatabaseRef,
84+
primitives::{HashMap, ResultAndState},
85+
};
8286
use std::{
8387
collections::BTreeMap,
8488
io::{Read, Write},
@@ -168,6 +172,8 @@ pub struct Backend {
168172
node_config: Arc<AsyncRwLock<NodeConfig>>,
169173
/// Slots in an epoch
170174
slots_in_an_epoch: u64,
175+
/// Precompiles to inject to the EVM.
176+
precompile_factory: Option<Arc<dyn PrecompileFactory>>,
171177
}
172178

173179
impl Backend {
@@ -214,7 +220,10 @@ impl Backend {
214220
Default::default()
215221
};
216222

217-
let slots_in_an_epoch = node_config.read().await.slots_in_an_epoch;
223+
let (slots_in_an_epoch, precompile_factory) = {
224+
let cfg = node_config.read().await;
225+
(cfg.slots_in_an_epoch, cfg.precompile_factory.clone())
226+
};
218227

219228
let backend = Self {
220229
db,
@@ -233,6 +242,7 @@ impl Backend {
233242
transaction_block_keeper,
234243
node_config,
235244
slots_in_an_epoch,
245+
precompile_factory,
236246
};
237247

238248
if let Some(interval_block_time) = automine_block_time {
@@ -800,6 +810,24 @@ impl Backend {
800810
env
801811
}
802812

813+
/// Creates an EVM instance with optionally injected precompiles.
814+
fn new_evm_with_inspector_ref<DB, I>(
815+
&self,
816+
db: DB,
817+
env: EnvWithHandlerCfg,
818+
inspector: I,
819+
) -> revm::Evm<'_, I, WrapDatabaseRef<DB>>
820+
where
821+
DB: revm::DatabaseRef,
822+
I: revm::Inspector<WrapDatabaseRef<DB>>,
823+
{
824+
let mut evm = new_evm_with_inspector_ref(db, env, inspector);
825+
if let Some(ref factory) = self.precompile_factory {
826+
inject_precompiles(&mut evm, factory.precompiles());
827+
}
828+
evm
829+
}
830+
803831
/// executes the transactions without writing to the underlying database
804832
pub async fn inspect_tx(
805833
&self,
@@ -812,9 +840,8 @@ impl Backend {
812840
env.tx = tx.pending_transaction.to_revm_tx_env();
813841
let db = self.db.read().await;
814842
let mut inspector = Inspector::default();
815-
816-
let ResultAndState { result, state } =
817-
new_evm_with_inspector_ref(&*db, env, &mut inspector).transact()?;
843+
let mut evm = self.new_evm_with_inspector_ref(&*db, env, &mut inspector);
844+
let ResultAndState { result, state } = evm.transact()?;
818845
let (exit_reason, gas_used, out, logs) = match result {
819846
ExecutionResult::Success { reason, gas_used, logs, output, .. } => {
820847
(reason.into(), gas_used, Some(output), Some(logs))
@@ -825,6 +852,7 @@ impl Backend {
825852
ExecutionResult::Halt { reason, gas_used } => (reason.into(), gas_used, None, None),
826853
};
827854

855+
drop(evm);
828856
inspector.print_logs();
829857

830858
Ok((exit_reason, out, gas_used, state, logs.unwrap_or_default()))
@@ -865,6 +893,7 @@ impl Backend {
865893
parent_hash: storage.best_hash,
866894
gas_used: 0,
867895
enable_steps_tracing: self.enable_steps_tracing,
896+
precompile_factory: self.precompile_factory.clone(),
868897
};
869898

870899
// create a new pending block
@@ -924,6 +953,7 @@ impl Backend {
924953
parent_hash: best_hash,
925954
gas_used: 0,
926955
enable_steps_tracing: self.enable_steps_tracing,
956+
precompile_factory: self.precompile_factory.clone(),
927957
};
928958
let executed_tx = executor.execute();
929959

@@ -1123,8 +1153,8 @@ impl Backend {
11231153
let mut inspector = Inspector::default();
11241154

11251155
let env = self.build_call_env(request, fee_details, block_env);
1126-
let ResultAndState { result, state } =
1127-
new_evm_with_inspector_ref(state, env, &mut inspector).transact()?;
1156+
let mut evm = self.new_evm_with_inspector_ref(state, env, &mut inspector);
1157+
let ResultAndState { result, state } = evm.transact()?;
11281158
let (exit_reason, gas_used, out) = match result {
11291159
ExecutionResult::Success { reason, gas_used, output, .. } => {
11301160
(reason.into(), gas_used, Some(output))
@@ -1134,6 +1164,7 @@ impl Backend {
11341164
}
11351165
ExecutionResult::Halt { reason, gas_used } => (reason.into(), gas_used, None),
11361166
};
1167+
drop(evm);
11371168
inspector.print_logs();
11381169
Ok((exit_reason, out, gas_used as u128, state))
11391170
}
@@ -1150,8 +1181,9 @@ impl Backend {
11501181
let block_number = block.number;
11511182

11521183
let env = self.build_call_env(request, fee_details, block);
1153-
let ResultAndState { result, state: _ } =
1154-
new_evm_with_inspector_ref(state, env, &mut inspector).transact()?;
1184+
let mut evm = self.new_evm_with_inspector_ref(state, env, &mut inspector);
1185+
let ResultAndState { result, state: _ } = evm.transact()?;
1186+
11551187
let (exit_reason, gas_used, out) = match result {
11561188
ExecutionResult::Success { reason, gas_used, output, .. } => {
11571189
(reason.into(), gas_used, Some(output))
@@ -1161,6 +1193,8 @@ impl Backend {
11611193
}
11621194
ExecutionResult::Halt { reason, gas_used } => (reason.into(), gas_used, None),
11631195
};
1196+
1197+
drop(evm);
11641198
let tracer = inspector.tracer.expect("tracer disappeared");
11651199
let return_value = out.as_ref().map(|o| o.data().clone()).unwrap_or_default();
11661200
let res = tracer.into_geth_builder().geth_traces(gas_used, return_value, opts);
@@ -1196,8 +1230,8 @@ impl Backend {
11961230
);
11971231

11981232
let env = self.build_call_env(request, fee_details, block_env);
1199-
let ResultAndState { result, state: _ } =
1200-
new_evm_with_inspector_ref(state, env, &mut inspector).transact()?;
1233+
let mut evm = self.new_evm_with_inspector_ref(state, env, &mut inspector);
1234+
let ResultAndState { result, state: _ } = evm.transact()?;
12011235
let (exit_reason, gas_used, out) = match result {
12021236
ExecutionResult::Success { reason, gas_used, output, .. } => {
12031237
(reason.into(), gas_used, Some(output))
@@ -1207,6 +1241,7 @@ impl Backend {
12071241
}
12081242
ExecutionResult::Halt { reason, gas_used } => (reason.into(), gas_used, None),
12091243
};
1244+
drop(evm);
12101245
let access_list = inspector.access_list();
12111246
Ok((exit_reason, out, gas_used, access_list))
12121247
}

crates/anvil/src/evm.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
use std::{fmt::Debug, sync::Arc};
2+
3+
use alloy_primitives::Address;
4+
use foundry_evm::revm::{self, precompile::Precompile, ContextPrecompile, ContextPrecompiles};
5+
6+
/// Object-safe trait that enables injecting extra precompiles when using
7+
/// `anvil` as a library.
8+
pub trait PrecompileFactory: Send + Sync + Unpin + Debug {
9+
/// Returns a set of precompiles to extend the EVM with.
10+
fn precompiles(&self) -> Vec<(Address, Precompile)>;
11+
}
12+
13+
/// Appends a handler register to `evm` that injects the given `precompiles`.
14+
///
15+
/// This will add an additional handler that extends the default precompiles with the given set of
16+
/// precompiles.
17+
pub fn inject_precompiles<DB, I>(
18+
evm: &mut revm::Evm<'_, I, DB>,
19+
precompiles: Vec<(Address, Precompile)>,
20+
) where
21+
DB: revm::Database,
22+
{
23+
evm.handler.append_handler_register_box(Box::new(move |handler| {
24+
let precompiles = precompiles.clone();
25+
let loaded_precompiles = handler.pre_execution().load_precompiles();
26+
handler.pre_execution.load_precompiles = Arc::new(move || {
27+
let mut loaded_precompiles = loaded_precompiles.clone();
28+
loaded_precompiles.extend(
29+
precompiles
30+
.clone()
31+
.into_iter()
32+
.map(|(addr, p)| (addr, ContextPrecompile::Ordinary(p))),
33+
);
34+
let mut default_precompiles = ContextPrecompiles::default();
35+
default_precompiles.extend(loaded_precompiles);
36+
default_precompiles
37+
});
38+
}));
39+
}
40+
41+
#[cfg(test)]
42+
mod tests {
43+
use crate::{evm::inject_precompiles, PrecompileFactory};
44+
use alloy_primitives::Address;
45+
use foundry_evm::revm::{
46+
self,
47+
primitives::{address, Bytes, Precompile, PrecompileResult, SpecId},
48+
};
49+
50+
#[test]
51+
fn build_evm_with_extra_precompiles() {
52+
const PRECOMPILE_ADDR: Address = address!("0000000000000000000000000000000000000071");
53+
fn my_precompile(_bytes: &Bytes, _gas_limit: u64) -> PrecompileResult {
54+
Ok((0, Bytes::new()))
55+
}
56+
57+
#[derive(Debug)]
58+
struct CustomPrecompileFactory;
59+
60+
impl PrecompileFactory for CustomPrecompileFactory {
61+
fn precompiles(&self) -> Vec<(Address, Precompile)> {
62+
vec![(PRECOMPILE_ADDR, Precompile::Standard(my_precompile))]
63+
}
64+
}
65+
66+
let db = revm::db::EmptyDB::default();
67+
let env = Box::<revm::primitives::Env>::default();
68+
let spec = SpecId::LATEST;
69+
let handler_cfg = revm::primitives::HandlerCfg::new(spec);
70+
let inspector = revm::inspectors::NoOpInspector;
71+
let context = revm::Context::new(revm::EvmContext::new_with_env(db, env), inspector);
72+
let handler = revm::Handler::new(handler_cfg);
73+
let mut evm = revm::Evm::new(context, handler);
74+
assert!(!evm
75+
.handler
76+
.pre_execution()
77+
.load_precompiles()
78+
.addresses()
79+
.any(|&addr| addr == PRECOMPILE_ADDR));
80+
81+
inject_precompiles(&mut evm, CustomPrecompileFactory.precompiles());
82+
assert!(evm
83+
.handler
84+
.pre_execution()
85+
.load_precompiles()
86+
.addresses()
87+
.any(|&addr| addr == PRECOMPILE_ADDR));
88+
89+
let result = evm.transact().unwrap();
90+
assert!(result.result.is_success());
91+
}
92+
}

crates/anvil/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ pub use hardfork::Hardfork;
5050

5151
/// ethereum related implementations
5252
pub mod eth;
53+
/// Evm related abstractions
54+
mod evm;
55+
pub use evm::{inject_precompiles, PrecompileFactory};
5356
/// support for polling filters
5457
pub mod filter;
5558
/// commandline output

0 commit comments

Comments
 (0)