1use alloy_evm::{
2 Database, Evm,
3 eth::EthEvmContext,
4 precompiles::{DynPrecompile, PrecompilesMap},
5};
6use alloy_primitives::Address;
7use foundry_evm::core::either_evm::EitherEvm;
8use op_revm::OpContext;
9use revm::Inspector;
10use std::fmt::Debug;
11
12pub trait PrecompileFactory: Send + Sync + Unpin + Debug {
15 fn precompiles(&self) -> Vec<(Address, DynPrecompile)>;
17}
18
19pub fn inject_custom_precompiles<DB, I>(
21 evm: &mut EitherEvm<DB, I, PrecompilesMap>,
22 precompiles: Vec<(Address, DynPrecompile)>,
23) where
24 DB: Database,
25 I: Inspector<EthEvmContext<DB>> + Inspector<OpContext<DB>>,
26{
27 for (addr, precompile) in precompiles {
28 evm.precompiles_mut().apply_precompile(&addr, move |_| Some(precompile));
29 }
30}
31
32#[cfg(test)]
33mod tests {
34 use std::convert::Infallible;
35
36 use crate::{PrecompileFactory, inject_custom_precompiles};
37 use alloy_evm::{
38 EthEvm, Evm, EvmEnv,
39 eth::EthEvmContext,
40 precompiles::{DynPrecompile, PrecompilesMap},
41 };
42 use alloy_op_evm::OpEvm;
43 use alloy_primitives::{Address, Bytes, TxKind, U256, address};
44 use foundry_evm::core::either_evm::EitherEvm;
45 use foundry_evm_networks::NetworkConfigs;
46 use itertools::Itertools;
47 use op_revm::{L1BlockInfo, OpContext, OpSpecId, OpTransaction, precompiles::OpPrecompiles};
48 use revm::{
49 Journal,
50 context::{CfgEnv, Evm as RevmEvm, JournalTr, LocalContext, TxEnv},
51 database::{EmptyDB, EmptyDBTyped},
52 handler::{EthPrecompiles, instructions::EthInstructions},
53 inspector::NoOpInspector,
54 interpreter::interpreter::EthInterpreter,
55 precompile::{PrecompileOutput, PrecompileSpecId, Precompiles},
56 primitives::hardfork::SpecId,
57 };
58
59 const ETH_PRAGUE_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000011");
61
62 const OP_ISTHMUS_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000100");
64
65 const PRECOMPILE_ADDR: Address = address!("0x0000000000000000000000000000000000000071");
67 const PAYLOAD: &[u8] = &[0xde, 0xad, 0xbe, 0xef];
68
69 #[derive(Debug)]
70 struct CustomPrecompileFactory;
71
72 impl PrecompileFactory for CustomPrecompileFactory {
73 fn precompiles(&self) -> Vec<(Address, DynPrecompile)> {
74 use alloy_evm::precompiles::PrecompileInput;
75 vec![(
76 PRECOMPILE_ADDR,
77 DynPrecompile::from(|input: PrecompileInput<'_>| {
78 Ok(PrecompileOutput {
79 bytes: Bytes::copy_from_slice(input.data),
80 gas_used: 0,
81 reverted: false,
82 })
83 }),
84 )]
85 }
86 }
87
88 fn create_eth_evm(
90 spec: SpecId,
91 ) -> (foundry_evm::Env, EitherEvm<EmptyDBTyped<Infallible>, NoOpInspector, PrecompilesMap>)
92 {
93 let eth_env = foundry_evm::Env {
94 evm_env: EvmEnv { block_env: Default::default(), cfg_env: CfgEnv::new_with_spec(spec) },
95 tx: TxEnv {
96 kind: TxKind::Call(PRECOMPILE_ADDR),
97 data: PAYLOAD.into(),
98 ..Default::default()
99 },
100 };
101
102 let eth_evm_context = EthEvmContext {
103 journaled_state: Journal::new(EmptyDB::default()),
104 block: eth_env.evm_env.block_env.clone(),
105 cfg: eth_env.evm_env.cfg_env.clone(),
106 tx: eth_env.tx.clone(),
107 chain: (),
108 local: LocalContext::default(),
109 error: Ok(()),
110 };
111
112 let eth_precompiles = EthPrecompiles {
113 precompiles: Precompiles::new(PrecompileSpecId::from_spec_id(spec)),
114 spec,
115 }
116 .precompiles;
117 let eth_evm = EitherEvm::Eth(EthEvm::new(
118 RevmEvm::new_with_inspector(
119 eth_evm_context,
120 NoOpInspector,
121 EthInstructions::<EthInterpreter, EthEvmContext<EmptyDB>>::default(),
122 PrecompilesMap::from_static(eth_precompiles),
123 ),
124 true,
125 ));
126
127 (eth_env, eth_evm)
128 }
129
130 fn create_op_evm(
132 spec: SpecId,
133 op_spec: OpSpecId,
134 ) -> (
135 crate::eth::backend::env::Env,
136 EitherEvm<EmptyDBTyped<Infallible>, NoOpInspector, PrecompilesMap>,
137 ) {
138 let op_env = crate::eth::backend::env::Env {
139 evm_env: EvmEnv { block_env: Default::default(), cfg_env: CfgEnv::new_with_spec(spec) },
140 tx: OpTransaction::<TxEnv> {
141 base: TxEnv {
142 kind: TxKind::Call(PRECOMPILE_ADDR),
143 data: PAYLOAD.into(),
144 ..Default::default()
145 },
146 ..Default::default()
147 },
148 networks: NetworkConfigs::with_optimism(),
149 };
150
151 let mut chain = L1BlockInfo::default();
152
153 if op_spec == OpSpecId::ISTHMUS {
154 chain.operator_fee_constant = Some(U256::from(0));
155 chain.operator_fee_scalar = Some(U256::from(0));
156 }
157
158 let op_cfg = op_env.evm_env.cfg_env.clone().with_spec(op_spec);
159 let op_evm_context = OpContext {
160 journaled_state: {
161 let mut journal = Journal::new(EmptyDB::default());
162 journal.set_spec_id(op_env.evm_env.cfg_env.spec);
164 journal
165 },
166 block: op_env.evm_env.block_env.clone(),
167 cfg: op_cfg.clone(),
168 tx: op_env.tx.clone(),
169 chain,
170 local: LocalContext::default(),
171 error: Ok(()),
172 };
173
174 let op_precompiles = OpPrecompiles::new_with_spec(op_cfg.spec).precompiles();
175 let op_evm = EitherEvm::Op(OpEvm::new(
176 op_revm::OpEvm(RevmEvm::new_with_inspector(
177 op_evm_context,
178 NoOpInspector,
179 EthInstructions::<EthInterpreter, OpContext<EmptyDB>>::default(),
180 PrecompilesMap::from_static(op_precompiles),
181 )),
182 true,
183 ));
184
185 (op_env, op_evm)
186 }
187
188 #[test]
189 fn build_eth_evm_with_extra_precompiles_default_spec() {
190 let (env, mut evm) = create_eth_evm(SpecId::default());
191
192 assert!(evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE));
194
195 assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
196
197 inject_custom_precompiles(&mut evm, CustomPrecompileFactory.precompiles());
198
199 assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
200
201 let result = match &mut evm {
202 EitherEvm::Eth(eth_evm) => eth_evm.transact(env.tx).unwrap(),
203 _ => unreachable!(),
204 };
205
206 assert!(result.result.is_success());
207 assert_eq!(result.result.output(), Some(&PAYLOAD.into()));
208 }
209
210 #[test]
211 fn build_eth_evm_with_extra_precompiles_london_spec() {
212 let (env, mut evm) = create_eth_evm(SpecId::LONDON);
213
214 assert!(!evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE));
216
217 assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
218
219 inject_custom_precompiles(&mut evm, CustomPrecompileFactory.precompiles());
220
221 assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
222
223 let result = match &mut evm {
224 EitherEvm::Eth(eth_evm) => eth_evm.transact(env.tx).unwrap(),
225 _ => unreachable!(),
226 };
227
228 assert!(result.result.is_success());
229 assert_eq!(result.result.output(), Some(&PAYLOAD.into()));
230 }
231
232 #[test]
233 fn build_op_evm_with_extra_precompiles_default_spec() {
234 let (env, mut evm) = create_op_evm(SpecId::default(), OpSpecId::default());
235
236 assert!(evm.precompiles().addresses().contains(&OP_ISTHMUS_PRECOMPILE));
238
239 assert!(evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE));
241
242 assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
243
244 inject_custom_precompiles(&mut evm, CustomPrecompileFactory.precompiles());
245
246 assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
247
248 let result = match &mut evm {
249 EitherEvm::Op(op_evm) => op_evm.transact(env.tx).unwrap(),
250 _ => unreachable!(),
251 };
252
253 assert!(result.result.is_success());
254 assert_eq!(result.result.output(), Some(&PAYLOAD.into()));
255 }
256
257 #[test]
258 fn build_op_evm_with_extra_precompiles_bedrock_spec() {
259 let (env, mut evm) = create_op_evm(SpecId::default(), OpSpecId::BEDROCK);
260
261 assert!(!evm.precompiles().addresses().contains(&OP_ISTHMUS_PRECOMPILE));
263
264 assert!(!evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE));
266
267 assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
268
269 inject_custom_precompiles(&mut evm, CustomPrecompileFactory.precompiles());
270
271 assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
272
273 let result = match &mut evm {
274 EitherEvm::Op(op_evm) => op_evm.transact(env.tx).unwrap(),
275 _ => unreachable!(),
276 };
277
278 assert!(result.result.is_success());
279 assert_eq!(result.result.output(), Some(&PAYLOAD.into()));
280 }
281}