revm_handler/
system_call.rs

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