anvil/
evm.rs

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