anvil/
evm.rs

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