feat(rpc): implement movePrecompileToAddress for eth_simulateV1 (#21414)

Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Matthias Seitz
2026-01-26 13:40:12 +01:00
committed by GitHub
parent 963bfeeeed
commit d4f28b02ff
2 changed files with 100 additions and 6 deletions

View File

@@ -20,8 +20,8 @@ use alloy_rpc_types_eth::{
use futures::Future;
use reth_errors::{ProviderError, RethError};
use reth_evm::{
env::BlockEnvironment, ConfigureEvm, Evm, EvmEnvFor, HaltReasonFor, InspectorFor,
TransactionEnv, TxEnvFor,
env::BlockEnvironment, execute::BlockBuilder, ConfigureEvm, Evm, EvmEnvFor, HaltReasonFor,
InspectorFor, TransactionEnv, TxEnvFor,
};
use reth_node_api::BlockBody;
use reth_primitives_traits::Recovered;
@@ -135,8 +135,8 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA
evm_env.block_env.inner_mut(),
);
}
if let Some(state_overrides) = state_overrides {
apply_state_overrides(state_overrides, &mut db)
if let Some(ref state_overrides) = state_overrides {
apply_state_overrides(state_overrides.clone(), &mut db)
.map_err(Self::Error::from_eth_err)?;
}
@@ -192,7 +192,16 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA
let evm = this
.evm_config()
.evm_with_env_and_inspector(&mut db, evm_env, inspector);
let builder = this.evm_config().create_block_builder(evm, &parent, ctx);
let mut builder = this.evm_config().create_block_builder(evm, &parent, ctx);
if let Some(ref state_overrides) = state_overrides {
simulate::apply_precompile_overrides(
state_overrides,
builder.evm_mut().precompiles_mut(),
)
.map_err(|e| Self::Error::from_eth_err(EthApiError::other(e)))?;
}
simulate::execute_transactions(
builder,
calls,
@@ -203,7 +212,16 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA
.map_err(map_err)?
} else {
let evm = this.evm_config().evm_with_env(&mut db, evm_env);
let builder = this.evm_config().create_block_builder(evm, &parent, ctx);
let mut builder = this.evm_config().create_block_builder(evm, &parent, ctx);
if let Some(ref state_overrides) = state_overrides {
simulate::apply_precompile_overrides(
state_overrides,
builder.evm_mut().precompiles_mut(),
)
.map_err(|e| Self::Error::from_eth_err(EthApiError::other(e)))?;
}
simulate::execute_transactions(
builder,
calls,

View File

@@ -6,9 +6,11 @@ use crate::{
};
use alloy_consensus::{transaction::TxHashRef, BlockHeader, Transaction as _};
use alloy_eips::eip2718::WithEncoded;
use alloy_evm::precompiles::PrecompilesMap;
use alloy_network::TransactionBuilder;
use alloy_rpc_types_eth::{
simulate::{SimCallResult, SimulateError, SimulatedBlock},
state::StateOverride,
BlockTransactionsKind,
};
use jsonrpsee_types::ErrorObject;
@@ -79,6 +81,9 @@ pub enum EthSimulateError {
/// Multiple `MovePrecompileToAddress` referencing the same address.
#[error("Multiple MovePrecompileToAddress referencing the same address")]
PrecompileDuplicateAddress,
/// Attempted to move a non-precompile address.
#[error("account {0} is not a precompile")]
NotAPrecompile(Address),
}
impl EthSimulateError {
@@ -98,6 +103,7 @@ impl EthSimulateError {
Self::SenderNotEOA => -38024,
Self::MaxInitCodeSizeExceeded => -38025,
Self::GasLimitReached => -38026,
Self::NotAPrecompile(_) => -32000,
}
}
}
@@ -108,6 +114,76 @@ impl ToRpcError for EthSimulateError {
}
}
/// Applies precompile move overrides from state overrides to the EVM's precompiles map.
///
/// This function processes `movePrecompileToAddress` entries from the state overrides and
/// moves precompiles from their original addresses to new addresses. The original address
/// is cleared (precompile removed) and the precompile is installed at the destination address.
///
/// # Validation
///
/// - The source address must be a precompile (exists in the precompiles map)
/// - Moving multiple precompiles to the same destination is allowed
/// - Self-references (moving to the same address) are not explicitly forbidden here since that
/// would be a no-op
///
/// # Arguments
///
/// * `state_overrides` - The state overrides containing potential `movePrecompileToAddress` entries
/// * `precompiles` - Mutable reference to the EVM's precompiles map
///
/// # Returns
///
/// Returns `Ok(())` on success, or an `EthSimulateError::NotAPrecompile` if a source address
/// is not a precompile.
pub fn apply_precompile_overrides(
state_overrides: &StateOverride,
precompiles: &mut PrecompilesMap,
) -> Result<(), EthSimulateError> {
use alloy_evm::precompiles::DynPrecompile;
let moves: Vec<_> = state_overrides
.iter()
.filter_map(|(source, account_override)| {
account_override.move_precompile_to.map(|dest| (*source, dest))
})
.collect();
if moves.is_empty() {
return Ok(());
}
for (source, _dest) in &moves {
if precompiles.get(source).is_none() {
return Err(EthSimulateError::NotAPrecompile(*source));
}
}
let mut extracted: Vec<(Address, Address, DynPrecompile)> = Vec::with_capacity(moves.len());
for (source, dest) in moves {
if source == dest {
continue;
}
let mut found_precompile: Option<DynPrecompile> = None;
precompiles.apply_precompile(&source, |existing| {
found_precompile = existing;
None
});
if let Some(precompile) = found_precompile {
extracted.push((source, dest, precompile));
}
}
for (_source, dest, precompile) in extracted {
precompiles.apply_precompile(&dest, |_| Some(precompile));
}
Ok(())
}
/// Converts all [`TransactionRequest`]s into [`Recovered`] transactions and applies them to the
/// given [`BlockExecutor`].
///