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