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