anvil/
evm.rs

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