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