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