diff --git a/crates/rpc/rpc-api/src/eth.rs b/crates/rpc/rpc-api/src/eth.rs
index 5b6553e731..b16cf6bf0f 100644
--- a/crates/rpc/rpc-api/src/eth.rs
+++ b/crates/rpc/rpc-api/src/eth.rs
@@ -4,8 +4,8 @@ use reth_primitives::{
H256, H64, U256, U64,
};
use reth_rpc_types::{
- CallRequest, EIP1186AccountProofResponse, FeeHistory, Index, RichBlock, SyncStatus,
- Transaction, TransactionReceipt, TransactionRequest, Work,
+ state::StateOverride, CallRequest, EIP1186AccountProofResponse, FeeHistory, Index, RichBlock,
+ SyncStatus, Transaction, TransactionReceipt, TransactionRequest, Work,
};
/// Eth rpc interface:
@@ -135,7 +135,12 @@ pub trait EthApi {
/// Executes a new message call immediately without creating a transaction on the block chain.
#[method(name = "eth_call")]
- async fn call(&self, request: CallRequest, block_number: Option) -> Result;
+ async fn call(
+ &self,
+ request: CallRequest,
+ block_number: Option,
+ state_overrides: Option,
+ ) -> Result;
/// Generates an access list for a transaction.
///
diff --git a/crates/rpc/rpc-builder/tests/it/http.rs b/crates/rpc/rpc-builder/tests/it/http.rs
index f6eec08ba3..c709f9d079 100644
--- a/crates/rpc/rpc-builder/tests/it/http.rs
+++ b/crates/rpc/rpc-builder/tests/it/http.rs
@@ -98,7 +98,7 @@ where
));
assert!(is_unimplemented(EthApiClient::transaction_receipt(client, hash).await.err().unwrap()));
assert!(is_unimplemented(
- EthApiClient::call(client, call_request.clone(), None).await.err().unwrap()
+ EthApiClient::call(client, call_request.clone(), None, None).await.err().unwrap()
));
assert!(is_unimplemented(
EthApiClient::create_access_list(client, call_request.clone(), None).await.err().unwrap()
diff --git a/crates/rpc/rpc-types/src/eth/state.rs b/crates/rpc/rpc-types/src/eth/state.rs
index c2d1edadce..9fbc0d8a13 100644
--- a/crates/rpc/rpc-types/src/eth/state.rs
+++ b/crates/rpc/rpc-types/src/eth/state.rs
@@ -9,7 +9,7 @@ pub type StateOverride = HashMap;
/// Custom account override used in call
#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase", deny_unknown_fields)]
+#[serde(default, rename_all = "camelCase", deny_unknown_fields)]
#[allow(missing_docs)]
pub struct AccountOverride {
pub nonce: Option,
diff --git a/crates/rpc/rpc/src/eth/api/call.rs b/crates/rpc/rpc/src/eth/api/call.rs
index 81414db12a..7c8ffc7069 100644
--- a/crates/rpc/rpc/src/eth/api/call.rs
+++ b/crates/rpc/rpc/src/eth/api/call.rs
@@ -11,11 +11,15 @@ use reth_primitives::{
};
use reth_provider::{BlockProvider, EvmEnvProvider, StateProvider, StateProviderFactory};
use reth_revm::database::{State, SubState};
-use reth_rpc_types::CallRequest;
+use reth_rpc_types::{
+ state::{AccountOverride, StateOverride},
+ CallRequest,
+};
use revm::{
+ db::{CacheDB, DatabaseRef},
primitives::{
- ruint::Uint, BlockEnv, CfgEnv, Env, ExecutionResult, Halt, ResultAndState, TransactTo,
- TxEnv,
+ ruint::Uint, BlockEnv, Bytecode, CfgEnv, Env, ExecutionResult, Halt, ResultAndState,
+ TransactTo, TxEnv,
},
Database,
};
@@ -55,10 +59,11 @@ where
&self,
request: CallRequest,
at: BlockId,
+ state_overrides: Option,
) -> EthResult<(ResultAndState, Env)> {
let (cfg, block_env, at) = self.evm_env_at(at).await?;
let state = self.state_at_block_id(at)?.ok_or_else(|| EthApiError::UnknownBlockNumber)?;
- self.call_with(cfg, block_env, request, state)
+ self.call_with(cfg, block_env, request, state, state_overrides)
}
/// Executes the call request using the given environment against the state provider
@@ -70,6 +75,7 @@ where
block: BlockEnv,
mut request: CallRequest,
state: S,
+ state_overrides: Option,
) -> EthResult<(ResultAndState, Env)>
where
S: StateProvider,
@@ -80,6 +86,12 @@ where
let mut env = build_call_evm_env(cfg, block, request)?;
let mut db = SubState::new(State::new(state));
+
+ // apply state overrides
+ if let Some(state_overrides) = state_overrides {
+ apply_state_overrides(state_overrides, &mut db)?;
+ }
+
transact(&mut db, env)
}
@@ -391,3 +403,72 @@ impl CallFees {
}
}
}
+
+/// Applies the given state overrides (a set of [AccountOverride]) to the [CacheDB].
+fn apply_state_overrides(overrides: StateOverride, db: &mut CacheDB) -> EthResult<()>
+where
+ DB: DatabaseRef,
+ EthApiError: From<::Error>,
+{
+ for (account, account_overrides) in overrides {
+ apply_account_override(account, account_overrides, db)?;
+ }
+ Ok(())
+}
+
+/// Applies a single [AccountOverride] to the [CacheDB].
+fn apply_account_override(
+ account: Address,
+ account_override: AccountOverride,
+ db: &mut CacheDB,
+) -> EthResult<()>
+where
+ DB: DatabaseRef,
+ EthApiError: From<::Error>,
+{
+ 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 = Some(Bytecode::new_raw(code.0));
+ }
+ if let Some(balance) = account_override.balance {
+ account_info.balance = balance;
+ }
+
+ db.insert_account_info(account, account_info);
+
+ // 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.replace_account_storage(
+ account,
+ new_account_state
+ .into_iter()
+ .map(|(slot, value)| {
+ (U256::from_be_bytes(slot.0), U256::from_be_bytes(value.0))
+ })
+ .collect(),
+ )?;
+ }
+ (None, Some(account_state_diff)) => {
+ for (slot, value) in account_state_diff {
+ db.insert_account_storage(
+ account,
+ U256::from_be_bytes(slot.0),
+ U256::from_be_bytes(value.0),
+ )?;
+ }
+ }
+ };
+
+ Ok(())
+}
diff --git a/crates/rpc/rpc/src/eth/api/server.rs b/crates/rpc/rpc/src/eth/api/server.rs
index ea8afb3556..471ff2eb16 100644
--- a/crates/rpc/rpc/src/eth/api/server.rs
+++ b/crates/rpc/rpc/src/eth/api/server.rs
@@ -14,8 +14,9 @@ use reth_primitives::{
use reth_provider::{BlockProvider, EvmEnvProvider, HeaderProvider, StateProviderFactory};
use reth_rpc_api::EthApiServer;
use reth_rpc_types::{
- CallRequest, EIP1186AccountProofResponse, FeeHistory, FeeHistoryCacheItem, Index, RichBlock,
- SyncStatus, TransactionReceipt, TransactionRequest, Work,
+ state::StateOverride, CallRequest, EIP1186AccountProofResponse, FeeHistory,
+ FeeHistoryCacheItem, Index, RichBlock, SyncStatus, TransactionReceipt, TransactionRequest,
+ Work,
};
use reth_transaction_pool::TransactionPool;
use serde_json::Value;
@@ -177,7 +178,12 @@ where
}
/// Handler for: `eth_call`
- async fn call(&self, _request: CallRequest, _block_number: Option) -> Result {
+ async fn call(
+ &self,
+ _request: CallRequest,
+ _block_number: Option,
+ _state_overrides: Option,
+ ) -> Result {
Err(internal_rpc_err("unimplemented"))
}
diff --git a/crates/rpc/rpc/src/eth/error.rs b/crates/rpc/rpc/src/eth/error.rs
index ee1e42ab0e..8a080c7b52 100644
--- a/crates/rpc/rpc/src/eth/error.rs
+++ b/crates/rpc/rpc/src/eth/error.rs
@@ -2,7 +2,7 @@
use crate::result::{internal_rpc_err, rpc_err};
use jsonrpsee::{core::Error as RpcError, types::error::INVALID_PARAMS_CODE};
-use reth_primitives::{constants::SELECTOR_LEN, U128, U256};
+use reth_primitives::{constants::SELECTOR_LEN, Address, U128, U256};
use reth_rpc_types::BlockError;
use reth_transaction_pool::error::PoolError;
use revm::primitives::{EVMError, Halt};
@@ -67,6 +67,10 @@ pub(crate) enum EthApiError {
/// Thrown when constructing an RPC block from a primitive block data failed.
#[error(transparent)]
InvalidBlockData(#[from] BlockError),
+ /// Thrown when a [AccountOverride](reth_rpc_types::state::AccountOverride) contains
+ /// conflicting `state` and `stateDiff` fields
+ #[error("account {0:?} has both 'state' and 'stateDiff'")]
+ BothStateAndStateDiffInOverride(Address),
/// Other internal error
#[error(transparent)]
Internal(#[from] reth_interfaces::Error),