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