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};
27use database_interface::DatabaseCommit;
28use interpreter::{interpreter::EthInterpreter, InterpreterResult};
29use primitives::{address, Address, Bytes, TxKind};
30use state::EvmState;
31
32/// The system address used for system calls.
33pub const SYSTEM_ADDRESS: Address = address!("0xfffffffffffffffffffffffffffffffffffffffe");
34
35/// Creates the system transaction with default values and set data and tx call target to system contract address
36/// that is going to be called.
37///
38/// The caller is set to be [`SYSTEM_ADDRESS`].
39///
40/// It is used inside [`SystemCallEvm`] and [`SystemCallCommitEvm`] traits to prepare EVM for system call execution.
41pub trait SystemCallTx: Sized {
42    /// Creates new transaction for system call.
43    fn new_system_tx(system_contract_address: Address, data: Bytes) -> Self {
44        Self::new_system_tx_with_caller(SYSTEM_ADDRESS, system_contract_address, data)
45    }
46
47    /// Creates a new system transaction with a custom caller address.
48    fn new_system_tx_with_caller(
49        caller: Address,
50        system_contract_address: Address,
51        data: Bytes,
52    ) -> Self;
53}
54
55impl SystemCallTx for TxEnv {
56    fn new_system_tx_with_caller(
57        caller: Address,
58        system_contract_address: Address,
59        data: Bytes,
60    ) -> Self {
61        TxEnv::builder()
62            .caller(caller)
63            .data(data)
64            .kind(TxKind::Call(system_contract_address))
65            .gas_limit(30_000_000)
66            .build()
67            .unwrap()
68    }
69}
70
71/// API for executing the system calls. System calls dont deduct the caller or reward the
72/// beneficiary. They are used before and after block execution to insert or obtain blockchain state.
73///
74/// It act similar to `transact` function and sets default Tx with data and system contract as a target.
75///
76/// # Note
77///
78/// Only one function needs implementation [`SystemCallEvm::system_call_one_with_caller`], other functions
79/// are derived from it.
80pub trait SystemCallEvm: ExecuteEvm {
81    /// System call is a special transaction call that is used to call a system contract.
82    ///
83    /// Transaction fields are reset and set in [`SystemCallTx`] and data and target are set to
84    /// given values.
85    ///
86    /// Block values are taken into account and will determent how system call will be executed.
87    fn system_call_one_with_caller(
88        &mut self,
89        caller: Address,
90        system_contract_address: Address,
91        data: Bytes,
92    ) -> Result<Self::ExecutionResult, Self::Error>;
93
94    /// System call is a special transaction call that is used to call a system contract.
95    ///
96    /// Transaction fields are reset and set in [`SystemCallTx`] and data and target are set to
97    /// given values.
98    ///
99    /// Block values are taken into account and will determent how system call will be executed.
100    fn system_call_one(
101        &mut self,
102        system_contract_address: Address,
103        data: Bytes,
104    ) -> Result<Self::ExecutionResult, Self::Error> {
105        self.system_call_one_with_caller(SYSTEM_ADDRESS, system_contract_address, data)
106    }
107
108    /// Internally calls [`SystemCallEvm::system_call_with_caller`].
109    fn system_call(
110        &mut self,
111        system_contract_address: Address,
112        data: Bytes,
113    ) -> Result<ExecResultAndState<Self::ExecutionResult, Self::State>, Self::Error> {
114        self.system_call_with_caller(SYSTEM_ADDRESS, system_contract_address, data)
115    }
116
117    /// Internally calls [`SystemCallEvm::system_call_one`] and [`ExecuteEvm::finalize`] functions to obtain the changed state.
118    fn system_call_with_caller(
119        &mut self,
120        caller: Address,
121        system_contract_address: Address,
122        data: Bytes,
123    ) -> Result<ExecResultAndState<Self::ExecutionResult, Self::State>, Self::Error> {
124        let result = self.system_call_one_with_caller(caller, system_contract_address, data)?;
125        let state = self.finalize();
126        Ok(ExecResultAndState::new(result, state))
127    }
128
129    /// System call is a special transaction call that is used to call a system contract.
130    ///
131    /// Transaction fields are reset and set in [`SystemCallTx`] and data and target are set to
132    /// given values.
133    ///
134    /// Block values are taken into account and will determent how system call will be executed.
135    #[deprecated(since = "0.1.0", note = "Use `system_call_one_with_caller` instead")]
136    fn transact_system_call_with_caller(
137        &mut self,
138        caller: Address,
139        system_contract_address: Address,
140        data: Bytes,
141    ) -> Result<Self::ExecutionResult, Self::Error> {
142        self.system_call_one_with_caller(caller, system_contract_address, data)
143    }
144
145    /// Calls [`SystemCallEvm::system_call_one`] with [`SYSTEM_ADDRESS`] as a caller.
146    #[deprecated(since = "0.1.0", note = "Use `system_call_one` instead")]
147    fn transact_system_call(
148        &mut self,
149        system_contract_address: Address,
150        data: Bytes,
151    ) -> Result<Self::ExecutionResult, Self::Error> {
152        self.system_call_one(system_contract_address, data)
153    }
154
155    /// Transact the system call and finalize.
156    ///
157    /// Internally calls combo of `transact_system_call` and `finalize` functions.
158    #[deprecated(since = "0.1.0", note = "Use `system_call` instead")]
159    fn transact_system_call_finalize(
160        &mut self,
161        system_contract_address: Address,
162        data: Bytes,
163    ) -> Result<ExecResultAndState<Self::ExecutionResult, Self::State>, Self::Error> {
164        self.system_call(system_contract_address, data)
165    }
166
167    /// Calls [`SystemCallEvm::system_call_one`] and `finalize` functions.
168    #[deprecated(since = "0.1.0", note = "Use `system_call_with_caller` instead")]
169    fn transact_system_call_with_caller_finalize(
170        &mut self,
171        caller: Address,
172        system_contract_address: Address,
173        data: Bytes,
174    ) -> Result<ExecResultAndState<Self::ExecutionResult, Self::State>, Self::Error> {
175        self.system_call_with_caller(caller, system_contract_address, data)
176    }
177}
178
179/// Extension of the [`SystemCallEvm`] trait that adds a method that commits the state after execution.
180pub trait SystemCallCommitEvm: SystemCallEvm + ExecuteCommitEvm {
181    /// Transact the system call and commit to the state.
182    fn system_call_commit(
183        &mut self,
184        system_contract_address: Address,
185        data: Bytes,
186    ) -> Result<Self::ExecutionResult, Self::Error> {
187        self.system_call_with_caller_commit(SYSTEM_ADDRESS, system_contract_address, data)
188    }
189
190    /// Transact the system call and commit to the state.
191    #[deprecated(since = "0.1.0", note = "Use `system_call_commit` instead")]
192    fn transact_system_call_commit(
193        &mut self,
194        system_contract_address: Address,
195        data: Bytes,
196    ) -> Result<Self::ExecutionResult, Self::Error> {
197        self.system_call_commit(system_contract_address, data)
198    }
199
200    /// Calls [`SystemCallCommitEvm::system_call_commit`] with a custom caller.
201    fn system_call_with_caller_commit(
202        &mut self,
203        caller: Address,
204        system_contract_address: Address,
205        data: Bytes,
206    ) -> Result<Self::ExecutionResult, Self::Error>;
207
208    /// Calls [`SystemCallCommitEvm::system_call_commit`] with a custom caller.
209    #[deprecated(since = "0.1.0", note = "Use `system_call_with_caller_commit` instead")]
210    fn transact_system_call_with_caller_commit(
211        &mut self,
212        caller: Address,
213        system_contract_address: Address,
214        data: Bytes,
215    ) -> Result<Self::ExecutionResult, Self::Error> {
216        self.system_call_with_caller_commit(caller, system_contract_address, data)
217    }
218}
219
220impl<CTX, INSP, INST, PRECOMPILES> SystemCallEvm
221    for Evm<CTX, INSP, INST, PRECOMPILES, EthFrame<EthInterpreter>>
222where
223    CTX: ContextTr<Journal: JournalTr<State = EvmState>, Tx: SystemCallTx> + ContextSetters,
224    INST: InstructionProvider<Context = CTX, InterpreterTypes = EthInterpreter>,
225    PRECOMPILES: PrecompileProvider<CTX, Output = InterpreterResult>,
226{
227    fn system_call_one_with_caller(
228        &mut self,
229        caller: Address,
230        system_contract_address: Address,
231        data: Bytes,
232    ) -> Result<Self::ExecutionResult, Self::Error> {
233        // set tx fields.
234        self.set_tx(CTX::Tx::new_system_tx_with_caller(
235            caller,
236            system_contract_address,
237            data,
238        ));
239        // create handler
240        MainnetHandler::default().run_system_call(self)
241    }
242}
243
244impl<CTX, INSP, INST, PRECOMPILES> SystemCallCommitEvm
245    for Evm<CTX, INSP, INST, PRECOMPILES, EthFrame<EthInterpreter>>
246where
247    CTX: ContextTr<Journal: JournalTr<State = EvmState>, Db: DatabaseCommit, Tx: SystemCallTx>
248        + ContextSetters,
249    INST: InstructionProvider<Context = CTX, InterpreterTypes = EthInterpreter>,
250    PRECOMPILES: PrecompileProvider<CTX, Output = InterpreterResult>,
251{
252    fn system_call_with_caller_commit(
253        &mut self,
254        caller: Address,
255        system_contract_address: Address,
256        data: Bytes,
257    ) -> Result<Self::ExecutionResult, Self::Error> {
258        self.system_call_with_caller(caller, system_contract_address, data)
259            .map(|output| {
260                self.db_mut().commit(output.state);
261                output.result
262            })
263    }
264}
265
266#[cfg(test)]
267mod tests {
268    use crate::{MainBuilder, MainContext};
269
270    use super::*;
271    use context::{
272        result::{ExecutionResult, Output, SuccessReason},
273        Context, Transaction,
274    };
275    use database::InMemoryDB;
276    use primitives::{b256, bytes, StorageKey, U256};
277    use state::{AccountInfo, Bytecode};
278
279    const HISTORY_STORAGE_ADDRESS: Address = address!("0x0000F90827F1C53a10cb7A02335B175320002935");
280    static HISTORY_STORAGE_CODE: Bytes = bytes!("0x3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500");
281
282    #[test]
283    fn test_system_call() {
284        let mut db = InMemoryDB::default();
285        db.insert_account_info(
286            HISTORY_STORAGE_ADDRESS,
287            AccountInfo::default().with_code(Bytecode::new_legacy(HISTORY_STORAGE_CODE.clone())),
288        );
289
290        let block_hash =
291            b256!("0x1111111111111111111111111111111111111111111111111111111111111111");
292
293        let mut evm = Context::mainnet()
294            .with_db(db)
295            // block with number 1 will set storage at slot 0.
296            .modify_block_chained(|b| b.number = U256::ONE)
297            .build_mainnet();
298        let output = evm
299            .system_call(HISTORY_STORAGE_ADDRESS, block_hash.0.into())
300            .unwrap();
301
302        // system call gas limit is 30M
303        assert_eq!(evm.ctx.tx().gas_limit(), 30_000_000);
304
305        assert_eq!(
306            output.result,
307            ExecutionResult::Success {
308                reason: SuccessReason::Stop,
309                gas_used: 22143,
310                gas_refunded: 0,
311                logs: vec![],
312                output: Output::Call(Bytes::default())
313            }
314        );
315        // only system contract is updated and present
316        assert_eq!(output.state.len(), 1);
317        assert_eq!(
318            output.state[&HISTORY_STORAGE_ADDRESS]
319                .storage
320                .get(&StorageKey::from(0))
321                .map(|slot| slot.present_value)
322                .unwrap_or_default(),
323            U256::from_be_bytes(block_hash.0),
324            "State is not updated {:?}",
325            output.state
326        );
327    }
328}