From d4f28b02ffcae342d04d12d55de43862dc7a0049 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 26 Jan 2026 13:40:12 +0100 Subject: [PATCH] feat(rpc): implement movePrecompileToAddress for eth_simulateV1 (#21414) Co-authored-by: Amp --- crates/rpc/rpc-eth-api/src/helpers/call.rs | 30 +++++++-- crates/rpc/rpc-eth-types/src/simulate.rs | 76 ++++++++++++++++++++++ 2 files changed, 100 insertions(+), 6 deletions(-) diff --git a/crates/rpc/rpc-eth-api/src/helpers/call.rs b/crates/rpc/rpc-eth-api/src/helpers/call.rs index 50a293f55c..7617130dd7 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/call.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/call.rs @@ -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, diff --git a/crates/rpc/rpc-eth-types/src/simulate.rs b/crates/rpc/rpc-eth-types/src/simulate.rs index 98658a75b5..2c30556c1c 100644 --- a/crates/rpc/rpc-eth-types/src/simulate.rs +++ b/crates/rpc/rpc-eth-types/src/simulate.rs @@ -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 = 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`]. ///