reth_evm/system_calls/
mod.rs

1//! System contract call functions.
2
3use crate::ConfigureEvm;
4use alloc::{boxed::Box, sync::Arc};
5use alloy_consensus::BlockHeader;
6use alloy_eips::{
7    eip7002::WITHDRAWAL_REQUEST_TYPE, eip7251::CONSOLIDATION_REQUEST_TYPE, eip7685::Requests,
8};
9use alloy_primitives::Bytes;
10use core::fmt::Display;
11use reth_chainspec::EthereumHardforks;
12use reth_execution_errors::BlockExecutionError;
13use revm::{Database, DatabaseCommit, Evm};
14use revm_primitives::{BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, EvmState, B256};
15
16mod eip2935;
17mod eip4788;
18mod eip7002;
19mod eip7251;
20
21/// A hook that is called after each state change.
22pub trait OnStateHook {
23    /// Invoked with the state after each system call.
24    fn on_state(&mut self, state: &EvmState);
25}
26
27impl<F> OnStateHook for F
28where
29    F: FnMut(&EvmState),
30{
31    fn on_state(&mut self, state: &EvmState) {
32        self(state)
33    }
34}
35
36/// An [`OnStateHook`] that does nothing.
37#[derive(Default, Debug, Clone)]
38#[non_exhaustive]
39pub struct NoopHook;
40
41impl OnStateHook for NoopHook {
42    fn on_state(&mut self, _state: &EvmState) {}
43}
44
45/// An ephemeral helper type for executing system calls.
46///
47/// This can be used to chain system transaction calls.
48#[allow(missing_debug_implementations)]
49pub struct SystemCaller<EvmConfig, Chainspec> {
50    evm_config: EvmConfig,
51    chain_spec: Arc<Chainspec>,
52    /// Optional hook to be called after each state change.
53    hook: Option<Box<dyn OnStateHook>>,
54}
55
56impl<EvmConfig, Chainspec> SystemCaller<EvmConfig, Chainspec> {
57    /// Create a new system caller with the given EVM config, database, and chain spec, and creates
58    /// the EVM with the given initialized config and block environment.
59    pub const fn new(evm_config: EvmConfig, chain_spec: Arc<Chainspec>) -> Self {
60        Self { evm_config, chain_spec, hook: None }
61    }
62
63    /// Installs a custom hook to be called after each state change.
64    pub fn with_state_hook(&mut self, hook: Option<Box<dyn OnStateHook>>) -> &mut Self {
65        self.hook = hook;
66        self
67    }
68
69    /// Convenience method to consume the type and drop borrowed fields
70    pub fn finish(self) {}
71}
72
73fn initialize_evm<'a, DB>(
74    db: &'a mut DB,
75    initialized_cfg: &'a CfgEnvWithHandlerCfg,
76    initialized_block_env: &'a BlockEnv,
77) -> Evm<'a, (), &'a mut DB>
78where
79    DB: Database,
80{
81    Evm::builder()
82        .with_db(db)
83        .with_env_with_handler_cfg(EnvWithHandlerCfg::new_with_cfg_env(
84            initialized_cfg.clone(),
85            initialized_block_env.clone(),
86            Default::default(),
87        ))
88        .build()
89}
90
91impl<EvmConfig, Chainspec> SystemCaller<EvmConfig, Chainspec>
92where
93    EvmConfig: ConfigureEvm,
94    Chainspec: EthereumHardforks,
95{
96    /// Apply pre execution changes.
97    pub fn apply_pre_execution_changes<DB, Ext, Block>(
98        &mut self,
99        block: &Block,
100        evm: &mut Evm<'_, Ext, DB>,
101    ) -> Result<(), BlockExecutionError>
102    where
103        DB: Database + DatabaseCommit,
104        DB::Error: Display,
105        Block: reth_primitives_traits::Block<Header = EvmConfig::Header>,
106    {
107        self.apply_blockhashes_contract_call(
108            block.header().timestamp(),
109            block.header().number(),
110            block.header().parent_hash(),
111            evm,
112        )?;
113        self.apply_beacon_root_contract_call(
114            block.header().timestamp(),
115            block.header().number(),
116            block.header().parent_beacon_block_root(),
117            evm,
118        )?;
119
120        Ok(())
121    }
122
123    /// Apply post execution changes.
124    pub fn apply_post_execution_changes<DB, Ext>(
125        &mut self,
126        evm: &mut Evm<'_, Ext, DB>,
127    ) -> Result<Requests, BlockExecutionError>
128    where
129        DB: Database + DatabaseCommit,
130        DB::Error: Display,
131    {
132        let mut requests = Requests::default();
133
134        // Collect all EIP-7685 requests
135        let withdrawal_requests = self.apply_withdrawal_requests_contract_call(evm)?;
136        if !withdrawal_requests.is_empty() {
137            requests.push_request(
138                core::iter::once(WITHDRAWAL_REQUEST_TYPE).chain(withdrawal_requests).collect(),
139            );
140        }
141
142        // Collect all EIP-7251 requests
143        let consolidation_requests = self.apply_consolidation_requests_contract_call(evm)?;
144        if !consolidation_requests.is_empty() {
145            requests.push_request(
146                core::iter::once(CONSOLIDATION_REQUEST_TYPE)
147                    .chain(consolidation_requests)
148                    .collect(),
149            );
150        }
151
152        Ok(requests)
153    }
154
155    /// Applies the pre-block call to the EIP-2935 blockhashes contract.
156    pub fn pre_block_blockhashes_contract_call<DB>(
157        &mut self,
158        db: &mut DB,
159        initialized_cfg: &CfgEnvWithHandlerCfg,
160        initialized_block_env: &BlockEnv,
161        parent_block_hash: B256,
162    ) -> Result<(), BlockExecutionError>
163    where
164        DB: Database + DatabaseCommit,
165        DB::Error: Display,
166    {
167        let mut evm = initialize_evm(db, initialized_cfg, initialized_block_env);
168        self.apply_blockhashes_contract_call(
169            initialized_block_env.timestamp.to(),
170            initialized_block_env.number.to(),
171            parent_block_hash,
172            &mut evm,
173        )?;
174
175        Ok(())
176    }
177
178    /// Applies the pre-block call to the EIP-2935 blockhashes contract.
179    pub fn apply_blockhashes_contract_call<DB, Ext>(
180        &mut self,
181        timestamp: u64,
182        block_number: u64,
183        parent_block_hash: B256,
184        evm: &mut Evm<'_, Ext, DB>,
185    ) -> Result<(), BlockExecutionError>
186    where
187        DB: Database + DatabaseCommit,
188        DB::Error: Display,
189    {
190        let result_and_state = eip2935::transact_blockhashes_contract_call(
191            &self.evm_config,
192            &self.chain_spec,
193            timestamp,
194            block_number,
195            parent_block_hash,
196            evm,
197        )?;
198
199        if let Some(res) = result_and_state {
200            if let Some(ref mut hook) = self.hook {
201                hook.on_state(&res.state);
202            }
203            evm.context.evm.db.commit(res.state);
204        }
205
206        Ok(())
207    }
208
209    /// Applies the pre-block call to the EIP-4788 beacon root contract.
210    pub fn pre_block_beacon_root_contract_call<DB>(
211        &mut self,
212        db: &mut DB,
213        initialized_cfg: &CfgEnvWithHandlerCfg,
214        initialized_block_env: &BlockEnv,
215        parent_beacon_block_root: Option<B256>,
216    ) -> Result<(), BlockExecutionError>
217    where
218        DB: Database + DatabaseCommit,
219        DB::Error: Display,
220    {
221        let mut evm = initialize_evm(db, initialized_cfg, initialized_block_env);
222
223        self.apply_beacon_root_contract_call(
224            initialized_block_env.timestamp.to(),
225            initialized_block_env.number.to(),
226            parent_beacon_block_root,
227            &mut evm,
228        )?;
229
230        Ok(())
231    }
232
233    /// Applies the pre-block call to the EIP-4788 beacon root contract.
234    pub fn apply_beacon_root_contract_call<DB, Ext>(
235        &mut self,
236        timestamp: u64,
237        block_number: u64,
238        parent_block_hash: Option<B256>,
239        evm: &mut Evm<'_, Ext, DB>,
240    ) -> Result<(), BlockExecutionError>
241    where
242        DB: Database + DatabaseCommit,
243        DB::Error: Display,
244    {
245        let result_and_state = eip4788::transact_beacon_root_contract_call(
246            &self.evm_config,
247            &self.chain_spec,
248            timestamp,
249            block_number,
250            parent_block_hash,
251            evm,
252        )?;
253
254        if let Some(res) = result_and_state {
255            if let Some(ref mut hook) = self.hook {
256                hook.on_state(&res.state);
257            }
258            evm.context.evm.db.commit(res.state);
259        }
260
261        Ok(())
262    }
263
264    /// Applies the post-block call to the EIP-7002 withdrawal request contract.
265    pub fn post_block_withdrawal_requests_contract_call<DB>(
266        &mut self,
267        db: &mut DB,
268        initialized_cfg: &CfgEnvWithHandlerCfg,
269        initialized_block_env: &BlockEnv,
270    ) -> Result<Bytes, BlockExecutionError>
271    where
272        DB: Database + DatabaseCommit,
273        DB::Error: Display,
274    {
275        let mut evm = initialize_evm(db, initialized_cfg, initialized_block_env);
276
277        let result = self.apply_withdrawal_requests_contract_call(&mut evm)?;
278
279        Ok(result)
280    }
281
282    /// Applies the post-block call to the EIP-7002 withdrawal request contract.
283    pub fn apply_withdrawal_requests_contract_call<DB, Ext>(
284        &mut self,
285        evm: &mut Evm<'_, Ext, DB>,
286    ) -> Result<Bytes, BlockExecutionError>
287    where
288        DB: Database + DatabaseCommit,
289        DB::Error: Display,
290    {
291        let result_and_state =
292            eip7002::transact_withdrawal_requests_contract_call(&self.evm_config.clone(), evm)?;
293
294        if let Some(ref mut hook) = self.hook {
295            hook.on_state(&result_and_state.state);
296        }
297        evm.context.evm.db.commit(result_and_state.state);
298
299        eip7002::post_commit(result_and_state.result)
300    }
301
302    /// Applies the post-block call to the EIP-7251 consolidation requests contract.
303    pub fn post_block_consolidation_requests_contract_call<DB>(
304        &mut self,
305        db: &mut DB,
306        initialized_cfg: &CfgEnvWithHandlerCfg,
307        initialized_block_env: &BlockEnv,
308    ) -> Result<Bytes, BlockExecutionError>
309    where
310        DB: Database + DatabaseCommit,
311        DB::Error: Display,
312    {
313        let mut evm = initialize_evm(db, initialized_cfg, initialized_block_env);
314
315        let res = self.apply_consolidation_requests_contract_call(&mut evm)?;
316
317        Ok(res)
318    }
319
320    /// Applies the post-block call to the EIP-7251 consolidation requests contract.
321    pub fn apply_consolidation_requests_contract_call<DB, Ext>(
322        &mut self,
323        evm: &mut Evm<'_, Ext, DB>,
324    ) -> Result<Bytes, BlockExecutionError>
325    where
326        DB: Database + DatabaseCommit,
327        DB::Error: Display,
328    {
329        let result_and_state =
330            eip7251::transact_consolidation_requests_contract_call(&self.evm_config.clone(), evm)?;
331
332        if let Some(ref mut hook) = self.hook {
333            hook.on_state(&result_and_state.state);
334        }
335        evm.context.evm.db.commit(result_and_state.state);
336
337        eip7251::post_commit(result_and_state.result)
338    }
339
340    /// Delegate to stored `OnStateHook`, noop if hook is `None`.
341    pub fn on_state(&mut self, state: &EvmState) {
342        if let Some(ref mut hook) = &mut self.hook {
343            hook.on_state(state);
344        }
345    }
346}