Skip to main content

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