anvil/
evm.rs

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