alloy_evm/block/system_calls/
mod.rs

1//! System contract call functions.
2
3use crate::{
4    block::{BlockExecutionError, OnStateHook},
5    Evm,
6};
7use alloc::{borrow::Cow, boxed::Box};
8use alloy_consensus::BlockHeader;
9use alloy_eips::{
10    eip7002::WITHDRAWAL_REQUEST_TYPE, eip7251::CONSOLIDATION_REQUEST_TYPE, eip7685::Requests,
11};
12use alloy_hardforks::EthereumHardforks;
13use alloy_primitives::{Bytes, B256};
14use revm::{state::EvmState, DatabaseCommit};
15
16use super::{StateChangePostBlockSource, StateChangePreBlockSource, StateChangeSource};
17
18mod eip2935;
19mod eip4788;
20mod eip7002;
21mod eip7251;
22
23/// An ephemeral helper type for executing system calls.
24///
25/// This can be used to chain system transaction calls.
26#[derive(derive_more::Debug)]
27pub struct SystemCaller<Spec> {
28    spec: Spec,
29    /// Optional hook to be called after each state change.
30    #[debug(skip)]
31    hook: Option<Box<dyn OnStateHook>>,
32}
33
34impl<Spec> SystemCaller<Spec> {
35    /// Create a new system caller with the given EVM config, database, and chain spec, and creates
36    /// the EVM with the given initialized config and block environment.
37    pub const fn new(spec: Spec) -> Self {
38        Self { spec, hook: None }
39    }
40
41    /// Installs a custom hook to be called after each state change.
42    pub fn with_state_hook(&mut self, hook: Option<Box<dyn OnStateHook>>) -> &mut Self {
43        self.hook = hook;
44        self
45    }
46}
47
48impl<Spec> SystemCaller<Spec>
49where
50    Spec: EthereumHardforks,
51{
52    /// Apply pre execution changes.
53    pub fn apply_pre_execution_changes(
54        &mut self,
55        header: impl BlockHeader,
56        evm: &mut impl Evm<DB: DatabaseCommit>,
57    ) -> Result<(), BlockExecutionError> {
58        self.apply_blockhashes_contract_call(header.parent_hash(), evm)?;
59        self.apply_beacon_root_contract_call(header.parent_beacon_block_root(), evm)?;
60
61        Ok(())
62    }
63
64    /// Apply post execution changes.
65    pub fn apply_post_execution_changes(
66        &mut self,
67        evm: &mut impl Evm<DB: DatabaseCommit>,
68    ) -> Result<Requests, BlockExecutionError> {
69        let mut requests = Requests::default();
70
71        // Collect all EIP-7685 requests
72        let withdrawal_requests = self.apply_withdrawal_requests_contract_call(evm)?;
73        if !withdrawal_requests.is_empty() {
74            requests.push_request_with_type(WITHDRAWAL_REQUEST_TYPE, withdrawal_requests);
75        }
76
77        // Collect all EIP-7251 requests
78        let consolidation_requests = self.apply_consolidation_requests_contract_call(evm)?;
79        if !consolidation_requests.is_empty() {
80            requests.push_request_with_type(CONSOLIDATION_REQUEST_TYPE, consolidation_requests);
81        }
82
83        Ok(requests)
84    }
85
86    /// Applies the pre-block call to the EIP-2935 blockhashes contract.
87    pub fn apply_blockhashes_contract_call(
88        &mut self,
89        parent_block_hash: B256,
90        evm: &mut impl Evm<DB: DatabaseCommit>,
91    ) -> Result<(), BlockExecutionError> {
92        let result_and_state =
93            eip2935::transact_blockhashes_contract_call(&self.spec, parent_block_hash, evm)?;
94
95        if let Some(res) = result_and_state {
96            if let Some(hook) = &mut self.hook {
97                hook.on_state(
98                    StateChangeSource::PreBlock(StateChangePreBlockSource::BlockHashesContract),
99                    &res.state,
100                );
101            }
102            evm.db_mut().commit(res.state);
103        }
104
105        Ok(())
106    }
107
108    /// Applies the pre-block call to the EIP-4788 beacon root contract.
109    pub fn apply_beacon_root_contract_call(
110        &mut self,
111        parent_beacon_block_root: Option<B256>,
112        evm: &mut impl Evm<DB: DatabaseCommit>,
113    ) -> Result<(), BlockExecutionError> {
114        let result_and_state =
115            eip4788::transact_beacon_root_contract_call(&self.spec, parent_beacon_block_root, evm)?;
116
117        if let Some(res) = result_and_state {
118            if let Some(hook) = &mut self.hook {
119                hook.on_state(
120                    StateChangeSource::PreBlock(StateChangePreBlockSource::BeaconRootContract),
121                    &res.state,
122                );
123            }
124            evm.db_mut().commit(res.state);
125        }
126
127        Ok(())
128    }
129
130    /// Applies the post-block call to the EIP-7002 withdrawal request contract.
131    pub fn apply_withdrawal_requests_contract_call(
132        &mut self,
133        evm: &mut impl Evm<DB: DatabaseCommit>,
134    ) -> Result<Bytes, BlockExecutionError> {
135        let result_and_state = eip7002::transact_withdrawal_requests_contract_call(evm)?;
136
137        if let Some(ref mut hook) = &mut self.hook {
138            hook.on_state(
139                StateChangeSource::PostBlock(
140                    StateChangePostBlockSource::WithdrawalRequestsContract,
141                ),
142                &result_and_state.state,
143            );
144        }
145        evm.db_mut().commit(result_and_state.state);
146
147        eip7002::post_commit(result_and_state.result)
148    }
149
150    /// Applies the post-block call to the EIP-7251 consolidation requests contract.
151    pub fn apply_consolidation_requests_contract_call(
152        &mut self,
153        evm: &mut impl Evm<DB: DatabaseCommit>,
154    ) -> Result<Bytes, BlockExecutionError> {
155        let result_and_state = eip7251::transact_consolidation_requests_contract_call(evm)?;
156
157        if let Some(ref mut hook) = &mut self.hook {
158            hook.on_state(
159                StateChangeSource::PostBlock(
160                    StateChangePostBlockSource::ConsolidationRequestsContract,
161                ),
162                &result_and_state.state,
163            );
164        }
165        evm.db_mut().commit(result_and_state.state);
166
167        eip7251::post_commit(result_and_state.result)
168    }
169
170    /// Delegate to stored `OnStateHook`, noop if hook is `None`.
171    pub fn on_state(&mut self, source: StateChangeSource, state: &EvmState) {
172        if let Some(hook) = &mut self.hook {
173            hook.on_state(source, state);
174        }
175    }
176
177    /// Invokes the state hook with the outcome of the given closure, forwards error if any.
178    pub fn try_on_state_with<'a, F, E>(&mut self, f: F) -> Result<(), E>
179    where
180        F: FnOnce() -> Result<(StateChangeSource, Cow<'a, EvmState>), E>,
181    {
182        self.invoke_hook_with(|hook| {
183            let (source, state) = f()?;
184            hook.on_state(source, &state);
185            Ok(())
186        })
187        .unwrap_or(Ok(()))
188    }
189
190    /// Invokes the state hook with the outcome of the given closure.
191    pub fn on_state_with<'a, F>(&mut self, f: F)
192    where
193        F: FnOnce() -> (StateChangeSource, Cow<'a, EvmState>),
194    {
195        self.invoke_hook_with(|hook| {
196            let (source, state) = f();
197            hook.on_state(source, &state);
198        });
199    }
200
201    /// Invokes the given closure with the configured state hook if any.
202    pub fn invoke_hook_with<F, R>(&mut self, f: F) -> Option<R>
203    where
204        F: FnOnce(&mut Box<dyn OnStateHook>) -> R,
205    {
206        self.hook.as_mut().map(f)
207    }
208}