use crate::{
instructions::InstructionProvider, EthFrame, ExecuteCommitEvm, ExecuteEvm, Handler,
MainnetHandler, PrecompileProvider,
};
use context::{
result::ResultAndState, ContextSetters, ContextTr, Evm, JournalTr, TransactionType, TxEnv,
};
use database_interface::DatabaseCommit;
use interpreter::{interpreter::EthInterpreter, InterpreterResult};
use primitives::{address, eip7825, Address, Bytes, TxKind};
use state::EvmState;
pub const SYSTEM_ADDRESS: Address = address!("0xfffffffffffffffffffffffffffffffffffffffe");
pub trait SystemCallTx: Sized {
fn new_system_tx(system_contract_address: Address, data: Bytes) -> Self {
Self::new_system_tx_with_caller(SYSTEM_ADDRESS, system_contract_address, data)
}
fn new_system_tx_with_caller(
caller: Address,
system_contract_address: Address,
data: Bytes,
) -> Self;
}
impl SystemCallTx for TxEnv {
fn new_system_tx_with_caller(
caller: Address,
system_contract_address: Address,
data: Bytes,
) -> Self {
TxEnv {
tx_type: TransactionType::Legacy as u8,
caller,
data,
kind: TxKind::Call(system_contract_address),
gas_limit: eip7825::TX_GAS_LIMIT_CAP,
..Default::default()
}
}
}
pub trait SystemCallEvm: ExecuteEvm {
fn transact_system_call_with_caller(
&mut self,
caller: Address,
system_contract_address: Address,
data: Bytes,
) -> Result<Self::ExecutionResult, Self::Error>;
fn transact_system_call(
&mut self,
system_contract_address: Address,
data: Bytes,
) -> Result<Self::ExecutionResult, Self::Error> {
self.transact_system_call_with_caller(SYSTEM_ADDRESS, system_contract_address, data)
}
fn transact_system_call_finalize(
&mut self,
system_contract_address: Address,
data: Bytes,
) -> Result<ResultAndState<Self::ExecutionResult, Self::State>, Self::Error> {
self.transact_system_call_with_caller_finalize(
SYSTEM_ADDRESS,
system_contract_address,
data,
)
}
fn transact_system_call_with_caller_finalize(
&mut self,
caller: Address,
system_contract_address: Address,
data: Bytes,
) -> Result<ResultAndState<Self::ExecutionResult, Self::State>, Self::Error> {
let result =
self.transact_system_call_with_caller(caller, system_contract_address, data)?;
let state = self.finalize();
Ok(ResultAndState::new(result, state))
}
}
pub trait SystemCallCommitEvm: SystemCallEvm + ExecuteCommitEvm {
fn transact_system_call_commit(
&mut self,
system_contract_address: Address,
data: Bytes,
) -> Result<Self::ExecutionResult, Self::Error> {
self.transact_system_call_with_caller_commit(SYSTEM_ADDRESS, system_contract_address, data)
}
fn transact_system_call_with_caller_commit(
&mut self,
caller: Address,
system_contract_address: Address,
data: Bytes,
) -> Result<Self::ExecutionResult, Self::Error>;
}
impl<CTX, INSP, INST, PRECOMPILES> SystemCallEvm for Evm<CTX, INSP, INST, PRECOMPILES>
where
CTX: ContextTr<Journal: JournalTr<State = EvmState>, Tx: SystemCallTx> + ContextSetters,
INST: InstructionProvider<Context = CTX, InterpreterTypes = EthInterpreter>,
PRECOMPILES: PrecompileProvider<CTX, Output = InterpreterResult>,
{
fn transact_system_call_with_caller(
&mut self,
caller: Address,
system_contract_address: Address,
data: Bytes,
) -> Result<Self::ExecutionResult, Self::Error> {
self.set_tx(CTX::Tx::new_system_tx_with_caller(
caller,
system_contract_address,
data,
));
let mut handler = MainnetHandler::<_, _, EthFrame<_, _, _>>::default();
handler.run_system_call(self)
}
}
impl<CTX, INSP, INST, PRECOMPILES> SystemCallCommitEvm for Evm<CTX, INSP, INST, PRECOMPILES>
where
CTX: ContextTr<Journal: JournalTr<State = EvmState>, Db: DatabaseCommit, Tx: SystemCallTx>
+ ContextSetters,
INST: InstructionProvider<Context = CTX, InterpreterTypes = EthInterpreter>,
PRECOMPILES: PrecompileProvider<CTX, Output = InterpreterResult>,
{
fn transact_system_call_with_caller_commit(
&mut self,
caller: Address,
system_contract_address: Address,
data: Bytes,
) -> Result<Self::ExecutionResult, Self::Error> {
self.transact_system_call_with_caller_finalize(caller, system_contract_address, data)
.map(|output| {
self.db_mut().commit(output.state);
output.result
})
}
}
#[cfg(test)]
mod tests {
use crate::{MainBuilder, MainContext};
use super::*;
use context::{
result::{ExecutionResult, Output, SuccessReason},
Context,
};
use database::InMemoryDB;
use primitives::{b256, bytes, StorageKey, U256};
use state::{AccountInfo, Bytecode};
const HISTORY_STORAGE_ADDRESS: Address = address!("0x0000F90827F1C53a10cb7A02335B175320002935");
static HISTORY_STORAGE_CODE: Bytes = bytes!("0x3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500");
#[test]
fn test_system_call() {
let mut db = InMemoryDB::default();
db.insert_account_info(
HISTORY_STORAGE_ADDRESS,
AccountInfo::default().with_code(Bytecode::new_legacy(HISTORY_STORAGE_CODE.clone())),
);
let block_hash =
b256!("0x1111111111111111111111111111111111111111111111111111111111111111");
let mut my_evm = Context::mainnet()
.with_db(db)
.modify_block_chained(|b| b.number = U256::ONE)
.build_mainnet();
let output = my_evm
.transact_system_call_finalize(HISTORY_STORAGE_ADDRESS, block_hash.0.into())
.unwrap();
assert_eq!(
output.result,
ExecutionResult::Success {
reason: SuccessReason::Stop,
gas_used: 22143,
gas_refunded: 0,
logs: vec![],
output: Output::Call(Bytes::default())
}
);
assert_eq!(output.state.len(), 1);
assert_eq!(
output.state[&HISTORY_STORAGE_ADDRESS]
.storage
.get(&StorageKey::from(0))
.map(|slot| slot.present_value)
.unwrap_or_default(),
U256::from_be_bytes(block_hash.0),
"State is not updated {:?}",
output.state
);
}
}