revm_handler/
system_call.rs

1use crate::{
2    frame::EthFrame, instructions::InstructionProvider, ExecuteCommitEvm, ExecuteEvm, Handler,
3    MainnetHandler, PrecompileProvider,
4};
5use context::{
6    result::ExecResultAndState, ContextSetters, ContextTr, Evm, JournalTr, TransactionType, TxEnv,
7};
8use database_interface::DatabaseCommit;
9use interpreter::{interpreter::EthInterpreter, InterpreterResult};
10use primitives::{address, eip7825, Address, Bytes, TxKind};
11use state::EvmState;
12
13/// The system address used for system calls.
14pub const SYSTEM_ADDRESS: Address = address!("0xfffffffffffffffffffffffffffffffffffffffe");
15
16/// Creates the system transaction with default values and set data and tx call target to system contract address
17/// that is going to be called.
18///
19/// The caller is set to be [`SYSTEM_ADDRESS`].
20///
21/// It is used inside [`SystemCallEvm`] and [`SystemCallCommitEvm`] traits to prepare EVM for system call execution.
22pub trait SystemCallTx: Sized {
23    /// Creates new transaction for system call.
24    fn new_system_tx(system_contract_address: Address, data: Bytes) -> Self {
25        Self::new_system_tx_with_caller(SYSTEM_ADDRESS, system_contract_address, data)
26    }
27
28    /// Creates a new system transaction with a custom caller address.
29    fn new_system_tx_with_caller(
30        caller: Address,
31        system_contract_address: Address,
32        data: Bytes,
33    ) -> Self;
34}
35
36impl SystemCallTx for TxEnv {
37    fn new_system_tx_with_caller(
38        caller: Address,
39        system_contract_address: Address,
40        data: Bytes,
41    ) -> Self {
42        TxEnv {
43            tx_type: TransactionType::Legacy as u8,
44            caller,
45            data,
46            kind: TxKind::Call(system_contract_address),
47            gas_limit: eip7825::TX_GAS_LIMIT_CAP,
48            ..Default::default()
49        }
50    }
51}
52
53/// API for executing the system calls. System calls dont deduct the caller or reward the
54/// beneficiary. They are used before and after block execution to insert or obtain blockchain state.
55///
56/// It act similar to `transact` function and sets default Tx with data and system contract as a target.
57pub trait SystemCallEvm: ExecuteEvm {
58    /// System call is a special transaction call that is used to call a system contract.
59    ///
60    /// Transaction fields are reset and set in [`SystemCallTx`] and data and target are set to
61    /// given values.
62    ///
63    /// Block values are taken into account and will determent how system call will be executed.
64    fn transact_system_call_with_caller(
65        &mut self,
66        caller: Address,
67        system_contract_address: Address,
68        data: Bytes,
69    ) -> Result<Self::ExecutionResult, Self::Error>;
70
71    /// Calls [`SystemCallEvm::transact_system_call_with_caller`] with [`SYSTEM_ADDRESS`] as a caller.
72    fn transact_system_call(
73        &mut self,
74        system_contract_address: Address,
75        data: Bytes,
76    ) -> Result<Self::ExecutionResult, Self::Error> {
77        self.transact_system_call_with_caller(SYSTEM_ADDRESS, system_contract_address, data)
78    }
79
80    /// Transact the system call and finalize.
81    ///
82    /// Internally calls combo of `transact_system_call` and `finalize` functions.
83    fn transact_system_call_finalize(
84        &mut self,
85        system_contract_address: Address,
86        data: Bytes,
87    ) -> Result<ExecResultAndState<Self::ExecutionResult, Self::State>, Self::Error> {
88        self.transact_system_call_with_caller_finalize(
89            SYSTEM_ADDRESS,
90            system_contract_address,
91            data,
92        )
93    }
94
95    /// Calls [`SystemCallEvm::transact_system_call_with_caller`] and `finalize` functions.
96    fn transact_system_call_with_caller_finalize(
97        &mut self,
98        caller: Address,
99        system_contract_address: Address,
100        data: Bytes,
101    ) -> Result<ExecResultAndState<Self::ExecutionResult, Self::State>, Self::Error> {
102        let result =
103            self.transact_system_call_with_caller(caller, system_contract_address, data)?;
104        let state = self.finalize();
105        Ok(ExecResultAndState::new(result, state))
106    }
107}
108
109/// Extension of the [`SystemCallEvm`] trait that adds a method that commits the state after execution.
110pub trait SystemCallCommitEvm: SystemCallEvm + ExecuteCommitEvm {
111    /// Transact the system call and commit to the state.
112    fn transact_system_call_commit(
113        &mut self,
114        system_contract_address: Address,
115        data: Bytes,
116    ) -> Result<Self::ExecutionResult, Self::Error> {
117        self.transact_system_call_with_caller_commit(SYSTEM_ADDRESS, system_contract_address, data)
118    }
119
120    /// Calls [`SystemCallCommitEvm::transact_system_call_commit`] with [`SYSTEM_ADDRESS`] as a caller.
121    fn transact_system_call_with_caller_commit(
122        &mut self,
123        caller: Address,
124        system_contract_address: Address,
125        data: Bytes,
126    ) -> Result<Self::ExecutionResult, Self::Error>;
127}
128
129impl<CTX, INSP, INST, PRECOMPILES> SystemCallEvm
130    for Evm<CTX, INSP, INST, PRECOMPILES, EthFrame<EthInterpreter>>
131where
132    CTX: ContextTr<Journal: JournalTr<State = EvmState>, Tx: SystemCallTx> + ContextSetters,
133    INST: InstructionProvider<Context = CTX, InterpreterTypes = EthInterpreter>,
134    PRECOMPILES: PrecompileProvider<CTX, Output = InterpreterResult>,
135{
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        // set tx fields.
143        self.set_tx(CTX::Tx::new_system_tx_with_caller(
144            caller,
145            system_contract_address,
146            data,
147        ));
148        // create handler
149        MainnetHandler::default().run_system_call(self)
150    }
151}
152
153impl<CTX, INSP, INST, PRECOMPILES> SystemCallCommitEvm
154    for Evm<CTX, INSP, INST, PRECOMPILES, EthFrame<EthInterpreter>>
155where
156    CTX: ContextTr<Journal: JournalTr<State = EvmState>, Db: DatabaseCommit, Tx: SystemCallTx>
157        + ContextSetters,
158    INST: InstructionProvider<Context = CTX, InterpreterTypes = EthInterpreter>,
159    PRECOMPILES: PrecompileProvider<CTX, Output = InterpreterResult>,
160{
161    fn transact_system_call_with_caller_commit(
162        &mut self,
163        caller: Address,
164        system_contract_address: Address,
165        data: Bytes,
166    ) -> Result<Self::ExecutionResult, Self::Error> {
167        self.transact_system_call_with_caller_finalize(caller, system_contract_address, data)
168            .map(|output| {
169                self.db_mut().commit(output.state);
170                output.result
171            })
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use crate::{MainBuilder, MainContext};
178
179    use super::*;
180    use context::{
181        result::{ExecutionResult, Output, SuccessReason},
182        Context,
183    };
184    use database::InMemoryDB;
185    use primitives::{b256, bytes, StorageKey, U256};
186    use state::{AccountInfo, Bytecode};
187
188    const HISTORY_STORAGE_ADDRESS: Address = address!("0x0000F90827F1C53a10cb7A02335B175320002935");
189    static HISTORY_STORAGE_CODE: Bytes = bytes!("0x3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500");
190
191    #[test]
192    fn test_system_call() {
193        let mut db = InMemoryDB::default();
194        db.insert_account_info(
195            HISTORY_STORAGE_ADDRESS,
196            AccountInfo::default().with_code(Bytecode::new_legacy(HISTORY_STORAGE_CODE.clone())),
197        );
198
199        let block_hash =
200            b256!("0x1111111111111111111111111111111111111111111111111111111111111111");
201
202        let mut my_evm = Context::mainnet()
203            .with_db(db)
204            // block with number 1 will set storage at slot 0.
205            .modify_block_chained(|b| b.number = U256::ONE)
206            .build_mainnet();
207        let output = my_evm
208            .transact_system_call_finalize(HISTORY_STORAGE_ADDRESS, block_hash.0.into())
209            .unwrap();
210
211        assert_eq!(
212            output.result,
213            ExecutionResult::Success {
214                reason: SuccessReason::Stop,
215                gas_used: 22143,
216                gas_refunded: 0,
217                logs: vec![],
218                output: Output::Call(Bytes::default())
219            }
220        );
221        // only system contract is updated and present
222        assert_eq!(output.state.len(), 1);
223        assert_eq!(
224            output.state[&HISTORY_STORAGE_ADDRESS]
225                .storage
226                .get(&StorageKey::from(0))
227                .map(|slot| slot.present_value)
228                .unwrap_or_default(),
229            U256::from_be_bytes(block_hash.0),
230            "State is not updated {:?}",
231            output.state
232        );
233    }
234}