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,
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 itertools::Itertools;
26 use op_revm::{L1BlockInfo, OpContext, OpSpecId, OpTransaction, precompiles::OpPrecompiles};
27 use revm::{
28 Journal,
29 context::{BlockEnv, CfgEnv, Evm as RevmEvm, JournalTr, LocalContext, TxEnv},
30 database::{EmptyDB, EmptyDBTyped},
31 handler::{EthPrecompiles, instructions::EthInstructions},
32 inspector::NoOpInspector,
33 interpreter::interpreter::EthInterpreter,
34 precompile::{PrecompileOutput, PrecompileSpecId, Precompiles},
35 primitives::hardfork::SpecId,
36 };
37
38 const ETH_PRAGUE_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000011");
40
41 const ETH_OSAKA_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000100");
43
44 const OP_ISTHMUS_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000100");
46
47 const PRECOMPILE_ADDR: Address = address!("0x0000000000000000000000000000000000000071");
49 const PAYLOAD: &[u8] = &[0xde, 0xad, 0xbe, 0xef];
50
51 #[derive(Debug)]
52 struct CustomPrecompileFactory;
53
54 impl PrecompileFactory for CustomPrecompileFactory {
55 fn precompiles(&self) -> Vec<(Address, DynPrecompile)> {
56 use alloy_evm::precompiles::PrecompileInput;
57 vec![(
58 PRECOMPILE_ADDR,
59 DynPrecompile::from(|input: PrecompileInput<'_>| {
60 Ok(PrecompileOutput {
61 bytes: Bytes::copy_from_slice(input.data),
62 gas_used: 0,
63 gas_refunded: 0,
64 reverted: false,
65 })
66 }),
67 )]
68 }
69 }
70
71 fn create_eth_evm(
73 spec: SpecId,
74 ) -> (TxEnv, EitherEvm<EmptyDBTyped<Infallible>, NoOpInspector, PrecompilesMap>) {
75 let tx_env = TxEnv {
76 kind: TxKind::Call(PRECOMPILE_ADDR),
77 data: PAYLOAD.into(),
78 ..Default::default()
79 };
80
81 let eth_evm_context = EthEvmContext {
82 journaled_state: Journal::new(EmptyDB::default()),
83 block: BlockEnv::default(),
84 cfg: CfgEnv::new_with_spec(spec),
85 tx: tx_env.clone(),
86 chain: (),
87 local: LocalContext::default(),
88 error: Ok(()),
89 };
90
91 let eth_precompiles = EthPrecompiles {
92 precompiles: Precompiles::new(PrecompileSpecId::from_spec_id(spec)),
93 spec,
94 }
95 .precompiles;
96 let eth_evm = EitherEvm::Eth(EthEvm::new(
97 RevmEvm::new_with_inspector(
98 eth_evm_context,
99 NoOpInspector,
100 EthInstructions::<EthInterpreter, EthEvmContext<EmptyDB>>::default(),
101 PrecompilesMap::from_static(eth_precompiles),
102 ),
103 true,
104 ));
105
106 (tx_env, eth_evm)
107 }
108
109 fn create_op_evm(
111 spec: SpecId,
112 op_spec: OpSpecId,
113 ) -> (OpTransaction<TxEnv>, EitherEvm<EmptyDBTyped<Infallible>, NoOpInspector, PrecompilesMap>)
114 {
115 let tx = OpTransaction::<TxEnv> {
116 base: TxEnv {
117 kind: TxKind::Call(PRECOMPILE_ADDR),
118 data: PAYLOAD.into(),
119 ..Default::default()
120 },
121 ..Default::default()
122 };
123
124 let mut chain = L1BlockInfo::default();
125
126 if op_spec == OpSpecId::ISTHMUS {
127 chain.operator_fee_constant = Some(U256::from(0));
128 chain.operator_fee_scalar = Some(U256::from(0));
129 }
130
131 let op_cfg: CfgEnv<OpSpecId> = CfgEnv::new_with_spec(op_spec);
132 let op_evm_context = OpContext {
133 journaled_state: {
134 let mut journal = Journal::new(EmptyDB::default());
135 journal.set_spec_id(spec);
137 journal
138 },
139 block: BlockEnv::default(),
140 cfg: op_cfg.clone(),
141 tx: tx.clone(),
142 chain,
143 local: LocalContext::default(),
144 error: Ok(()),
145 };
146
147 let op_precompiles = OpPrecompiles::new_with_spec(op_cfg.spec).precompiles();
148 let op_evm = EitherEvm::Op(OpEvm::new(
149 op_revm::OpEvm(RevmEvm::new_with_inspector(
150 op_evm_context,
151 NoOpInspector,
152 EthInstructions::<EthInterpreter, OpContext<EmptyDB>>::default(),
153 PrecompilesMap::from_static(op_precompiles),
154 )),
155 true,
156 ));
157
158 (tx, op_evm)
159 }
160
161 #[test]
162 fn build_eth_evm_with_extra_precompiles_osaka_spec() {
163 let (tx_env, mut evm) = create_eth_evm(SpecId::OSAKA);
164
165 assert!(evm.precompiles().addresses().contains(Ð_OSAKA_PRECOMPILE));
167
168 assert!(evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE));
170
171 assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
172
173 evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles());
174
175 assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
176
177 let result = match &mut evm {
178 EitherEvm::Eth(eth_evm) => eth_evm.transact(tx_env).unwrap(),
179 _ => unreachable!(),
180 };
181
182 assert!(result.result.is_success());
183 assert_eq!(result.result.output(), Some(&PAYLOAD.into()));
184 }
185
186 #[test]
187 fn build_eth_evm_with_extra_precompiles_london_spec() {
188 let (tx_env, mut evm) = create_eth_evm(SpecId::LONDON);
189
190 assert!(!evm.precompiles().addresses().contains(Ð_OSAKA_PRECOMPILE));
192
193 assert!(!evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE));
195
196 assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
197
198 evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles());
199
200 assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
201
202 let result = match &mut evm {
203 EitherEvm::Eth(eth_evm) => eth_evm.transact(tx_env).unwrap(),
204 _ => unreachable!(),
205 };
206
207 assert!(result.result.is_success());
208 assert_eq!(result.result.output(), Some(&PAYLOAD.into()));
209 }
210
211 #[test]
212 fn build_eth_evm_with_extra_precompiles_prague_spec() {
213 let (tx_env, mut evm) = create_eth_evm(SpecId::PRAGUE);
214
215 assert!(!evm.precompiles().addresses().contains(Ð_OSAKA_PRECOMPILE));
217
218 assert!(evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE));
220
221 assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
222
223 evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles());
224
225 assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
226
227 let result = match &mut evm {
228 EitherEvm::Eth(eth_evm) => eth_evm.transact(tx_env).unwrap(),
229 _ => unreachable!(),
230 };
231
232 assert!(result.result.is_success());
233 assert_eq!(result.result.output(), Some(&PAYLOAD.into()));
234 }
235
236 #[test]
237 fn build_op_evm_with_extra_precompiles_isthmus_spec() {
238 let (tx, mut evm) = create_op_evm(SpecId::OSAKA, OpSpecId::ISTHMUS);
239
240 assert!(evm.precompiles().addresses().contains(&OP_ISTHMUS_PRECOMPILE));
242
243 assert!(evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE));
245
246 assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
247
248 evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles());
249
250 assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
251
252 let result = match &mut evm {
253 EitherEvm::Op(op_evm) => op_evm.transact(tx).unwrap(),
254 _ => unreachable!(),
255 };
256
257 assert!(result.result.is_success());
258 assert_eq!(result.result.output(), Some(&PAYLOAD.into()));
259 }
260
261 #[test]
262 fn build_op_evm_with_extra_precompiles_bedrock_spec() {
263 let (tx, mut evm) = create_op_evm(SpecId::OSAKA, OpSpecId::BEDROCK);
264
265 assert!(!evm.precompiles().addresses().contains(&OP_ISTHMUS_PRECOMPILE));
267
268 assert!(!evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE));
270
271 assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
272
273 evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles());
274
275 assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
276
277 let result = match &mut evm {
278 EitherEvm::Op(op_evm) => op_evm.transact(tx).unwrap(),
279 _ => unreachable!(),
280 };
281
282 assert!(result.result.is_success());
283 assert_eq!(result.result.output(), Some(&PAYLOAD.into()));
284 }
285}