anvil/
evm.rs

1use alloy_evm::precompiles::DynPrecompile;
2use alloy_primitives::Address;
3use std::fmt::Debug;
4
5/// Object-safe trait that enables injecting extra precompiles when using
6/// `anvil` as a library.
7pub trait PrecompileFactory: Send + Sync + Unpin + Debug {
8    /// Returns a set of precompiles to extend the EVM with.
9    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    // A precompile activated in the `Prague` spec (BLS12-381 G2 map).
40    const ETH_PRAGUE_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000011");
41
42    // A precompile activated in the `Osaka` spec (EIP-7951).
43    const ETH_OSAKA_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000100");
44
45    // A precompile activated in the `Isthmus` spec.
46    const OP_ISTHMUS_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000100");
47
48    // A custom precompile address and payload for testing.
49    const PRECOMPILE_ADDR: Address = address!("0x0000000000000000000000000000000000000071");
50    const PAYLOAD: &[u8] = &[0xde, 0xad, 0xbe, 0xef];
51
52    #[derive(Debug)]
53    struct CustomPrecompileFactory;
54
55    impl PrecompileFactory for CustomPrecompileFactory {
56        fn precompiles(&self) -> Vec<(Address, DynPrecompile)> {
57            use alloy_evm::precompiles::PrecompileInput;
58            vec![(
59                PRECOMPILE_ADDR,
60                DynPrecompile::from(|input: PrecompileInput<'_>| {
61                    Ok(PrecompileOutput {
62                        bytes: Bytes::copy_from_slice(input.data),
63                        gas_used: 0,
64                        gas_refunded: 0,
65                        reverted: false,
66                    })
67                }),
68            )]
69        }
70    }
71
72    /// Creates a new EVM instance with the custom precompile factory.
73    fn create_eth_evm(
74        spec: SpecId,
75    ) -> (foundry_evm::Env, EitherEvm<EmptyDBTyped<Infallible>, NoOpInspector, PrecompilesMap>)
76    {
77        let eth_env = foundry_evm::Env {
78            evm_env: EvmEnv { block_env: Default::default(), cfg_env: CfgEnv::new_with_spec(spec) },
79            tx: TxEnv {
80                kind: TxKind::Call(PRECOMPILE_ADDR),
81                data: PAYLOAD.into(),
82                ..Default::default()
83            },
84        };
85
86        let eth_evm_context = EthEvmContext {
87            journaled_state: Journal::new(EmptyDB::default()),
88            block: eth_env.evm_env.block_env.clone(),
89            cfg: eth_env.evm_env.cfg_env.clone(),
90            tx: eth_env.tx.clone(),
91            chain: (),
92            local: LocalContext::default(),
93            error: Ok(()),
94        };
95
96        let eth_precompiles = EthPrecompiles {
97            precompiles: Precompiles::new(PrecompileSpecId::from_spec_id(spec)),
98            spec,
99        }
100        .precompiles;
101        let eth_evm = EitherEvm::Eth(EthEvm::new(
102            RevmEvm::new_with_inspector(
103                eth_evm_context,
104                NoOpInspector,
105                EthInstructions::<EthInterpreter, EthEvmContext<EmptyDB>>::default(),
106                PrecompilesMap::from_static(eth_precompiles),
107            ),
108            true,
109        ));
110
111        (eth_env, eth_evm)
112    }
113
114    /// Creates a new OP EVM instance with the custom precompile factory.
115    fn create_op_evm(
116        spec: SpecId,
117        op_spec: OpSpecId,
118    ) -> (
119        crate::eth::backend::env::Env,
120        EitherEvm<EmptyDBTyped<Infallible>, NoOpInspector, PrecompilesMap>,
121    ) {
122        let op_env = crate::eth::backend::env::Env {
123            evm_env: EvmEnv { block_env: Default::default(), cfg_env: CfgEnv::new_with_spec(spec) },
124            tx: OpTransaction::<TxEnv> {
125                base: TxEnv {
126                    kind: TxKind::Call(PRECOMPILE_ADDR),
127                    data: PAYLOAD.into(),
128                    ..Default::default()
129                },
130                ..Default::default()
131            },
132            networks: NetworkConfigs::with_optimism(),
133        };
134
135        let mut chain = L1BlockInfo::default();
136
137        if op_spec == OpSpecId::ISTHMUS {
138            chain.operator_fee_constant = Some(U256::from(0));
139            chain.operator_fee_scalar = Some(U256::from(0));
140        }
141
142        let op_cfg: CfgEnv<OpSpecId> = CfgEnv::new_with_spec(op_spec);
143        let op_evm_context = OpContext {
144            journaled_state: {
145                let mut journal = Journal::new(EmptyDB::default());
146                // Converting SpecId into OpSpecId
147                journal.set_spec_id(op_env.evm_env.cfg_env.spec);
148                journal
149            },
150            block: op_env.evm_env.block_env.clone(),
151            cfg: op_cfg.clone(),
152            tx: op_env.tx.clone(),
153            chain,
154            local: LocalContext::default(),
155            error: Ok(()),
156        };
157
158        let op_precompiles = OpPrecompiles::new_with_spec(op_cfg.spec).precompiles();
159        let op_evm = EitherEvm::Op(OpEvm::new(
160            op_revm::OpEvm(RevmEvm::new_with_inspector(
161                op_evm_context,
162                NoOpInspector,
163                EthInstructions::<EthInterpreter, OpContext<EmptyDB>>::default(),
164                PrecompilesMap::from_static(op_precompiles),
165            )),
166            true,
167        ));
168
169        (op_env, op_evm)
170    }
171
172    #[test]
173    fn build_eth_evm_with_extra_precompiles_osaka_spec() {
174        let (env, mut evm) = create_eth_evm(SpecId::OSAKA);
175
176        // Check that the Osaka precompile IS present when using the Osaka spec.
177        assert!(evm.precompiles().addresses().contains(&ETH_OSAKA_PRECOMPILE));
178
179        // Check that the Prague precompile IS present when using the Osaka spec.
180        assert!(evm.precompiles().addresses().contains(&ETH_PRAGUE_PRECOMPILE));
181
182        assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
183
184        evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles());
185
186        assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
187
188        let result = match &mut evm {
189            EitherEvm::Eth(eth_evm) => eth_evm.transact(env.tx).unwrap(),
190            _ => unreachable!(),
191        };
192
193        assert!(result.result.is_success());
194        assert_eq!(result.result.output(), Some(&PAYLOAD.into()));
195    }
196
197    #[test]
198    fn build_eth_evm_with_extra_precompiles_london_spec() {
199        let (env, mut evm) = create_eth_evm(SpecId::LONDON);
200
201        // Check that the Osaka precompile IS NOT present when using the London spec.
202        assert!(!evm.precompiles().addresses().contains(&ETH_OSAKA_PRECOMPILE));
203
204        // Check that the Prague precompile IS NOT present when using the London spec.
205        assert!(!evm.precompiles().addresses().contains(&ETH_PRAGUE_PRECOMPILE));
206
207        assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
208
209        evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles());
210
211        assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
212
213        let result = match &mut evm {
214            EitherEvm::Eth(eth_evm) => eth_evm.transact(env.tx).unwrap(),
215            _ => unreachable!(),
216        };
217
218        assert!(result.result.is_success());
219        assert_eq!(result.result.output(), Some(&PAYLOAD.into()));
220    }
221
222    #[test]
223    fn build_eth_evm_with_extra_precompiles_prague_spec() {
224        let (env, mut evm) = create_eth_evm(SpecId::PRAGUE);
225
226        // Check that the Osaka precompile IS NOT present when using the Prague spec.
227        assert!(!evm.precompiles().addresses().contains(&ETH_OSAKA_PRECOMPILE));
228
229        // Check that the Prague precompile IS present when using the Prague spec.
230        assert!(evm.precompiles().addresses().contains(&ETH_PRAGUE_PRECOMPILE));
231
232        assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
233
234        evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles());
235
236        assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
237
238        let result = match &mut evm {
239            EitherEvm::Eth(eth_evm) => eth_evm.transact(env.tx).unwrap(),
240            _ => unreachable!(),
241        };
242
243        assert!(result.result.is_success());
244        assert_eq!(result.result.output(), Some(&PAYLOAD.into()));
245    }
246
247    #[test]
248    fn build_op_evm_with_extra_precompiles_isthmus_spec() {
249        let (env, mut evm) = create_op_evm(SpecId::OSAKA, OpSpecId::ISTHMUS);
250
251        // Check that the Isthmus precompile IS present when using the Isthmus spec.
252        assert!(evm.precompiles().addresses().contains(&OP_ISTHMUS_PRECOMPILE));
253
254        // Check that the Prague precompile IS present when using the Isthmus spec.
255        assert!(evm.precompiles().addresses().contains(&ETH_PRAGUE_PRECOMPILE));
256
257        assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
258
259        evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles());
260
261        assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
262
263        let result = match &mut evm {
264            EitherEvm::Op(op_evm) => op_evm.transact(env.tx).unwrap(),
265            _ => unreachable!(),
266        };
267
268        assert!(result.result.is_success());
269        assert_eq!(result.result.output(), Some(&PAYLOAD.into()));
270    }
271
272    #[test]
273    fn build_op_evm_with_extra_precompiles_bedrock_spec() {
274        let (env, mut evm) = create_op_evm(SpecId::OSAKA, OpSpecId::BEDROCK);
275
276        // Check that the Isthmus precompile IS NOT present when using the `OpSpecId::BEDROCK` spec.
277        assert!(!evm.precompiles().addresses().contains(&OP_ISTHMUS_PRECOMPILE));
278
279        // Check that the Prague precompile IS NOT present when using the `OpSpecId::BEDROCK` spec.
280        assert!(!evm.precompiles().addresses().contains(&ETH_PRAGUE_PRECOMPILE));
281
282        assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
283
284        evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles());
285
286        assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
287
288        let result = match &mut evm {
289            EitherEvm::Op(op_evm) => op_evm.transact(env.tx).unwrap(),
290            _ => unreachable!(),
291        };
292
293        assert!(result.result.is_success());
294        assert_eq!(result.result.output(), Some(&PAYLOAD.into()));
295    }
296}