anvil/
evm.rs

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