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