diff --git a/crates/evm/src/evm/db.rs b/crates/evm/src/evm/db.rs index d29c52eee..370c30cf2 100644 --- a/crates/evm/src/evm/db.rs +++ b/crates/evm/src/evm/db.rs @@ -1,3 +1,6 @@ +#[cfg(feature = "native")] +use std::collections::HashMap; + use reth_primitives::{Address, B256}; use revm::primitives::{AccountInfo as ReVmAccountInfo, Bytecode, U256}; use revm::Database; @@ -6,6 +9,23 @@ use sov_state::codec::BcsCodec; use super::{AccountInfo, DbAccount}; +// infallible +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum DBError {} + +impl std::fmt::Display for DBError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "EVM Infallible DBError") + } +} + +// impl stdError for dberror +impl std::error::Error for DBError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + None + } +} + pub(crate) struct EvmDb<'a, C: sov_modules_api::Context> { pub(crate) accounts: sov_modules_api::StateMap, pub(crate) code: sov_modules_api::StateMap, @@ -27,22 +47,32 @@ impl<'a, C: sov_modules_api::Context> EvmDb<'a, C> { working_set, } } -} -// infallible -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum DBError {} + #[cfg(feature = "native")] + pub(crate) fn override_block_hash(&mut self, number: u64, hash: B256) { + self.last_block_hashes + .set(&U256::from(number), &hash, self.working_set); + } -impl std::fmt::Display for DBError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "EVM Infallible DBError") + #[cfg(feature = "native")] + pub(crate) fn override_account(&mut self, account: &Address, info: AccountInfo) { + self.accounts.set(account, &info, self.working_set); } -} -// impl stdError for dberror -impl std::error::Error for DBError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - None + #[cfg(feature = "native")] + pub(crate) fn override_set_account_storage( + &mut self, + account: &Address, + state_diff: HashMap, + ) { + let db_account = DbAccount::new(*account); + for (slot, value) in state_diff { + db_account.storage.set( + &U256::from_be_bytes(slot.0), + &U256::from_be_bytes(value.0), + self.working_set, + ); + } } } diff --git a/crates/evm/src/query.rs b/crates/evm/src/query.rs index 038c1b88a..767f17d77 100644 --- a/crates/evm/src/query.rs +++ b/crates/evm/src/query.rs @@ -13,9 +13,10 @@ use reth_primitives::{ }; use reth_provider::ProviderError; use reth_rpc_eth_types::error::{EthApiError, EthResult, RevertError, RpcInvalidTransactionError}; +use reth_rpc_types::state::StateOverride; use reth_rpc_types::trace::geth::{GethDebugTracingOptions, GethTrace}; use reth_rpc_types::{ - AnyReceiptEnvelope, AnyTransactionReceipt, Log, OtherFields, ReceiptWithBloom, + AnyReceiptEnvelope, AnyTransactionReceipt, BlockOverrides, Log, OtherFields, ReceiptWithBloom, TransactionReceipt, }; use reth_rpc_types_compat::block::from_primitive_with_hash; @@ -494,8 +495,8 @@ impl Evm { &self, request: reth_rpc_types::TransactionRequest, block_id: Option, - _state_overrides: Option, - _block_overrides: Option>, + state_overrides: Option, + block_overrides: Option, working_set: &mut WorkingSet, ) -> RpcResult { let block_number = match block_id { @@ -509,7 +510,7 @@ impl Evm { None => BlockNumberOrTag::Latest, }; - let (block_env, mut cfg_env) = { + let (mut block_env, mut cfg_env) = { let block_env = match block_number { BlockNumberOrTag::Pending => get_pending_block_env(self, working_set), _ => { @@ -519,6 +520,7 @@ impl Evm { BlockEnv::from(&block) } }; + // Set evm state to block if needed match block_number { BlockNumberOrTag::Pending | BlockNumberOrTag::Latest => {} @@ -536,6 +538,14 @@ impl Evm { let mut evm_db = self.get_db(working_set); + if let Some(mut block_overrides) = block_overrides { + apply_block_overrides(&mut block_env, &mut block_overrides, &mut evm_db); + } + + if let Some(state_overrides) = state_overrides { + apply_state_overrides(state_overrides, &mut evm_db)?; + } + let cap_to_balance = evm_db .basic(request.from.unwrap_or_default()) .map_err(EthApiError::from)? diff --git a/crates/evm/src/rpc_helpers/mod.rs b/crates/evm/src/rpc_helpers/mod.rs index bf905b40b..2019e5762 100644 --- a/crates/evm/src/rpc_helpers/mod.rs +++ b/crates/evm/src/rpc_helpers/mod.rs @@ -1,9 +1,120 @@ +use std::collections::HashMap; + +pub use filter::*; +pub use log_utils::*; +pub use responses::*; +use reth_primitives::{keccak256, Address}; +use reth_rpc_eth_types::{EthApiError, EthResult}; +use reth_rpc_types::state::AccountOverride; +use reth_rpc_types::BlockOverrides; +use revm::Database; + mod filter; mod log_utils; mod responses; mod tracing_utils; -pub use filter::*; -pub use log_utils::*; -pub use responses::*; pub(crate) use tracing_utils::*; + +use crate::db::EvmDb; +#[cfg(feature = "native")] +use crate::primitive_types::BlockEnv; + +#[cfg(feature = "native")] +/// Applies all instances [`AccountOverride`] to the [`EvmDb`]. +pub(crate) fn apply_state_overrides( + state_overrides: HashMap, + db: &mut EvmDb, +) -> EthResult<()> { + for (address, account_overrides) in state_overrides { + apply_account_override(address, account_overrides, db)?; + } + + Ok(()) +} + +#[cfg(feature = "native")] +/// Applies a single [`AccountOverride`] to the [`EvmDb`]. +pub(crate) fn apply_account_override( + account: Address, + account_override: AccountOverride, + db: &mut EvmDb, +) -> EthResult<()> { + // we need to fetch the account via the `DatabaseRef` to not update the state of the account, + // which is modified via `Database::basic_ref` + let mut account_info = db.basic(account)?.unwrap_or_default(); + + if let Some(nonce) = account_override.nonce { + account_info.nonce = nonce; + } + if let Some(code) = account_override.code { + account_info.code_hash = keccak256(code); + } + if let Some(balance) = account_override.balance { + account_info.balance = balance; + } + + db.override_account(&account, account_info.into()); + + // We ensure that not both state and state_diff are set. + // If state is set, we must mark the account as "NewlyCreated", so that the old storage + // isn't read from + match (account_override.state, account_override.state_diff) { + (Some(_), Some(_)) => return Err(EthApiError::BothStateAndStateDiffInOverride(account)), + (None, None) => { + // nothing to do + } + (Some(new_account_state), None) => { + db.override_set_account_storage(&account, new_account_state); + } + (None, Some(account_state_diff)) => { + db.override_set_account_storage(&account, account_state_diff); + } + }; + + Ok(()) +} + +#[cfg(feature = "native")] +/// Applies all instances of [`BlockOverride`] to the [`EvmDb`]. +pub(crate) fn apply_block_overrides( + block_env: &mut BlockEnv, + block_overrides: &mut BlockOverrides, + db: &mut EvmDb, +) { + if let Some(block_hashes) = block_overrides.block_hash.take() { + // override block hashes + for (num, hash) in block_hashes { + db.override_block_hash(num, hash); + } + } + + let BlockOverrides { + number, + time, + gas_limit, + coinbase, + random, + base_fee, + block_hash: _, + difficulty: _, + } = *block_overrides; + if let Some(number) = number { + block_env.number = number.saturating_to(); + } + if let Some(time) = time { + block_env.timestamp = time; + } + if let Some(gas_limit) = gas_limit { + block_env.gas_limit = gas_limit; + } + if let Some(coinbase) = coinbase { + block_env.coinbase = coinbase; + } + if let Some(random) = random { + block_env.prevrandao = random; + } + if let Some(base_fee) = base_fee { + block_env.basefee = base_fee.saturating_to(); + } +} diff --git a/crates/evm/src/tests/call_tests.rs b/crates/evm/src/tests/call_tests.rs index 0e87bfabd..a87680d3e 100644 --- a/crates/evm/src/tests/call_tests.rs +++ b/crates/evm/src/tests/call_tests.rs @@ -1,9 +1,11 @@ +use std::collections::BTreeMap; use std::str::FromStr; use alloy_eips::BlockId; use reth_primitives::constants::ETHEREUM_BLOCK_GAS_LIMIT; use reth_primitives::{address, b256, Address, BlockNumberOrTag, Bytes, Log, LogData, TxKind, U64}; use reth_rpc_types::request::{TransactionInput, TransactionRequest}; +use reth_rpc_types::BlockOverrides; use revm::primitives::{hex, SpecId, KECCAK_EMPTY, U256}; use sov_modules_api::default_context::DefaultContext; use sov_modules_api::hooks::HookSoftConfirmationInfo; @@ -1508,3 +1510,135 @@ fn test_l1_fee_halt() { U256::from(447 + 96 + 2 * L1_FEE_OVERHEAD as u64) ); } + +#[test] +fn test_call_with_block_overrides() { + let (config, dev_signer, contract_addr) = + get_evm_config(U256::from_str("100000000000000000000").unwrap(), None); + + let (mut evm, mut working_set) = get_evm(&config); + let l1_fee_rate = 0; + let mut l2_height = 2; + + let soft_confirmation_info = HookSoftConfirmationInfo { + l2_height, + da_slot_hash: [5u8; 32], + da_slot_height: 1, + da_slot_txs_commitment: [42u8; 32], + pre_state_root: [10u8; 32].to_vec(), + current_spec: SovSpecId::Genesis, + pub_key: vec![], + deposit_data: vec![], + l1_fee_rate, + timestamp: 0, + }; + + // Deploy block hashes contract + let sender_address = generate_address::("sender"); + evm.begin_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + { + let sequencer_address = generate_address::("sequencer"); + let context = C::new( + sender_address, + sequencer_address, + l2_height, + SovSpecId::Genesis, + l1_fee_rate, + ); + + let deploy_message = create_contract_message(&dev_signer, 0, BlockHashContract::default()); + + evm.call( + CallMessage { + txs: vec![deploy_message], + }, + &context, + &mut working_set, + ) + .unwrap(); + } + evm.end_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + evm.finalize_hook(&[99u8; 32].into(), &mut working_set.accessory_state()); + + // Create empty EVM blocks + for _i in 0..10 { + let l1_fee_rate = 0; + let soft_confirmation_info = HookSoftConfirmationInfo { + l2_height, + da_slot_hash: [5u8; 32], + da_slot_height: 1, + da_slot_txs_commitment: [42u8; 32], + pre_state_root: [99u8; 32].to_vec(), + current_spec: SovSpecId::Genesis, + pub_key: vec![], + deposit_data: vec![], + l1_fee_rate, + timestamp: 0, + }; + evm.begin_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + evm.end_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + evm.finalize_hook(&[99u8; 32].into(), &mut working_set.accessory_state()); + + l2_height += 1; + } + + // Construct block override with custom hashes + let mut block_hashes = BTreeMap::new(); + block_hashes.insert(1, [1; 32].into()); + block_hashes.insert(2, [2; 32].into()); + + // Call with block overrides and check that the hash for 1st block is what we want + let call_result = evm + .get_call( + TransactionRequest { + from: None, + to: Some(TxKind::Call(contract_addr)), + input: TransactionInput::new(BlockHashContract::default().get_block_hash(1).into()), + ..Default::default() + }, + None, + None, + Some(BlockOverrides { + number: None, + difficulty: None, + time: None, + gas_limit: None, + coinbase: None, + random: None, + base_fee: None, + block_hash: Some(block_hashes.clone()), + }), + &mut working_set, + ) + .unwrap(); + + let expected_hash = Bytes::from_iter([1; 32]); + assert_eq!(call_result, expected_hash); + + // Call with block overrides and check that the hash for 2nd block is what we want + let call_result = evm + .get_call( + TransactionRequest { + from: None, + to: Some(TxKind::Call(contract_addr)), + input: TransactionInput::new(BlockHashContract::default().get_block_hash(2).into()), + ..Default::default() + }, + None, + None, + Some(BlockOverrides { + number: None, + difficulty: None, + time: None, + gas_limit: None, + coinbase: None, + random: None, + base_fee: None, + block_hash: Some(block_hashes), + }), + &mut working_set, + ) + .unwrap(); + let expected_hash = Bytes::from_iter([2; 32]); + assert_eq!(call_result, expected_hash); +} diff --git a/crates/evm/src/tests/queries/basic_queries.rs b/crates/evm/src/tests/queries/basic_queries.rs index f8c7487b5..3ee5886c9 100644 --- a/crates/evm/src/tests/queries/basic_queries.rs +++ b/crates/evm/src/tests/queries/basic_queries.rs @@ -13,7 +13,7 @@ use crate::tests::queries::init_evm; #[test] fn get_block_by_hash_test() { // make a block - let (evm, mut working_set, _, _) = init_evm(); + let (evm, mut working_set, _, _, _) = init_evm(); let result = evm.get_block_by_hash([5u8; 32].into(), Some(false), &mut working_set); @@ -34,7 +34,7 @@ fn get_block_by_hash_test() { #[test] fn get_block_by_number_test() { // make a block - let (evm, mut working_set, _, _) = init_evm(); + let (evm, mut working_set, _, _, _) = init_evm(); let result = evm.get_block_by_number( Some(BlockNumberOrTag::Number(1000)), @@ -60,7 +60,7 @@ fn get_block_by_number_test() { #[test] fn get_block_receipts_test() { // make a block - let (evm, mut working_set, _, _) = init_evm(); + let (evm, mut working_set, _, _, _) = init_evm(); let result = evm.get_block_receipts( BlockId::Number(BlockNumberOrTag::Number(1000)), @@ -91,7 +91,7 @@ fn get_block_receipts_test() { #[test] fn get_transaction_by_block_hash_and_index_test() { - let (evm, mut working_set, _, _) = init_evm(); + let (evm, mut working_set, _, _, _) = init_evm(); let result = evm.get_transaction_by_block_hash_and_index( [0u8; 32].into(), @@ -136,7 +136,7 @@ fn get_transaction_by_block_hash_and_index_test() { #[test] fn get_transaction_by_block_number_and_index_test() { - let (evm, mut working_set, _, _) = init_evm(); + let (evm, mut working_set, _, _, _) = init_evm(); let result = evm.get_transaction_by_block_number_and_index( BlockNumberOrTag::Number(100), @@ -186,7 +186,7 @@ fn get_transaction_by_block_number_and_index_test() { #[test] fn get_block_transaction_count_by_hash_test() { - let (evm, mut working_set, _, _) = init_evm(); + let (evm, mut working_set, _, _, _) = init_evm(); let result = evm.eth_get_block_transaction_count_by_hash(B256::from([0u8; 32]), &mut working_set); @@ -231,7 +231,7 @@ fn get_block_transaction_count_by_hash_test() { #[test] fn get_block_transaction_count_by_number_test() { - let (evm, mut working_set, _, _) = init_evm(); + let (evm, mut working_set, _, _, _) = init_evm(); let result = evm .eth_get_block_transaction_count_by_number(BlockNumberOrTag::Number(5), &mut working_set); @@ -253,7 +253,7 @@ fn get_block_transaction_count_by_number_test() { #[test] fn call_test() { - let (evm, mut working_set, signer, _) = init_evm(); + let (evm, mut working_set, _, signer, _) = init_evm(); let fail_result = evm.get_call( TransactionRequest { diff --git a/crates/evm/src/tests/queries/estimate_gas_tests.rs b/crates/evm/src/tests/queries/estimate_gas_tests.rs index e461d1bdd..157053fff 100644 --- a/crates/evm/src/tests/queries/estimate_gas_tests.rs +++ b/crates/evm/src/tests/queries/estimate_gas_tests.rs @@ -338,8 +338,8 @@ fn test_access_list() { } #[test] -fn test_estimate_gas_with_varied_inputs() { - let (evm, mut working_set, signer, _) = init_evm(); +fn estimate_gas_with_varied_inputs_test() { + let (evm, mut working_set, _, signer, _) = init_evm(); let simple_call_data = 0; let simple_result = diff --git a/crates/evm/src/tests/queries/evm_call_tests.rs b/crates/evm/src/tests/queries/evm_call_tests.rs index 167aa92a4..8acd761d9 100644 --- a/crates/evm/src/tests/queries/evm_call_tests.rs +++ b/crates/evm/src/tests/queries/evm_call_tests.rs @@ -1,9 +1,11 @@ +use std::collections::HashMap; use std::str::FromStr; use jsonrpsee::core::RpcResult; use reth_primitives::{address, Address, BlockNumberOrTag, Bytes, TxKind}; use reth_rpc_eth_types::RpcInvalidTransactionError; use reth_rpc_types::request::{TransactionInput, TransactionRequest}; +use reth_rpc_types::state::AccountOverride; use reth_rpc_types::BlockId; use revm::primitives::U256; use sov_modules_api::hooks::HookSoftConfirmationInfo; @@ -18,7 +20,7 @@ use crate::Evm; #[test] fn call_contract_without_value() { - let (evm, mut working_set, signer, _) = init_evm(); + let (evm, mut working_set, _, signer, _) = init_evm(); let contract = SimpleStorageContract::default(); let contract_address = Address::from_str("0xeeb03d20dae810f52111b853b31c8be6f30f4cd3").unwrap(); @@ -66,7 +68,7 @@ fn call_contract_without_value() { #[test] fn test_state_change() { - let (mut evm, mut working_set, signer, l2_height) = init_evm(); + let (mut evm, mut working_set, _, signer, l2_height) = init_evm(); let balance_1 = evm.get_balance(signer.address(), None, &mut working_set); @@ -112,7 +114,7 @@ fn test_state_change() { #[test] fn call_contract_with_value_transfer() { - let (evm, mut working_set, signer, _) = init_evm(); + let (evm, mut working_set, _, signer, _) = init_evm(); let contract = SimpleStorageContract::default(); let contract_address = Address::from_str("0xeeb03d20dae810f52111b853b31c8be6f30f4cd3").unwrap(); @@ -138,7 +140,7 @@ fn call_contract_with_value_transfer() { #[test] fn call_contract_with_invalid_nonce() { - let (evm, mut working_set, signer, _) = init_evm(); + let (evm, mut working_set, _, signer, _) = init_evm(); let contract = SimpleStorageContract::default(); let contract_address = Address::from_str("0xeeb03d20dae810f52111b853b31c8be6f30f4cd3").unwrap(); @@ -188,7 +190,7 @@ fn call_contract_with_invalid_nonce() { #[test] fn call_to_nonexistent_contract() { - let (evm, mut working_set, signer, _) = init_evm(); + let (evm, mut working_set, _, signer, _) = init_evm(); let nonexistent_contract_address = Address::from_str("0x000000000000000000000000000000000000dead").unwrap(); @@ -216,7 +218,7 @@ fn call_to_nonexistent_contract() { #[test] fn call_with_high_gas_price() { - let (evm, mut working_set, signer, _) = init_evm(); + let (evm, mut working_set, _, signer, _) = init_evm(); let contract = SimpleStorageContract::default(); let contract_address = Address::from_str("0xeeb03d20dae810f52111b853b31c8be6f30f4cd3").unwrap(); @@ -246,7 +248,7 @@ fn call_with_high_gas_price() { #[test] fn test_eip1559_fields_call() { - let (evm, mut working_set, signer, _) = init_evm(); + let (evm, mut working_set, _, signer, _) = init_evm(); let default_result = eth_call_eip1559( &evm, @@ -506,3 +508,91 @@ fn gas_price_call_test() { assert!(result_high_fees.is_ok()); working_set.unset_archival_version(); } + +#[test] +fn test_call_with_state_overrides() { + let (evm, mut working_set, prover_storage, signer, _) = init_evm(); + + let contract = SimpleStorageContract::default(); + let contract_address = Address::from_str("0xeeb03d20dae810f52111b853b31c8be6f30f4cd3").unwrap(); + + // Get value of contract before state override + let call_result_without_state_override = evm + .get_call( + TransactionRequest { + from: Some(signer.address()), + to: Some(TxKind::Call(contract_address)), + input: TransactionInput::new(contract.get_call_data().into()), + ..Default::default() + }, + None, + None, + None, + &mut working_set, + ) + .unwrap(); + + assert_eq!( + call_result_without_state_override, + U256::from(478).to_be_bytes_vec() + ); + + // Override the state and check returned value + let mut state = HashMap::new(); + state.insert(U256::from(0).into(), U256::from(15).into()); + + let mut state_override = HashMap::new(); + state_override.insert( + contract_address, + AccountOverride { + balance: None, + nonce: None, + code: None, + state: Some(state), + state_diff: None, + }, + ); + let call_result_with_state_override = evm + .get_call( + TransactionRequest { + from: Some(signer.address()), + to: Some(TxKind::Call(contract_address)), + input: TransactionInput::new(contract.get_call_data().into()), + ..Default::default() + }, + None, + Some(state_override), + None, + &mut working_set, + ) + .unwrap(); + + assert_eq!( + call_result_with_state_override, + U256::from(15).to_be_bytes_vec() + ); + + // Start with a fresh working set, because the previous one was for a separate RPC call. + let mut working_set = WorkingSet::new(prover_storage); + + // Get value of contract AFTER state override, this MUST be the original value. + let call_result_without_state_override = evm + .get_call( + TransactionRequest { + from: Some(signer.address()), + to: Some(TxKind::Call(contract_address)), + input: TransactionInput::new(contract.get_call_data().into()), + ..Default::default() + }, + None, + None, + None, + &mut working_set, + ) + .unwrap(); + + assert_eq!( + call_result_without_state_override, + U256::from(478).to_be_bytes_vec() + ); +} diff --git a/crates/evm/src/tests/queries/log_tests.rs b/crates/evm/src/tests/queries/log_tests.rs index 0d7e2f696..8a80963aa 100644 --- a/crates/evm/src/tests/queries/log_tests.rs +++ b/crates/evm/src/tests/queries/log_tests.rs @@ -22,7 +22,7 @@ type C = DefaultContext; #[test] fn logs_for_filter_test() { - let (evm, mut working_set, _, _) = init_evm(); + let (evm, mut working_set, _, _, _) = init_evm(); let result = evm.eth_get_logs( Filter { diff --git a/crates/evm/src/tests/queries/mod.rs b/crates/evm/src/tests/queries/mod.rs index 9afb1bd33..f6ecb8955 100644 --- a/crates/evm/src/tests/queries/mod.rs +++ b/crates/evm/src/tests/queries/mod.rs @@ -11,7 +11,9 @@ use sov_modules_api::default_context::DefaultContext; use sov_modules_api::hooks::HookSoftConfirmationInfo; use sov_modules_api::utils::generate_address; use sov_modules_api::{Context, Module, WorkingSet}; +use sov_prover_storage_manager::SnapshotManager; use sov_rollup_interface::spec::SpecId as SovSpecId; +use sov_state::{DefaultStorageSpec, ProverStorage}; use crate::call::CallMessage; use crate::smart_contracts::{ @@ -30,7 +32,13 @@ type C = DefaultContext; /// Block 1 has 3 transactions /// Block 2 has 4 transactions /// Block 3 has 2 transactions -fn init_evm() -> (Evm, WorkingSet, TestSigner, u64) { +fn init_evm() -> ( + Evm, + WorkingSet, + ProverStorage, + TestSigner, + u64, +) { let dev_signer: TestSigner = TestSigner::new_random(); let mut config = EvmConfig { @@ -206,7 +214,7 @@ fn init_evm() -> (Evm, WorkingSet, TestSigner, u64) { let working_set: WorkingSet = WorkingSet::new(prover_storage.clone()); - (evm, working_set, dev_signer, l2_height) + (evm, working_set, prover_storage, dev_signer, l2_height) } pub fn init_evm_single_block() -> (Evm, WorkingSet, TestSigner) {