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 `transact_system_call` for pre/post block hooks
6//!
7//! The client should use [`SystemCallEvm::transact_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.transact_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.transact_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, eip7825, 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(eip7825::TX_GAS_LIMIT_CAP)
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.
75pub trait SystemCallEvm: ExecuteEvm {
76    /// System call is a special transaction call that is used to call a system contract.
77    ///
78    /// Transaction fields are reset and set in [`SystemCallTx`] and data and target are set to
79    /// given values.
80    ///
81    /// Block values are taken into account and will determent how system call will be executed.
82    fn transact_system_call_with_caller(
83        &mut self,
84        caller: Address,
85        system_contract_address: Address,
86        data: Bytes,
87    ) -> Result<Self::ExecutionResult, Self::Error>;
88
89    /// Calls [`SystemCallEvm::transact_system_call_with_caller`] with [`SYSTEM_ADDRESS`] as a caller.
90    fn transact_system_call(
91        &mut self,
92        system_contract_address: Address,
93        data: Bytes,
94    ) -> Result<Self::ExecutionResult, Self::Error> {
95        self.transact_system_call_with_caller(SYSTEM_ADDRESS, system_contract_address, data)
96    }
97
98    /// Transact the system call and finalize.
99    ///
100    /// Internally calls combo of `transact_system_call` and `finalize` functions.
101    fn transact_system_call_finalize(
102        &mut self,
103        system_contract_address: Address,
104        data: Bytes,
105    ) -> Result<ExecResultAndState<Self::ExecutionResult, Self::State>, Self::Error> {
106        self.transact_system_call_with_caller_finalize(
107            SYSTEM_ADDRESS,
108            system_contract_address,
109            data,
110        )
111    }
112
113    /// Calls [`SystemCallEvm::transact_system_call_with_caller`] and `finalize` functions.
114    fn transact_system_call_with_caller_finalize(
115        &mut self,
116        caller: Address,
117        system_contract_address: Address,
118        data: Bytes,
119    ) -> Result<ExecResultAndState<Self::ExecutionResult, Self::State>, Self::Error> {
120        let result =
121            self.transact_system_call_with_caller(caller, system_contract_address, data)?;
122        let state = self.finalize();
123        Ok(ExecResultAndState::new(result, state))
124    }
125}
126
127/// Extension of the [`SystemCallEvm`] trait that adds a method that commits the state after execution.
128pub trait SystemCallCommitEvm: SystemCallEvm + ExecuteCommitEvm {
129    /// Transact the system call and commit to the state.
130    fn transact_system_call_commit(
131        &mut self,
132        system_contract_address: Address,
133        data: Bytes,
134    ) -> Result<Self::ExecutionResult, Self::Error> {
135        self.transact_system_call_with_caller_commit(SYSTEM_ADDRESS, system_contract_address, data)
136    }
137
138    /// Calls [`SystemCallCommitEvm::transact_system_call_commit`] with [`SYSTEM_ADDRESS`] as a caller.
139    fn transact_system_call_with_caller_commit(
140        &mut self,
141        caller: Address,
142        system_contract_address: Address,
143        data: Bytes,
144    ) -> Result<Self::ExecutionResult, Self::Error>;
145}
146
147impl<CTX, INSP, INST, PRECOMPILES> SystemCallEvm
148    for Evm<CTX, INSP, INST, PRECOMPILES, EthFrame<EthInterpreter>>
149where
150    CTX: ContextTr<Journal: JournalTr<State = EvmState>, Tx: SystemCallTx> + ContextSetters,
151    INST: InstructionProvider<Context = CTX, InterpreterTypes = EthInterpreter>,
152    PRECOMPILES: PrecompileProvider<CTX, Output = InterpreterResult>,
153{
154    fn transact_system_call_with_caller(
155        &mut self,
156        caller: Address,
157        system_contract_address: Address,
158        data: Bytes,
159    ) -> Result<Self::ExecutionResult, Self::Error> {
160        // set tx fields.
161        self.set_tx(CTX::Tx::new_system_tx_with_caller(
162            caller,
163            system_contract_address,
164            data,
165        ));
166        // create handler
167        MainnetHandler::default().run_system_call(self)
168    }
169}
170
171impl<CTX, INSP, INST, PRECOMPILES> SystemCallCommitEvm
172    for Evm<CTX, INSP, INST, PRECOMPILES, EthFrame<EthInterpreter>>
173where
174    CTX: ContextTr<Journal: JournalTr<State = EvmState>, Db: DatabaseCommit, Tx: SystemCallTx>
175        + ContextSetters,
176    INST: InstructionProvider<Context = CTX, InterpreterTypes = EthInterpreter>,
177    PRECOMPILES: PrecompileProvider<CTX, Output = InterpreterResult>,
178{
179    fn transact_system_call_with_caller_commit(
180        &mut self,
181        caller: Address,
182        system_contract_address: Address,
183        data: Bytes,
184    ) -> Result<Self::ExecutionResult, Self::Error> {
185        self.transact_system_call_with_caller_finalize(caller, system_contract_address, data)
186            .map(|output| {
187                self.db_mut().commit(output.state);
188                output.result
189            })
190    }
191}
192
193#[cfg(test)]
194mod tests {
195    use crate::{MainBuilder, MainContext};
196
197    use super::*;
198    use context::{
199        result::{ExecutionResult, Output, SuccessReason},
200        Context,
201    };
202    use database::InMemoryDB;
203    use primitives::{b256, bytes, StorageKey, U256};
204    use state::{AccountInfo, Bytecode};
205
206    const HISTORY_STORAGE_ADDRESS: Address = address!("0x0000F90827F1C53a10cb7A02335B175320002935");
207    static HISTORY_STORAGE_CODE: Bytes = bytes!("0x3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500");
208
209    #[test]
210    fn test_system_call() {
211        let mut db = InMemoryDB::default();
212        db.insert_account_info(
213            HISTORY_STORAGE_ADDRESS,
214            AccountInfo::default().with_code(Bytecode::new_legacy(HISTORY_STORAGE_CODE.clone())),
215        );
216
217        let block_hash =
218            b256!("0x1111111111111111111111111111111111111111111111111111111111111111");
219
220        let mut my_evm = Context::mainnet()
221            .with_db(db)
222            // block with number 1 will set storage at slot 0.
223            .modify_block_chained(|b| b.number = U256::ONE)
224            .build_mainnet();
225        let output = my_evm
226            .transact_system_call_finalize(HISTORY_STORAGE_ADDRESS, block_hash.0.into())
227            .unwrap();
228
229        assert_eq!(
230            output.result,
231            ExecutionResult::Success {
232                reason: SuccessReason::Stop,
233                gas_used: 22143,
234                gas_refunded: 0,
235                logs: vec![],
236                output: Output::Call(Bytes::default())
237            }
238        );
239        // only system contract is updated and present
240        assert_eq!(output.state.len(), 1);
241        assert_eq!(
242            output.state[&HISTORY_STORAGE_ADDRESS]
243                .storage
244                .get(&StorageKey::from(0))
245                .map(|slot| slot.present_value)
246                .unwrap_or_default(),
247            U256::from_be_bytes(block_hash.0),
248            "State is not updated {:?}",
249            output.state
250        );
251    }
252}