Skip to main content

anvil/evm/
mod.rs

1use alloy_evm::precompiles::{DynPrecompile, PrecompilesMap};
2use alloy_primitives::Address;
3use std::fmt::Debug;
4
5#[cfg(feature = "optimism")]
6mod optimism;
7
8/// Object-safe trait that enables injecting extra precompiles when using
9/// `anvil` as a library.
10pub trait PrecompileFactory: Send + Sync + Unpin + Debug {
11    /// Returns a set of precompiles to extend the EVM with.
12    fn precompiles(&self) -> Vec<(Address, DynPrecompile)>;
13
14    /// Installs precompiles into the EVM precompile map.
15    fn install(&self, precompiles: &mut PrecompilesMap) {
16        precompiles.extend_precompiles(self.precompiles());
17    }
18}
19
20#[cfg(test)]
21mod tests {
22    use std::convert::Infallible;
23
24    use crate::PrecompileFactory;
25    use alloy_evm::{
26        EthEvm, Evm,
27        eth::EthEvmContext,
28        precompiles::{DynPrecompile, PrecompilesMap},
29    };
30    use alloy_primitives::{Address, Bytes, TxKind, address};
31    use itertools::Itertools;
32    use revm::{
33        Journal,
34        context::{BlockEnv, CfgEnv, Evm as RevmEvm, JournalTr, LocalContext, TxEnv},
35        database::{EmptyDB, EmptyDBTyped},
36        handler::{EthPrecompiles, instructions::EthInstructions},
37        inspector::NoOpInspector,
38        interpreter::interpreter::EthInterpreter,
39        precompile::{PrecompileOutput, PrecompileSpecId, PrecompileStatus, Precompiles},
40        primitives::hardfork::SpecId,
41    };
42
43    // A precompile activated in the `Prague` spec (BLS12-381 G2 map).
44    pub(super) const ETH_PRAGUE_PRECOMPILE: Address =
45        address!("0x0000000000000000000000000000000000000011");
46
47    // A precompile activated in the `Osaka` spec (EIP-7951).
48    const ETH_OSAKA_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000100");
49
50    // A custom precompile address and payload for testing.
51    pub(super) const PRECOMPILE_ADDR: Address =
52        address!("0x0000000000000000000000000000000000000071");
53    const DYNAMIC_PRECOMPILE_ADDR: Address = address!("0xdead000000000000000000000000000000000071");
54    const DYNAMIC_PRECOMPILE_PREFIX: [u8; 2] = [0xde, 0xad];
55    pub(super) const PAYLOAD: &[u8] = &[0xde, 0xad, 0xbe, 0xef];
56
57    fn echo_precompile() -> DynPrecompile {
58        use alloy_evm::precompiles::PrecompileInput;
59        DynPrecompile::from(|input: PrecompileInput<'_>| {
60            Ok(PrecompileOutput {
61                status: PrecompileStatus::Success,
62                bytes: Bytes::copy_from_slice(input.data),
63                gas_used: 0,
64                gas_refunded: 0,
65                state_gas_used: 0,
66                reservoir: input.reservoir,
67            })
68        })
69    }
70
71    #[derive(Debug)]
72    pub(super) struct CustomPrecompileFactory;
73
74    impl PrecompileFactory for CustomPrecompileFactory {
75        fn precompiles(&self) -> Vec<(Address, DynPrecompile)> {
76            vec![(PRECOMPILE_ADDR, echo_precompile())]
77        }
78    }
79
80    #[derive(Debug)]
81    struct DynamicLookupPrecompileFactory;
82
83    impl PrecompileFactory for DynamicLookupPrecompileFactory {
84        fn precompiles(&self) -> Vec<(Address, DynPrecompile)> {
85            Vec::new()
86        }
87
88        fn install(&self, precompiles: &mut PrecompilesMap) {
89            precompiles.set_precompile_lookup(|address: &Address| {
90                address.as_slice().starts_with(&DYNAMIC_PRECOMPILE_PREFIX).then(echo_precompile)
91            });
92        }
93    }
94
95    /// Creates a new Eth EVM instance.
96    fn create_eth_evm(
97        spec: SpecId,
98    ) -> (TxEnv, EthEvm<EmptyDBTyped<Infallible>, NoOpInspector, PrecompilesMap>) {
99        let tx_env = TxEnv {
100            kind: TxKind::Call(PRECOMPILE_ADDR),
101            data: PAYLOAD.into(),
102            ..Default::default()
103        };
104
105        let eth_evm_context = EthEvmContext {
106            journaled_state: Journal::new(EmptyDB::default()),
107            block: BlockEnv::default(),
108            cfg: CfgEnv::new_with_spec(spec),
109            tx: tx_env.clone(),
110            chain: (),
111            local: LocalContext::default(),
112            error: Ok(()),
113        };
114
115        let eth_precompiles = EthPrecompiles {
116            precompiles: Precompiles::new(PrecompileSpecId::from_spec_id(spec)),
117            spec,
118        }
119        .precompiles;
120        let eth_evm = EthEvm::new(
121            RevmEvm::new_with_inspector(
122                eth_evm_context,
123                NoOpInspector,
124                EthInstructions::<EthInterpreter, EthEvmContext<EmptyDB>>::new_mainnet_with_spec(
125                    spec,
126                ),
127                PrecompilesMap::from_static(eth_precompiles),
128            ),
129            true,
130        );
131
132        (tx_env, eth_evm)
133    }
134
135    #[test]
136    fn build_eth_evm_with_extra_precompiles_osaka_spec() {
137        let (tx_env, mut evm) = create_eth_evm(SpecId::OSAKA);
138
139        assert!(evm.precompiles().addresses().contains(&ETH_OSAKA_PRECOMPILE));
140        assert!(evm.precompiles().addresses().contains(&ETH_PRAGUE_PRECOMPILE));
141        assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
142
143        CustomPrecompileFactory.install(evm.precompiles_mut());
144
145        assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
146
147        let result = evm.transact(tx_env).unwrap();
148        assert!(result.result.is_success());
149        assert_eq!(result.result.output(), Some(&PAYLOAD.into()));
150    }
151
152    #[test]
153    fn build_eth_evm_with_extra_precompiles_london_spec() {
154        let (tx_env, mut evm) = create_eth_evm(SpecId::LONDON);
155
156        assert!(!evm.precompiles().addresses().contains(&ETH_OSAKA_PRECOMPILE));
157        assert!(!evm.precompiles().addresses().contains(&ETH_PRAGUE_PRECOMPILE));
158        assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
159
160        CustomPrecompileFactory.install(evm.precompiles_mut());
161
162        assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
163
164        let result = evm.transact(tx_env).unwrap();
165        assert!(result.result.is_success());
166        assert_eq!(result.result.output(), Some(&PAYLOAD.into()));
167    }
168
169    #[test]
170    fn build_eth_evm_with_extra_precompiles_prague_spec() {
171        let (tx_env, mut evm) = create_eth_evm(SpecId::PRAGUE);
172
173        assert!(!evm.precompiles().addresses().contains(&ETH_OSAKA_PRECOMPILE));
174        assert!(evm.precompiles().addresses().contains(&ETH_PRAGUE_PRECOMPILE));
175        assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
176
177        CustomPrecompileFactory.install(evm.precompiles_mut());
178
179        assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
180
181        let result = evm.transact(tx_env).unwrap();
182        assert!(result.result.is_success());
183        assert_eq!(result.result.output(), Some(&PAYLOAD.into()));
184    }
185
186    #[test]
187    fn factory_install_supports_dynamic_lookup() {
188        let (mut tx_env, mut evm) = create_eth_evm(SpecId::PRAGUE);
189        tx_env.kind = TxKind::Call(DYNAMIC_PRECOMPILE_ADDR);
190
191        assert!(!evm.precompiles().addresses().contains(&DYNAMIC_PRECOMPILE_ADDR));
192        assert!(evm.precompiles().get(&DYNAMIC_PRECOMPILE_ADDR).is_none());
193
194        DynamicLookupPrecompileFactory.install(evm.precompiles_mut());
195
196        assert!(!evm.precompiles().addresses().contains(&DYNAMIC_PRECOMPILE_ADDR));
197        assert!(evm.precompiles().get(&DYNAMIC_PRECOMPILE_ADDR).is_some());
198
199        let result = evm.transact(tx_env).unwrap();
200        assert!(result.result.is_success());
201        assert_eq!(result.result.output(), Some(&PAYLOAD.into()));
202    }
203}