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                state_gas_spilled: 0,
67                reservoir: input.reservoir,
68            })
69        })
70    }
71
72    #[derive(Debug)]
73    pub(super) struct CustomPrecompileFactory;
74
75    impl PrecompileFactory for CustomPrecompileFactory {
76        fn precompiles(&self) -> Vec<(Address, DynPrecompile)> {
77            vec![(PRECOMPILE_ADDR, echo_precompile())]
78        }
79    }
80
81    #[derive(Debug)]
82    struct DynamicLookupPrecompileFactory;
83
84    impl PrecompileFactory for DynamicLookupPrecompileFactory {
85        fn precompiles(&self) -> Vec<(Address, DynPrecompile)> {
86            Vec::new()
87        }
88
89        fn install(&self, precompiles: &mut PrecompilesMap) {
90            precompiles.set_precompile_lookup(|address: &Address| {
91                address.as_slice().starts_with(&DYNAMIC_PRECOMPILE_PREFIX).then(echo_precompile)
92            });
93        }
94    }
95
96    /// Creates a new Eth EVM instance.
97    fn create_eth_evm(
98        spec: SpecId,
99    ) -> (TxEnv, EthEvm<EmptyDBTyped<Infallible>, NoOpInspector, PrecompilesMap>) {
100        let tx_env = TxEnv {
101            kind: TxKind::Call(PRECOMPILE_ADDR),
102            data: PAYLOAD.into(),
103            ..Default::default()
104        };
105
106        let eth_evm_context = EthEvmContext {
107            journaled_state: Journal::new(EmptyDB::default()),
108            block: BlockEnv::default(),
109            cfg: CfgEnv::new_with_spec(spec),
110            tx: tx_env.clone(),
111            chain: (),
112            local: LocalContext::default(),
113            error: Ok(()),
114        };
115
116        let eth_precompiles = EthPrecompiles {
117            precompiles: Precompiles::new(PrecompileSpecId::from_spec_id(spec)),
118            spec,
119        }
120        .precompiles;
121        let eth_evm = EthEvm::new(
122            RevmEvm::new_with_inspector(
123                eth_evm_context,
124                NoOpInspector,
125                EthInstructions::<EthInterpreter, EthEvmContext<EmptyDB>>::new_mainnet_with_spec(
126                    spec,
127                ),
128                PrecompilesMap::from_static(eth_precompiles),
129            ),
130            true,
131        );
132
133        (tx_env, eth_evm)
134    }
135
136    #[test]
137    fn build_eth_evm_with_extra_precompiles_osaka_spec() {
138        let (tx_env, mut evm) = create_eth_evm(SpecId::OSAKA);
139
140        assert!(evm.precompiles().addresses().contains(&ETH_OSAKA_PRECOMPILE));
141        assert!(evm.precompiles().addresses().contains(&ETH_PRAGUE_PRECOMPILE));
142        assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
143
144        CustomPrecompileFactory.install(evm.precompiles_mut());
145
146        assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
147
148        let result = evm.transact(tx_env).unwrap();
149        assert!(result.result.is_success());
150        assert_eq!(result.result.output(), Some(&PAYLOAD.into()));
151    }
152
153    #[test]
154    fn build_eth_evm_with_extra_precompiles_london_spec() {
155        let (tx_env, mut evm) = create_eth_evm(SpecId::LONDON);
156
157        assert!(!evm.precompiles().addresses().contains(&ETH_OSAKA_PRECOMPILE));
158        assert!(!evm.precompiles().addresses().contains(&ETH_PRAGUE_PRECOMPILE));
159        assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
160
161        CustomPrecompileFactory.install(evm.precompiles_mut());
162
163        assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
164
165        let result = evm.transact(tx_env).unwrap();
166        assert!(result.result.is_success());
167        assert_eq!(result.result.output(), Some(&PAYLOAD.into()));
168    }
169
170    #[test]
171    fn build_eth_evm_with_extra_precompiles_prague_spec() {
172        let (tx_env, mut evm) = create_eth_evm(SpecId::PRAGUE);
173
174        assert!(!evm.precompiles().addresses().contains(&ETH_OSAKA_PRECOMPILE));
175        assert!(evm.precompiles().addresses().contains(&ETH_PRAGUE_PRECOMPILE));
176        assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
177
178        CustomPrecompileFactory.install(evm.precompiles_mut());
179
180        assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR));
181
182        let result = evm.transact(tx_env).unwrap();
183        assert!(result.result.is_success());
184        assert_eq!(result.result.output(), Some(&PAYLOAD.into()));
185    }
186
187    #[test]
188    fn factory_install_supports_dynamic_lookup() {
189        let (mut tx_env, mut evm) = create_eth_evm(SpecId::PRAGUE);
190        tx_env.kind = TxKind::Call(DYNAMIC_PRECOMPILE_ADDR);
191
192        assert!(!evm.precompiles().addresses().contains(&DYNAMIC_PRECOMPILE_ADDR));
193        assert!(evm.precompiles().get(&DYNAMIC_PRECOMPILE_ADDR).is_none());
194
195        DynamicLookupPrecompileFactory.install(evm.precompiles_mut());
196
197        assert!(!evm.precompiles().addresses().contains(&DYNAMIC_PRECOMPILE_ADDR));
198        assert!(evm.precompiles().get(&DYNAMIC_PRECOMPILE_ADDR).is_some());
199
200        let result = evm.transact(tx_env).unwrap();
201        assert!(result.result.is_success());
202        assert_eq!(result.result.output(), Some(&PAYLOAD.into()));
203    }
204}