1use alloy_evm::precompiles::{DynPrecompile, PrecompilesMap};
2use alloy_primitives::Address;
3use std::fmt::Debug;
4
5#[cfg(feature = "optimism")]
6mod optimism;
7
8pub trait PrecompileFactory: Send + Sync + Unpin + Debug {
11 fn precompiles(&self) -> Vec<(Address, DynPrecompile)>;
13
14 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 pub(super) const ETH_PRAGUE_PRECOMPILE: Address =
45 address!("0x0000000000000000000000000000000000000011");
46
47 const ETH_OSAKA_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000100");
49
50 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 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(Ð_OSAKA_PRECOMPILE));
141 assert!(evm.precompiles().addresses().contains(Ð_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(Ð_OSAKA_PRECOMPILE));
158 assert!(!evm.precompiles().addresses().contains(Ð_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(Ð_OSAKA_PRECOMPILE));
175 assert!(evm.precompiles().addresses().contains(Ð_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}