revm_handler/
system_call.rs

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