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 OP_ISTHMUS_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000100");
44
45 const PRECOMPILE_ADDR: Address = address!("0x0000000000000000000000000000000000000071");
47 const PAYLOAD: &[u8] = &[0xde, 0xad, 0xbe, 0xef];
48
49 #[derive(Debug)]
50 struct CustomPrecompileFactory;
51
52 impl PrecompileFactory for CustomPrecompileFactory {
53 fn precompiles(&self) -> Vec<(Address, DynPrecompile)> {
54 use alloy_evm::precompiles::PrecompileInput;
55 vec![(
56 PRECOMPILE_ADDR,
57 DynPrecompile::from(|input: PrecompileInput<'_>| {
58 Ok(PrecompileOutput {
59 bytes: Bytes::copy_from_slice(input.data),
60 gas_used: 0,
61 gas_refunded: 0,
62 reverted: false,
63 })
64 }),
65 )]
66 }
67 }
68
69 fn create_eth_evm(
71 spec: SpecId,
72 ) -> (foundry_evm::Env, EitherEvm<EmptyDBTyped<Infallible>, NoOpInspector, PrecompilesMap>)
73 {
74 let eth_env = foundry_evm::Env {
75 evm_env: EvmEnv { block_env: Default::default(), cfg_env: CfgEnv::new_with_spec(spec) },
76 tx: TxEnv {
77 kind: TxKind::Call(PRECOMPILE_ADDR),
78 data: PAYLOAD.into(),
79 ..Default::default()
80 },
81 };
82
83 let eth_evm_context = EthEvmContext {
84 journaled_state: Journal::new(EmptyDB::default()),
85 block: eth_env.evm_env.block_env.clone(),
86 cfg: eth_env.evm_env.cfg_env.clone(),
87 tx: eth_env.tx.clone(),
88 chain: (),
89 local: LocalContext::default(),
90 error: Ok(()),
91 };
92
93 let eth_precompiles = EthPrecompiles {
94 precompiles: Precompiles::new(PrecompileSpecId::from_spec_id(spec)),
95 spec,
96 }
97 .precompiles;
98 let eth_evm = EitherEvm::Eth(EthEvm::new(
99 RevmEvm::new_with_inspector(
100 eth_evm_context,
101 NoOpInspector,
102 EthInstructions::<EthInterpreter, EthEvmContext<EmptyDB>>::default(),
103 PrecompilesMap::from_static(eth_precompiles),
104 ),
105 true,
106 ));
107
108 (eth_env, eth_evm)
109 }
110
111 fn create_op_evm(
113 spec: SpecId,
114 op_spec: OpSpecId,
115 ) -> (
116 crate::eth::backend::env::Env,
117 EitherEvm<EmptyDBTyped<Infallible>, NoOpInspector, PrecompilesMap>,
118 ) {
119 let op_env = crate::eth::backend::env::Env {
120 evm_env: EvmEnv { block_env: Default::default(), cfg_env: CfgEnv::new_with_spec(spec) },
121 tx: OpTransaction::<TxEnv> {
122 base: TxEnv {
123 kind: TxKind::Call(PRECOMPILE_ADDR),
124 data: PAYLOAD.into(),
125 ..Default::default()
126 },
127 ..Default::default()
128 },
129 networks: NetworkConfigs::with_optimism(),
130 };
131
132 let mut chain = L1BlockInfo::default();
133
134 if op_spec == OpSpecId::ISTHMUS {
135 chain.operator_fee_constant = Some(U256::from(0));
136 chain.operator_fee_scalar = Some(U256::from(0));
137 }
138
139 let op_cfg = op_env.evm_env.cfg_env.clone().with_spec(op_spec);
140 let op_evm_context = OpContext {
141 journaled_state: {
142 let mut journal = Journal::new(EmptyDB::default());
143 journal.set_spec_id(op_env.evm_env.cfg_env.spec);
145 journal
146 },
147 block: op_env.evm_env.block_env.clone(),
148 cfg: op_cfg.clone(),
149 tx: op_env.tx.clone(),
150 chain,
151 local: LocalContext::default(),
152 error: Ok(()),
153 };
154
155 let op_precompiles = OpPrecompiles::new_with_spec(op_cfg.spec).precompiles();
156 let op_evm = EitherEvm::Op(OpEvm::new(
157 op_revm::OpEvm(RevmEvm::new_with_inspector(
158 op_evm_context,
159 NoOpInspector,
160 EthInstructions::<EthInterpreter, OpContext<EmptyDB>>::default(),
161 PrecompilesMap::from_static(op_precompiles),
162 )),
163 true,
164 ));
165
166 (op_env, op_evm)
167 }
168
169 #[test]
170 fn build_eth_evm_with_extra_precompiles_default_spec() {
171 let (env, mut evm) = create_eth_evm(SpecId::default());
172
173 assert!(evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE));
175
176 assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
177
178 evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles());
179
180 assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
181
182 let result = match &mut evm {
183 EitherEvm::Eth(eth_evm) => eth_evm.transact(env.tx).unwrap(),
184 _ => unreachable!(),
185 };
186
187 assert!(result.result.is_success());
188 assert_eq!(result.result.output(), Some(&PAYLOAD.into()));
189 }
190
191 #[test]
192 fn build_eth_evm_with_extra_precompiles_london_spec() {
193 let (env, mut evm) = create_eth_evm(SpecId::LONDON);
194
195 assert!(!evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE));
197
198 assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
199
200 evm.precompiles_mut().extend_precompiles(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_op_evm_with_extra_precompiles_default_spec() {
215 let (env, mut evm) = create_op_evm(SpecId::default(), OpSpecId::default());
216
217 assert!(evm.precompiles().addresses().contains(&OP_ISTHMUS_PRECOMPILE));
219
220 assert!(evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE));
222
223 assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
224
225 evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles());
226
227 assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
228
229 let result = match &mut evm {
230 EitherEvm::Op(op_evm) => op_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_bedrock_spec() {
240 let (env, mut evm) = create_op_evm(SpecId::default(), OpSpecId::BEDROCK);
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 evm.precompiles_mut().extend_precompiles(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}