Skip to main content

revm_handler/
system_call.rs

1//! System call logic for external state transitions required by certain EIPs (notably [EIP-2935](https://eips.ethereum.org/EIPS/eip-2935) and [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788)).
2//!
3//! These EIPs require the client to perform special system calls to update state (such as block hashes or beacon roots) at block boundaries, outside of normal EVM transaction execution. REVM provides the system call mechanism, but the actual state transitions must be performed by the client or test harness, not by the EVM itself.
4//!
5//! # Example: Using `system_call` for pre/post block hooks
6//!
7//! The client should use [`SystemCallEvm::system_call`] method to perform required state updates before or after block execution, as specified by the EIP:
8//!
9//! ```rust,ignore
10//! // Example: update beacon root (EIP-4788) at the start of a block
11//! let beacon_root: Bytes = ...; // obtained from consensus layer
12//! let beacon_contract: Address = "0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02".parse().unwrap();
13//! evm.system_call(beacon_contract, beacon_root)?;
14//!
15//! // Example: update block hash (EIP-2935) at the end of a block
16//! let block_hash: Bytes = ...; // new block hash
17//! let history_contract: Address = "0x0000F90827F1C53a10cb7A02335B175320002935".parse().unwrap();
18//! evm.system_call(history_contract, block_hash)?;
19//! ```
20//!
21//! See the book section on [External State Transitions](../../book/src/external_state_transitions.md) for more details.
22use crate::{
23    frame::EthFrame, instructions::InstructionProvider, ExecuteCommitEvm, ExecuteEvm, Handler,
24    MainnetHandler, PrecompileProvider,
25};
26use context::{result::ExecResultAndState, ContextSetters, ContextTr, Evm, JournalTr, TxEnv};
27#[cfg(feature = "asyncdb")]
28use database_interface::async_db::{on_fiber_result_with_stack, AsyncResult};
29use database_interface::DatabaseCommit;
30use interpreter::{interpreter::EthInterpreter, InterpreterResult};
31use primitives::{address, eip8037, Address, Bytes, TxKind};
32use state::EvmState;
33#[cfg(feature = "asyncdb")]
34use std::ptr::NonNull;
35
36/// The system address used for system calls.
37pub const SYSTEM_ADDRESS: Address = address!("0xfffffffffffffffffffffffffffffffffffffffe");
38
39/// Maximum number of SSTOREs a system call reserves state gas for under EIP-8037.
40pub const SYSTEM_MAX_SSTORES_PER_CALL: u64 = 16;
41
42/// Gas limit for system calls under EIP-8037.
43///
44/// System calls get the base 30M regular-gas budget plus
45/// a state-gas reservoir sized for `SYSTEM_MAX_SSTORES_PER_CALL` storage writes.
46pub const SYSTEM_CALL_GAS_LIMIT: u64 = 30_000_000
47    + eip8037::SSTORE_SET_BYTES * eip8037::CPSB_GLAMSTERDAM * SYSTEM_MAX_SSTORES_PER_CALL;
48
49/// Creates the system transaction with default values and set data and tx call target to system contract address
50/// that is going to be called.
51///
52/// The caller is set to be [`SYSTEM_ADDRESS`].
53///
54/// It is used inside [`SystemCallEvm`] and [`SystemCallCommitEvm`] traits to prepare EVM for system call execution.
55pub trait SystemCallTx: Sized {
56    /// Creates new transaction for system call.
57    fn new_system_tx(system_contract_address: Address, data: Bytes) -> Self {
58        Self::new_system_tx_with_caller(SYSTEM_ADDRESS, system_contract_address, data)
59    }
60
61    /// Creates a new system transaction with a custom caller address.
62    fn new_system_tx_with_caller(
63        caller: Address,
64        system_contract_address: Address,
65        data: Bytes,
66    ) -> Self;
67}
68
69impl SystemCallTx for TxEnv {
70    fn new_system_tx_with_caller(
71        caller: Address,
72        system_contract_address: Address,
73        data: Bytes,
74    ) -> Self {
75        TxEnv::builder()
76            .caller(caller)
77            .data(data)
78            .kind(TxKind::Call(system_contract_address))
79            .gas_limit(SYSTEM_CALL_GAS_LIMIT)
80            .build()
81            .unwrap()
82    }
83}
84
85/// API for executing the system calls. System calls dont deduct the caller or reward the
86/// beneficiary. They are used before and after block execution to insert or obtain blockchain state.
87///
88/// It act similar to `transact` function and sets default Tx with data and system contract as a target.
89///
90/// # Note
91///
92/// Only one function needs implementation [`SystemCallEvm::system_call_one_with_caller`], other functions
93/// are derived from it.
94pub trait SystemCallEvm: ExecuteEvm {
95    /// System call is a special transaction call that is used to call a system contract.
96    ///
97    /// Transaction fields are reset and set in [`SystemCallTx`] and data and target are set to
98    /// given values.
99    ///
100    /// Block values are taken into account and will determent how system call will be executed.
101    fn system_call_one_with_caller(
102        &mut self,
103        caller: Address,
104        system_contract_address: Address,
105        data: Bytes,
106    ) -> Result<Self::ExecutionResult, Self::Error>;
107
108    /// System call is a special transaction call that is used to call a system contract.
109    ///
110    /// Transaction fields are reset and set in [`SystemCallTx`] and data and target are set to
111    /// given values.
112    ///
113    /// Block values are taken into account and will determent how system call will be executed.
114    fn system_call_one(
115        &mut self,
116        system_contract_address: Address,
117        data: Bytes,
118    ) -> Result<Self::ExecutionResult, Self::Error> {
119        self.system_call_one_with_caller(SYSTEM_ADDRESS, system_contract_address, data)
120    }
121
122    /// Internally calls [`SystemCallEvm::system_call_with_caller`].
123    fn system_call(
124        &mut self,
125        system_contract_address: Address,
126        data: Bytes,
127    ) -> Result<ExecResultAndState<Self::ExecutionResult, Self::State>, Self::Error> {
128        self.system_call_with_caller(SYSTEM_ADDRESS, system_contract_address, data)
129    }
130
131    /// Internally calls [`SystemCallEvm::system_call_one`] and [`ExecuteEvm::finalize`] functions to obtain the changed state.
132    fn system_call_with_caller(
133        &mut self,
134        caller: Address,
135        system_contract_address: Address,
136        data: Bytes,
137    ) -> Result<ExecResultAndState<Self::ExecutionResult, Self::State>, Self::Error> {
138        let result = self.system_call_one_with_caller(caller, system_contract_address, data)?;
139        let state = self.finalize();
140        Ok(ExecResultAndState::new(result, state))
141    }
142
143    /// System call is a special transaction call that is used to call a system contract.
144    ///
145    /// Transaction fields are reset and set in [`SystemCallTx`] and data and target are set to
146    /// given values.
147    ///
148    /// Block values are taken into account and will determent how system call will be executed.
149    #[deprecated(since = "0.1.0", note = "Use `system_call_one_with_caller` instead")]
150    fn transact_system_call_with_caller(
151        &mut self,
152        caller: Address,
153        system_contract_address: Address,
154        data: Bytes,
155    ) -> Result<Self::ExecutionResult, Self::Error> {
156        self.system_call_one_with_caller(caller, system_contract_address, data)
157    }
158
159    /// Calls [`SystemCallEvm::system_call_one`] with [`SYSTEM_ADDRESS`] as a caller.
160    #[deprecated(since = "0.1.0", note = "Use `system_call_one` instead")]
161    fn transact_system_call(
162        &mut self,
163        system_contract_address: Address,
164        data: Bytes,
165    ) -> Result<Self::ExecutionResult, Self::Error> {
166        self.system_call_one(system_contract_address, data)
167    }
168
169    /// Transact the system call and finalize.
170    ///
171    /// Internally calls combo of `transact_system_call` and `finalize` functions.
172    #[deprecated(since = "0.1.0", note = "Use `system_call` instead")]
173    fn transact_system_call_finalize(
174        &mut self,
175        system_contract_address: Address,
176        data: Bytes,
177    ) -> Result<ExecResultAndState<Self::ExecutionResult, Self::State>, Self::Error> {
178        self.system_call(system_contract_address, data)
179    }
180
181    /// Calls [`SystemCallEvm::system_call_one`] and `finalize` functions.
182    #[deprecated(since = "0.1.0", note = "Use `system_call_with_caller` instead")]
183    fn transact_system_call_with_caller_finalize(
184        &mut self,
185        caller: Address,
186        system_contract_address: Address,
187        data: Bytes,
188    ) -> Result<ExecResultAndState<Self::ExecutionResult, Self::State>, Self::Error> {
189        self.system_call_with_caller(caller, system_contract_address, data)
190    }
191}
192
193/// Extension of the [`SystemCallEvm`] trait that adds a method that commits the state after execution.
194pub trait SystemCallCommitEvm: SystemCallEvm + ExecuteCommitEvm {
195    /// Transact the system call and commit to the state.
196    fn system_call_commit(
197        &mut self,
198        system_contract_address: Address,
199        data: Bytes,
200    ) -> Result<Self::ExecutionResult, Self::Error> {
201        self.system_call_with_caller_commit(SYSTEM_ADDRESS, system_contract_address, data)
202    }
203
204    /// Transact the system call and commit to the state.
205    #[deprecated(since = "0.1.0", note = "Use `system_call_commit` instead")]
206    fn transact_system_call_commit(
207        &mut self,
208        system_contract_address: Address,
209        data: Bytes,
210    ) -> Result<Self::ExecutionResult, Self::Error> {
211        self.system_call_commit(system_contract_address, data)
212    }
213
214    /// Calls [`SystemCallCommitEvm::system_call_commit`] with a custom caller.
215    fn system_call_with_caller_commit(
216        &mut self,
217        caller: Address,
218        system_contract_address: Address,
219        data: Bytes,
220    ) -> Result<Self::ExecutionResult, Self::Error>;
221
222    /// Calls [`SystemCallCommitEvm::system_call_commit`] with a custom caller.
223    #[deprecated(since = "0.1.0", note = "Use `system_call_with_caller_commit` instead")]
224    fn transact_system_call_with_caller_commit(
225        &mut self,
226        caller: Address,
227        system_contract_address: Address,
228        data: Bytes,
229    ) -> Result<Self::ExecutionResult, Self::Error> {
230        self.system_call_with_caller_commit(caller, system_contract_address, data)
231    }
232}
233
234/// Async extension of the [`SystemCallEvm`] trait that runs execution on an async fiber.
235#[cfg(feature = "asyncdb")]
236pub trait SystemCallEvmAsync: SystemCallEvm {
237    /// System call executed on an async fiber.
238    fn system_call_one_with_caller_async(
239        &mut self,
240        caller: Address,
241        system_contract_address: Address,
242        data: Bytes,
243    ) -> impl core::future::Future<Output = AsyncResult<Self::ExecutionResult, Self::Error>> + Send + '_;
244
245    /// System call executed on an async fiber.
246    fn system_call_one_async(
247        &mut self,
248        system_contract_address: Address,
249        data: Bytes,
250    ) -> impl core::future::Future<Output = AsyncResult<Self::ExecutionResult, Self::Error>> + Send + '_
251    {
252        self.system_call_one_with_caller_async(SYSTEM_ADDRESS, system_contract_address, data)
253    }
254
255    /// System call executed and finalized on an async fiber.
256    fn system_call_with_caller_async(
257        &mut self,
258        caller: Address,
259        system_contract_address: Address,
260        data: Bytes,
261    ) -> impl core::future::Future<
262        Output = AsyncResult<ExecResultAndState<Self::ExecutionResult, Self::State>, Self::Error>,
263    > + Send
264           + '_;
265
266    /// System call executed and finalized on an async fiber.
267    fn system_call_async(
268        &mut self,
269        system_contract_address: Address,
270        data: Bytes,
271    ) -> impl core::future::Future<
272        Output = AsyncResult<ExecResultAndState<Self::ExecutionResult, Self::State>, Self::Error>,
273    > + Send
274           + '_ {
275        self.system_call_with_caller_async(SYSTEM_ADDRESS, system_contract_address, data)
276    }
277}
278
279impl<CTX, INSP, INST, PRECOMPILES> SystemCallEvm
280    for Evm<CTX, INSP, INST, PRECOMPILES, EthFrame<EthInterpreter>>
281where
282    CTX: ContextTr<Journal: JournalTr<State = EvmState>, Tx: SystemCallTx> + ContextSetters,
283    INST: InstructionProvider<Context = CTX, InterpreterTypes = EthInterpreter>,
284    PRECOMPILES: PrecompileProvider<CTX, Output = InterpreterResult>,
285{
286    fn system_call_one_with_caller(
287        &mut self,
288        caller: Address,
289        system_contract_address: Address,
290        data: Bytes,
291    ) -> Result<Self::ExecutionResult, Self::Error> {
292        // set tx fields.
293        self.set_tx(CTX::Tx::new_system_tx_with_caller(
294            caller,
295            system_contract_address,
296            data,
297        ));
298        // create handler
299        MainnetHandler::default().run_system_call(self)
300    }
301}
302
303#[cfg(feature = "asyncdb")]
304impl<CTX, INSP, INST, PRECOMPILES> SystemCallEvmAsync
305    for Evm<CTX, INSP, INST, PRECOMPILES, EthFrame<EthInterpreter>>
306where
307    CTX: ContextTr<Journal: JournalTr<State = EvmState>, Tx: SystemCallTx> + ContextSetters,
308    INST: InstructionProvider<Context = CTX, InterpreterTypes = EthInterpreter>,
309    PRECOMPILES: PrecompileProvider<CTX, Output = InterpreterResult>,
310    Self: ExecuteEvm,
311    <Self as ExecuteEvm>::ExecutionResult: Send,
312    <Self as ExecuteEvm>::State: Send,
313    <Self as ExecuteEvm>::Error: Send,
314{
315    #[inline]
316    fn system_call_one_with_caller_async(
317        &mut self,
318        caller: Address,
319        system_contract_address: Address,
320        data: Bytes,
321    ) -> impl core::future::Future<Output = AsyncResult<Self::ExecutionResult, Self::Error>> + Send + '_
322    {
323        let stack = NonNull::from(&mut self.async_stack);
324        // SAFETY: The returned future owns the exclusive `&mut self` borrow, so nothing else can
325        // access the EVM stack slot until that future is dropped.
326        unsafe {
327            on_fiber_result_with_stack(stack, move || {
328                self.system_call_one_with_caller(caller, system_contract_address, data)
329            })
330        }
331    }
332
333    #[inline]
334    fn system_call_with_caller_async(
335        &mut self,
336        caller: Address,
337        system_contract_address: Address,
338        data: Bytes,
339    ) -> impl core::future::Future<
340        Output = AsyncResult<ExecResultAndState<Self::ExecutionResult, Self::State>, Self::Error>,
341    > + Send
342           + '_ {
343        let stack = NonNull::from(&mut self.async_stack);
344        // SAFETY: The returned future owns the exclusive `&mut self` borrow, so nothing else can
345        // access the EVM stack slot until that future is dropped.
346        unsafe {
347            on_fiber_result_with_stack(stack, move || {
348                self.system_call_with_caller(caller, system_contract_address, data)
349            })
350        }
351    }
352}
353
354impl<CTX, INSP, INST, PRECOMPILES> SystemCallCommitEvm
355    for Evm<CTX, INSP, INST, PRECOMPILES, EthFrame<EthInterpreter>>
356where
357    CTX: ContextTr<Journal: JournalTr<State = EvmState>, Db: DatabaseCommit, Tx: SystemCallTx>
358        + ContextSetters,
359    INST: InstructionProvider<Context = CTX, InterpreterTypes = EthInterpreter>,
360    PRECOMPILES: PrecompileProvider<CTX, Output = InterpreterResult>,
361{
362    fn system_call_with_caller_commit(
363        &mut self,
364        caller: Address,
365        system_contract_address: Address,
366        data: Bytes,
367    ) -> Result<Self::ExecutionResult, Self::Error> {
368        self.system_call_with_caller(caller, system_contract_address, data)
369            .map(|output| {
370                self.db_mut().commit(output.state);
371                output.result
372            })
373    }
374}
375
376#[cfg(test)]
377mod tests {
378    use crate::{MainBuilder, MainContext};
379
380    use super::*;
381    use context::{
382        result::{ExecutionResult, Output, ResultGas, SuccessReason},
383        Context, Transaction,
384    };
385    use database::InMemoryDB;
386    use primitives::{b256, bytes, StorageKey, U256};
387    use state::{AccountInfo, Bytecode};
388
389    const HISTORY_STORAGE_ADDRESS: Address = address!("0x0000F90827F1C53a10cb7A02335B175320002935");
390    static HISTORY_STORAGE_CODE: Bytes = bytes!("0x3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500");
391
392    #[test]
393    fn test_system_call() {
394        let mut db = InMemoryDB::default();
395        db.insert_account_info(
396            HISTORY_STORAGE_ADDRESS,
397            AccountInfo::default().with_code(Bytecode::new_legacy(HISTORY_STORAGE_CODE.clone())),
398        );
399
400        let block_hash =
401            b256!("0x1111111111111111111111111111111111111111111111111111111111111111");
402
403        let mut evm = Context::mainnet()
404            .with_db(db)
405            // block with number 1 will set storage at slot 0.
406            .modify_block_chained(|b| b.number = U256::ONE)
407            .build_mainnet();
408        let output = evm
409            .system_call(HISTORY_STORAGE_ADDRESS, block_hash.0.into())
410            .unwrap();
411
412        // EIP-8037 adds a state-gas reservoir on top of the 30M base limit.
413        assert_eq!(evm.ctx.tx().gas_limit(), SYSTEM_CALL_GAS_LIMIT);
414
415        assert_eq!(
416            output.result,
417            ExecutionResult::Success {
418                reason: SuccessReason::Stop,
419                gas: ResultGas::default().with_total_gas_spent(22143),
420                logs: vec![],
421                output: Output::Call(Bytes::default())
422            }
423        );
424        // only system contract is updated and present
425        assert_eq!(output.state.len(), 1);
426        assert_eq!(
427            output.state[&HISTORY_STORAGE_ADDRESS]
428                .storage
429                .get(&StorageKey::from(0))
430                .map(|slot| slot.present_value)
431                .unwrap_or_default(),
432            U256::from_be_bytes(block_hash.0),
433            "State is not updated {:?}",
434            output.state
435        );
436    }
437}