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