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