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
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 pub(super) const ETH_PRAGUE_PRECOMPILE: Address = Address::with_last_byte(0x11);
44
45 const ETH_OSAKA_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000100");
47
48 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 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(Ð_OSAKA_PRECOMPILE));
136 assert!(evm.precompiles().addresses().contains(Ð_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(Ð_OSAKA_PRECOMPILE));
153 assert!(!evm.precompiles().addresses().contains(Ð_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(Ð_OSAKA_PRECOMPILE));
170 assert!(evm.precompiles().addresses().contains(Ð_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}