From da1baf80cf0a8edf1d4967e676b0cab39e194961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B3nica=20Jin?= Date: Fri, 31 Oct 2025 13:00:27 +0100 Subject: [PATCH 01/32] add eth_transact_at_time --- .../revive/rpc/src/client/runtime_api.rs | 16 +++++++- substrate/frame/revive/rpc/src/lib.rs | 1 + substrate/frame/revive/src/exec.rs | 24 +++++++++--- substrate/frame/revive/src/lib.rs | 39 ++++++++++++++++--- substrate/frame/revive/src/primitives.rs | 12 ++++++ substrate/frame/timestamp/src/lib.rs | 5 +++ 6 files changed, 84 insertions(+), 13 deletions(-) diff --git a/substrate/frame/revive/rpc/src/client/runtime_api.rs b/substrate/frame/revive/rpc/src/client/runtime_api.rs index 8db3df914c539..c91de7eff0c3d 100644 --- a/substrate/frame/revive/rpc/src/client/runtime_api.rs +++ b/substrate/frame/revive/rpc/src/client/runtime_api.rs @@ -21,8 +21,9 @@ use crate::{ ClientError, }; use pallet_revive::{ - evm::{Block as EthBlock, GenericTransaction, ReceiptGasInfo, Trace, H160, U256}, + evm::{decode_revert_reason, Block as EthBlock, GenericTransaction, ReceiptGasInfo, Trace, H160, U256}, EthTransactInfo, + EthTransactError, }; use sp_core::H256; use subxt::OnlineClient; @@ -70,7 +71,18 @@ impl RuntimeApi { let result = self.0.call(payload).await?; match result { Err(err) => { - log::debug!(target: LOG_TARGET, "Dry run failed {err:?}"); + // Attempt to decode revert reason if available + let decoded_msg = match &err.0 { + EthTransactError::Data(data) => { + match decode_revert_reason(data) { + Some(reason) => format!("execution reverted: {reason}"), + None => "execution reverted".to_string(), + } + } + EthTransactError::Message(msg) => msg.clone(), + }; + log::debug!(target: LOG_TARGET, "err = {:?}", err); + log::debug!(target: LOG_TARGET, "Dry run failed {decoded_msg}"); Err(ClientError::TransactError(err.0)) }, Ok(result) => Ok(result.0), diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index d742ad937f3b9..1b73e35e16319 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -147,6 +147,7 @@ impl EthRpcServer for EthRpcServerImpl { log::trace!(target: LOG_TARGET, "estimate_gas transaction={transaction:?} block={block:?}"); let hash = self.client.block_hash_for_tag(block.unwrap_or_default().into()).await?; let runtime_api = self.client.runtime_api(hash); + log::trace!("calling dry_run"); let dry_run = runtime_api.dry_run(transaction).await?; log::trace!(target: LOG_TARGET, "estimate_gas result={dry_run:?}"); Ok(dry_run.eth_gas) diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 60901419c742d..00754051472ac 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -37,7 +37,7 @@ use alloc::{ collections::{BTreeMap, BTreeSet}, vec::Vec, }; -use core::{fmt::Debug, marker::PhantomData, mem, ops::ControlFlow}; +use core::{cmp, fmt::Debug, marker::PhantomData, mem, ops::ControlFlow}; use frame_support::{ crypto::ecdsa::ECDSAExt, dispatch::DispatchResult, @@ -951,12 +951,25 @@ where return Ok(None); }; + let mut latest_timestamp = T::Time::now(); + let mut block_number = >::block_number(); + if exec_config.is_dry_run { + block_number += 1u32.saturated_into(); + if let Some(ts_override) = exec_config.dry_run_timestamp_override { + let moment_override: <::Time as Time>::Moment = ts_override.saturated_into(); + let delta: <::Time as Time>::Moment = 1000u64.saturated_into(); + latest_timestamp = cmp::max(latest_timestamp + delta, moment_override); + } + } + // let latest_timestamp = T::Time::now() + 1000; + // let override_timestamp: <::Time as Time>::Moment = exec_config.dry_run_timestamp_override.saturated_into(); + // let timestamp = cmp::max(min_timestamp, override_timestamp); let stack = Self { origin, gas_meter, storage_meter, - timestamp: T::Time::now(), - block_number: >::block_number(), + timestamp: latest_timestamp, + block_number: block_number, first_frame, frames: Default::default(), transient_storage: TransientStorage::new(limits::TRANSIENT_STORAGE_BYTES), @@ -965,7 +978,8 @@ where contracts_to_be_destroyed: BTreeMap::new(), _phantom: Default::default(), }; - + log::trace!(target: LOG_TARGET, "stack timestamp: {:?}", stack.timestamp); + log::trace!(target: LOG_TARGET, "stack block number: {:?}", stack.block_number); Ok(Some((stack, executable))) } @@ -1411,7 +1425,7 @@ where } else { self.transient_storage.rollback_transaction(); } - + log::trace!(target: LOG_TARGET, "frame finished with: {success}"); log::trace!(target: LOG_TARGET, "frame finished with: {output:?}"); self.pop_frame(success); diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index c869d5d65402a..3a1e9b155ce21 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -1513,12 +1513,14 @@ impl Pallet { Ok(result) }; let result = Self::run_guarded(try_call); - ContractResult { + let res = ContractResult { result: result.map_err(|r| r.error), gas_consumed: gas_meter.gas_consumed(), gas_required: gas_meter.gas_required(), storage_deposit, - } + }; + log::debug!(target: LOG_TARGET, "{res:?}"); + res } /// Prepare a dry run for the given account. @@ -1611,6 +1613,7 @@ impl Pallet { /// - `tx`: The Ethereum transaction to simulate. pub fn dry_run_eth_transact( mut tx: GenericTransaction, + timestamp_override: Option, ) -> Result>, EthTransactError> where T::Nonce: Into, @@ -1675,7 +1678,7 @@ impl Pallet { call_info.encoded_len, base_info.total_weight(), ) - .with_dry_run() + .with_dry_run_timestamp_override(timestamp_override) }; // emulate transaction behavior @@ -1691,7 +1694,7 @@ impl Pallet { } // the deposit is done when the transaction is transformed from an `eth_transact` - // we emulate this behavior for the dry-run her + // we emulate this behavior for the dry-run here T::FeeInfo::deposit_txfee(T::Currency::issue(fees)); let extract_error = |err| { @@ -1725,6 +1728,7 @@ impl Pallet { Default::default() } else { // Dry run the call. + log::debug!(target: LOG_TARGET, "calling bare_call"); let result = crate::Pallet::::bare_call( OriginFor::::signed(origin), dest, @@ -1738,6 +1742,7 @@ impl Pallet { let data = match result.result { Ok(return_value) => { if return_value.did_revert() { + log::debug!(target: LOG_TARGET, "contract call reverted"); return Err(EthTransactError::Data(return_value.data)); } return_value.data @@ -2332,7 +2337,7 @@ environmental!(executing_contract: bool); sp_api::decl_runtime_apis! { /// The API used to dry-run contract interactions. - #[api_version(1)] + #[api_version(2)] pub trait ReviveApi where AccountId: Codec, Balance: Codec, @@ -2397,6 +2402,16 @@ sp_api::decl_runtime_apis! { /// See [`crate::Pallet::dry_run_eth_transact`] fn eth_transact(tx: GenericTransaction) -> Result, EthTransactError>; + /// Perform an Ethereum call with an explicit timestamp. + /// + /// This is used by RPCs (like gas estimation) that have access to the system clock. + /// The timestamp can be used for simulating time-dependent smart contract logic + /// or for consistent gas estimation results. + fn eth_transact_at_time( + tx: GenericTransaction, + timestamp: u64 + ) -> Result, EthTransactError>; + /// Upload new code without instantiating a contract from it. /// /// See [`crate::Pallet::bare_upload_code`]. @@ -2559,7 +2574,19 @@ macro_rules! impl_runtime_apis_plus_revive_traits { sp_runtime::traits::TransactionExtension, sp_runtime::traits::Block as BlockT }; - $crate::Pallet::::dry_run_eth_transact(tx) + $crate::Pallet::::dry_run_eth_transact(tx, None) + } + + fn eth_transact_at_time( + tx: $crate::evm::GenericTransaction, + timestamp: u64 + ) -> Result<$crate::EthTransactInfo, $crate::EthTransactError> { + use $crate::{ + codec::Encode, evm::runtime::EthExtra, frame_support::traits::Get, + sp_runtime::traits::TransactionExtension, + sp_runtime::traits::Block as BlockT + }; + $crate::Pallet::::dry_run_eth_transact(tx, Some(timestamp)) } fn call( diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs index 2180fd27c467c..6c4d5ce85d5e3 100644 --- a/substrate/frame/revive/src/primitives.rs +++ b/substrate/frame/revive/src/primitives.rs @@ -350,6 +350,8 @@ pub struct ExecConfig { /// Whether this configuration was created for a dry-run execution. /// Use to enable logic that should only run in dry-run mode. pub is_dry_run: bool, + /// An optional timestamp override for dry-run executions. + pub dry_run_timestamp_override: Option, /// An optional mock handler that can be used to override certain behaviors. /// This is primarily used for testing purposes and should be `None` in production /// environments. @@ -364,6 +366,7 @@ impl ExecConfig { collect_deposit_from_hold: None, effective_gas_price: None, is_dry_run: false, + dry_run_timestamp_override: None, mock_handler: None, } } @@ -375,6 +378,7 @@ impl ExecConfig { effective_gas_price: None, mock_handler: None, is_dry_run: false, + dry_run_timestamp_override: None, } } @@ -386,6 +390,7 @@ impl ExecConfig { effective_gas_price: Some(effective_gas_price), mock_handler: None, is_dry_run: false, + dry_run_timestamp_override: None, } } @@ -394,6 +399,13 @@ impl ExecConfig { self.is_dry_run = true; self } + + /// Set a timestamp override for dry-run executions. + pub fn with_dry_run_timestamp_override(mut self, timestamp: Option) -> Self { + self.is_dry_run = true; + self.dry_run_timestamp_override = timestamp; + self + } } /// Indicates whether the code was removed after the last refcount was decremented. diff --git a/substrate/frame/timestamp/src/lib.rs b/substrate/frame/timestamp/src/lib.rs index dc3f29fded938..7948b88c85cd7 100644 --- a/substrate/frame/timestamp/src/lib.rs +++ b/substrate/frame/timestamp/src/lib.rs @@ -355,6 +355,11 @@ impl Time for Pallet { type Moment = T::Moment; fn now() -> Self::Moment { + log::debug!( + target: "runtime::timestamp", + "`pallet_timestamp::Time::now` called, returning now = {:?}", + Now::::get(), + ); Now::::get() } } From bd927ad4d17ce144301bf70137a30459701995b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B3nica=20Jin?= Date: Fri, 31 Oct 2025 13:57:28 +0100 Subject: [PATCH 02/32] add: local timestamp in estimate_gas --- .../frame/revive/rpc/src/client/runtime_api.rs | 11 +++++++++-- substrate/frame/revive/rpc/src/lib.rs | 14 ++++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/substrate/frame/revive/rpc/src/client/runtime_api.rs b/substrate/frame/revive/rpc/src/client/runtime_api.rs index c91de7eff0c3d..afa1f5c3cb15e 100644 --- a/substrate/frame/revive/rpc/src/client/runtime_api.rs +++ b/substrate/frame/revive/rpc/src/client/runtime_api.rs @@ -66,9 +66,16 @@ impl RuntimeApi { pub async fn dry_run( &self, tx: GenericTransaction, + timestamp_override: Option, ) -> Result, ClientError> { - let payload = subxt_client::apis().revive_api().eth_transact(tx.into()); - let result = self.0.call(payload).await?; + let result = match timestamp_override { + Some(ts) => { + self.0.call(subxt_client::apis().revive_api().eth_transact_at_time(tx.into(), ts)).await? + } + None => { + self.0.call(subxt_client::apis().revive_api().eth_transact(tx.into())).await? + } + }; match result { Err(err) => { // Attempt to decode revert reason if available diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index 1b73e35e16319..0264f5a897716 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -26,6 +26,7 @@ use pallet_revive::evm::*; use sp_core::{keccak_256, H160, H256, U256}; use thiserror::Error; use tokio::time::Duration; +use std::time::{SystemTime, UNIX_EPOCH}; pub mod cli; pub mod client; @@ -145,10 +146,18 @@ impl EthRpcServer for EthRpcServerImpl { block: Option, ) -> RpcResult { log::trace!(target: LOG_TARGET, "estimate_gas transaction={transaction:?} block={block:?}"); + // TODO change the default to pending instead of latest let hash = self.client.block_hash_for_tag(block.unwrap_or_default().into()).await?; let runtime_api = self.client.runtime_api(hash); log::trace!("calling dry_run"); - let dry_run = runtime_api.dry_run(transaction).await?; + let timestamp_override = match SystemTime::now().duration_since(UNIX_EPOCH) { + Ok(dur) => Some(dur.as_millis() as u64), // milliseconds since epoch + Err(err) => { + log::warn!(target: LOG_TARGET, "system time before UNIX_EPOCH: {err:?}, skipping timestamp override"); + None + } + }; + let dry_run = runtime_api.dry_run(transaction, timestamp_override).await?; log::trace!(target: LOG_TARGET, "estimate_gas result={dry_run:?}"); Ok(dry_run.eth_gas) } @@ -158,9 +167,10 @@ impl EthRpcServer for EthRpcServerImpl { transaction: GenericTransaction, block: Option, ) -> RpcResult { + // TODO: check if this needs timestamp let hash = self.client.block_hash_for_tag(block.unwrap_or_default()).await?; let runtime_api = self.client.runtime_api(hash); - let dry_run = runtime_api.dry_run(transaction).await?; + let dry_run = runtime_api.dry_run(transaction, None).await?; Ok(dry_run.data.into()) } From b75f9ff7e45bd36219abf438d71d3c7d91ac956f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B3nica=20Jin?= Date: Fri, 31 Oct 2025 18:15:35 +0100 Subject: [PATCH 03/32] refactor: merge eth_transact_at_time into eth_transact --- .../revive/rpc/src/client/runtime_api.rs | 12 +++----- substrate/frame/revive/src/evm/runtime.rs | 2 +- substrate/frame/revive/src/lib.rs | 29 +++---------------- 3 files changed, 9 insertions(+), 34 deletions(-) diff --git a/substrate/frame/revive/rpc/src/client/runtime_api.rs b/substrate/frame/revive/rpc/src/client/runtime_api.rs index afa1f5c3cb15e..545bb55b2d5d2 100644 --- a/substrate/frame/revive/rpc/src/client/runtime_api.rs +++ b/substrate/frame/revive/rpc/src/client/runtime_api.rs @@ -68,14 +68,10 @@ impl RuntimeApi { tx: GenericTransaction, timestamp_override: Option, ) -> Result, ClientError> { - let result = match timestamp_override { - Some(ts) => { - self.0.call(subxt_client::apis().revive_api().eth_transact_at_time(tx.into(), ts)).await? - } - None => { - self.0.call(subxt_client::apis().revive_api().eth_transact(tx.into())).await? - } - }; + let payload = subxt_client::apis() + .revive_api() + .eth_transact(tx.into(), timestamp_override); + let result = self.0.call(payload).await?; match result { Err(err) => { // Attempt to decode revert reason if available diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index 5674088de4efc..1ec0adba15577 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -429,7 +429,7 @@ mod test { let account = Account::default(); Self::fund_account(&account); - let dry_run = crate::Pallet::::dry_run_eth_transact(self.tx.clone()); + let dry_run = crate::Pallet::::dry_run_eth_transact(self.tx.clone(), None); self.tx.gas_price = Some(>::evm_base_fee()); match dry_run { diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 3a1e9b155ce21..f75dcc0b51eea 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -2400,17 +2400,7 @@ sp_api::decl_runtime_apis! { /// Perform an Ethereum call. /// /// See [`crate::Pallet::dry_run_eth_transact`] - fn eth_transact(tx: GenericTransaction) -> Result, EthTransactError>; - - /// Perform an Ethereum call with an explicit timestamp. - /// - /// This is used by RPCs (like gas estimation) that have access to the system clock. - /// The timestamp can be used for simulating time-dependent smart contract logic - /// or for consistent gas estimation results. - fn eth_transact_at_time( - tx: GenericTransaction, - timestamp: u64 - ) -> Result, EthTransactError>; + fn eth_transact(tx: GenericTransaction, timestamp: Option) -> Result, EthTransactError>; /// Upload new code without instantiating a contract from it. /// @@ -2568,25 +2558,14 @@ macro_rules! impl_runtime_apis_plus_revive_traits { fn eth_transact( tx: $crate::evm::GenericTransaction, + timestamp: Option ) -> Result<$crate::EthTransactInfo, $crate::EthTransactError> { use $crate::{ codec::Encode, evm::runtime::EthExtra, frame_support::traits::Get, sp_runtime::traits::TransactionExtension, sp_runtime::traits::Block as BlockT }; - $crate::Pallet::::dry_run_eth_transact(tx, None) - } - - fn eth_transact_at_time( - tx: $crate::evm::GenericTransaction, - timestamp: u64 - ) -> Result<$crate::EthTransactInfo, $crate::EthTransactError> { - use $crate::{ - codec::Encode, evm::runtime::EthExtra, frame_support::traits::Get, - sp_runtime::traits::TransactionExtension, - sp_runtime::traits::Block as BlockT - }; - $crate::Pallet::::dry_run_eth_transact(tx, Some(timestamp)) + $crate::Pallet::::dry_run_eth_transact(tx, timestamp) } fn call( @@ -2719,7 +2698,7 @@ macro_rules! impl_runtime_apis_plus_revive_traits { t.watch_address(&tx.from.unwrap_or_default()); t.watch_address(&$crate::Pallet::::block_author()); - let result = trace(t, || Self::eth_transact(tx)); + let result = trace(t, || Self::eth_transact(tx, None)); if let Some(trace) = tracer.collect_trace() { Ok(trace) From c4ea78f71d74fc2c3fde6bf0adcc7aa2a6732465 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B3nica=20Jin?= Date: Fri, 31 Oct 2025 19:14:56 +0100 Subject: [PATCH 04/32] change estimate_gas block default to pending --- substrate/frame/revive/rpc/src/lib.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index 0264f5a897716..4c09018c1c17d 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -146,17 +146,19 @@ impl EthRpcServer for EthRpcServerImpl { block: Option, ) -> RpcResult { log::trace!(target: LOG_TARGET, "estimate_gas transaction={transaction:?} block={block:?}"); - // TODO change the default to pending instead of latest - let hash = self.client.block_hash_for_tag(block.unwrap_or_default().into()).await?; - let runtime_api = self.client.runtime_api(hash); - log::trace!("calling dry_run"); - let timestamp_override = match SystemTime::now().duration_since(UNIX_EPOCH) { - Ok(dur) => Some(dur.as_millis() as u64), // milliseconds since epoch - Err(err) => { - log::warn!(target: LOG_TARGET, "system time before UNIX_EPOCH: {err:?}, skipping timestamp override"); - None + let block = block.unwrap_or(BlockNumberOrTag::BlockTag(BlockTag::Pending)); + let timestamp_override = match block { + BlockNumberOrTag::BlockTag(BlockTag::Pending) => { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .ok() + .map(|dur| dur.as_millis() as u64) } + _ => None, }; + let hash = self.client.block_hash_for_tag(block.into()).await?; + let runtime_api = self.client.runtime_api(hash); + log::trace!("calling dry_run"); let dry_run = runtime_api.dry_run(transaction, timestamp_override).await?; log::trace!(target: LOG_TARGET, "estimate_gas result={dry_run:?}"); Ok(dry_run.eth_gas) From de1a29414b266bb46a219a4b8e88a56bb0e03540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B3nica=20Jin?= Date: Fri, 31 Oct 2025 19:51:04 +0100 Subject: [PATCH 05/32] add: timestamp to eth_call --- substrate/frame/revive/rpc/src/lib.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index 4c09018c1c17d..0d3cbf03a328b 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -169,10 +169,19 @@ impl EthRpcServer for EthRpcServerImpl { transaction: GenericTransaction, block: Option, ) -> RpcResult { - // TODO: check if this needs timestamp - let hash = self.client.block_hash_for_tag(block.unwrap_or_default()).await?; + let block = block.unwrap_or_default(); + let timestamp_override = match block { + BlockNumberOrTagOrHash::BlockTag(BlockTag::Pending) => { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .ok() + .map(|dur| dur.as_millis() as u64) + } + _ => None, + }; + let hash = self.client.block_hash_for_tag(block).await?; let runtime_api = self.client.runtime_api(hash); - let dry_run = runtime_api.dry_run(transaction, None).await?; + let dry_run = runtime_api.dry_run(transaction, timestamp_override).await?; Ok(dry_run.data.into()) } From 3ec16d50bf753bed92b5da47ad0caba34a46f14c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B3nica=20Jin?= Date: Mon, 3 Nov 2025 11:09:39 +0100 Subject: [PATCH 06/32] refactor timestamp source & fix tests --- Cargo.lock | 1 + substrate/frame/revive/rpc/Cargo.toml | 1 + substrate/frame/revive/rpc/src/lib.rs | 16 +++------------- substrate/frame/revive/src/tests/sol/contract.rs | 3 +++ 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8a99f4001567e..2d57f4e988a9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13364,6 +13364,7 @@ dependencies = [ "sp-io", "sp-rpc", "sp-runtime", + "sp-timestamp", "sp-weights", "sqlx", "static_init", diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml index 1c387d80e4d05..60af7f08f8e7b 100644 --- a/substrate/frame/revive/rpc/Cargo.toml +++ b/substrate/frame/revive/rpc/Cargo.toml @@ -41,6 +41,7 @@ sp-core = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true } sp-rpc = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } +sp-timestamp = { workspace = true } sp-weights = { workspace = true, default-features = true } sqlx = { workspace = true, features = ["macros", "runtime-tokio", "sqlite"] } subxt = { workspace = true, default-features = true, features = ["reconnecting-rpc-client"] } diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index 0d3cbf03a328b..5b37db30f61be 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -24,9 +24,9 @@ use jsonrpsee::{ }; use pallet_revive::evm::*; use sp_core::{keccak_256, H160, H256, U256}; +use sp_timestamp::{Timestamp}; use thiserror::Error; use tokio::time::Duration; -use std::time::{SystemTime, UNIX_EPOCH}; pub mod cli; pub mod client; @@ -148,12 +148,7 @@ impl EthRpcServer for EthRpcServerImpl { log::trace!(target: LOG_TARGET, "estimate_gas transaction={transaction:?} block={block:?}"); let block = block.unwrap_or(BlockNumberOrTag::BlockTag(BlockTag::Pending)); let timestamp_override = match block { - BlockNumberOrTag::BlockTag(BlockTag::Pending) => { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .ok() - .map(|dur| dur.as_millis() as u64) - } + BlockNumberOrTag::BlockTag(BlockTag::Pending) => Some(Timestamp::current().as_millis()), _ => None, }; let hash = self.client.block_hash_for_tag(block.into()).await?; @@ -171,12 +166,7 @@ impl EthRpcServer for EthRpcServerImpl { ) -> RpcResult { let block = block.unwrap_or_default(); let timestamp_override = match block { - BlockNumberOrTagOrHash::BlockTag(BlockTag::Pending) => { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .ok() - .map(|dur| dur.as_millis() as u64) - } + BlockNumberOrTagOrHash::BlockTag(BlockTag::Pending) => Some(Timestamp::current().as_millis()), _ => None, }; let hash = self.client.block_hash_for_tag(block).await?; diff --git a/substrate/frame/revive/src/tests/sol/contract.rs b/substrate/frame/revive/src/tests/sol/contract.rs index 74144ac3db845..e5a265b70bcc4 100644 --- a/substrate/frame/revive/src/tests/sol/contract.rs +++ b/substrate/frame/revive/src/tests/sol/contract.rs @@ -401,6 +401,7 @@ fn mock_caller_hook_works(caller_type: FixtureType, callee_type: FixtureType) { collect_deposit_from_hold: None, effective_gas_price: None, is_dry_run: false, + dry_run_timestamp_override: None, mock_handler: Some(Box::new(MockHandlerImpl { mock_caller: Some(BOB_ADDR), mock_call: Default::default(), @@ -454,6 +455,7 @@ fn mock_call_hook_works(caller_type: FixtureType, callee_type: FixtureType) { collect_deposit_from_hold: None, effective_gas_price: None, is_dry_run: false, + dry_run_timestamp_override: None, mock_handler: Some(Box::new(MockHandlerImpl { mock_caller: None, mock_call: iter::once(( @@ -514,6 +516,7 @@ fn mock_delegatecall_hook_works(caller_type: FixtureType, callee_type: FixtureTy collect_deposit_from_hold: None, effective_gas_price: None, is_dry_run: false, + dry_run_timestamp_override: None, mock_handler: Some(Box::new(MockHandlerImpl { mock_caller: None, mock_call: Default::default(), From ae995229ecb71519f3fcf77debf10ac1fb6ea357 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B3nica=20Jin?= Date: Mon, 3 Nov 2025 11:28:04 +0100 Subject: [PATCH 07/32] remove redundant logs and comments --- substrate/frame/revive/rpc/src/lib.rs | 1 - substrate/frame/revive/src/exec.rs | 7 +------ substrate/frame/revive/src/lib.rs | 10 +++------- substrate/frame/timestamp/src/lib.rs | 5 ----- 4 files changed, 4 insertions(+), 19 deletions(-) diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index 5b37db30f61be..d1fbb8a4b5cb7 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -153,7 +153,6 @@ impl EthRpcServer for EthRpcServerImpl { }; let hash = self.client.block_hash_for_tag(block.into()).await?; let runtime_api = self.client.runtime_api(hash); - log::trace!("calling dry_run"); let dry_run = runtime_api.dry_run(transaction, timestamp_override).await?; log::trace!(target: LOG_TARGET, "estimate_gas result={dry_run:?}"); Ok(dry_run.eth_gas) diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 00754051472ac..4c210e5eb630c 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -961,9 +961,7 @@ where latest_timestamp = cmp::max(latest_timestamp + delta, moment_override); } } - // let latest_timestamp = T::Time::now() + 1000; - // let override_timestamp: <::Time as Time>::Moment = exec_config.dry_run_timestamp_override.saturated_into(); - // let timestamp = cmp::max(min_timestamp, override_timestamp); + let stack = Self { origin, gas_meter, @@ -978,8 +976,6 @@ where contracts_to_be_destroyed: BTreeMap::new(), _phantom: Default::default(), }; - log::trace!(target: LOG_TARGET, "stack timestamp: {:?}", stack.timestamp); - log::trace!(target: LOG_TARGET, "stack block number: {:?}", stack.block_number); Ok(Some((stack, executable))) } @@ -1425,7 +1421,6 @@ where } else { self.transient_storage.rollback_transaction(); } - log::trace!(target: LOG_TARGET, "frame finished with: {success}"); log::trace!(target: LOG_TARGET, "frame finished with: {output:?}"); self.pop_frame(success); diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index f75dcc0b51eea..a468e8c0c67d7 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -1513,14 +1513,12 @@ impl Pallet { Ok(result) }; let result = Self::run_guarded(try_call); - let res = ContractResult { + ContractResult { result: result.map_err(|r| r.error), gas_consumed: gas_meter.gas_consumed(), gas_required: gas_meter.gas_required(), storage_deposit, - }; - log::debug!(target: LOG_TARGET, "{res:?}"); - res + } } /// Prepare a dry run for the given account. @@ -1728,7 +1726,6 @@ impl Pallet { Default::default() } else { // Dry run the call. - log::debug!(target: LOG_TARGET, "calling bare_call"); let result = crate::Pallet::::bare_call( OriginFor::::signed(origin), dest, @@ -1742,7 +1739,6 @@ impl Pallet { let data = match result.result { Ok(return_value) => { if return_value.did_revert() { - log::debug!(target: LOG_TARGET, "contract call reverted"); return Err(EthTransactError::Data(return_value.data)); } return_value.data @@ -2337,7 +2333,7 @@ environmental!(executing_contract: bool); sp_api::decl_runtime_apis! { /// The API used to dry-run contract interactions. - #[api_version(2)] + #[api_version(1)] pub trait ReviveApi where AccountId: Codec, Balance: Codec, diff --git a/substrate/frame/timestamp/src/lib.rs b/substrate/frame/timestamp/src/lib.rs index 7948b88c85cd7..dc3f29fded938 100644 --- a/substrate/frame/timestamp/src/lib.rs +++ b/substrate/frame/timestamp/src/lib.rs @@ -355,11 +355,6 @@ impl Time for Pallet { type Moment = T::Moment; fn now() -> Self::Moment { - log::debug!( - target: "runtime::timestamp", - "`pallet_timestamp::Time::now` called, returning now = {:?}", - Now::::get(), - ); Now::::get() } } From 0db67ae0c9a5571b212edeb7676f9963c39810c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B3nica=20Jin?= Date: Mon, 3 Nov 2025 13:19:38 +0100 Subject: [PATCH 08/32] add: dry_run timestamp override tests --- .../revive/rpc/src/client/runtime_api.rs | 22 ++++----- substrate/frame/revive/rpc/src/lib.rs | 5 +- substrate/frame/revive/src/exec.rs | 5 +- .../frame/revive/src/tests/sol/block_info.rs | 48 ++++++++++++++++++- 4 files changed, 63 insertions(+), 17 deletions(-) diff --git a/substrate/frame/revive/rpc/src/client/runtime_api.rs b/substrate/frame/revive/rpc/src/client/runtime_api.rs index 545bb55b2d5d2..80541af1c4d3d 100644 --- a/substrate/frame/revive/rpc/src/client/runtime_api.rs +++ b/substrate/frame/revive/rpc/src/client/runtime_api.rs @@ -21,9 +21,11 @@ use crate::{ ClientError, }; use pallet_revive::{ - evm::{decode_revert_reason, Block as EthBlock, GenericTransaction, ReceiptGasInfo, Trace, H160, U256}, - EthTransactInfo, - EthTransactError, + evm::{ + decode_revert_reason, Block as EthBlock, GenericTransaction, ReceiptGasInfo, Trace, H160, + U256, + }, + EthTransactError, EthTransactInfo, }; use sp_core::H256; use subxt::OnlineClient; @@ -68,20 +70,16 @@ impl RuntimeApi { tx: GenericTransaction, timestamp_override: Option, ) -> Result, ClientError> { - let payload = subxt_client::apis() - .revive_api() - .eth_transact(tx.into(), timestamp_override); + let payload = subxt_client::apis().revive_api().eth_transact(tx.into(), timestamp_override); let result = self.0.call(payload).await?; match result { Err(err) => { // Attempt to decode revert reason if available let decoded_msg = match &err.0 { - EthTransactError::Data(data) => { - match decode_revert_reason(data) { - Some(reason) => format!("execution reverted: {reason}"), - None => "execution reverted".to_string(), - } - } + EthTransactError::Data(data) => match decode_revert_reason(data) { + Some(reason) => format!("execution reverted: {reason}"), + None => "execution reverted".to_string(), + }, EthTransactError::Message(msg) => msg.clone(), }; log::debug!(target: LOG_TARGET, "err = {:?}", err); diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index d1fbb8a4b5cb7..66d27709bacd8 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -24,7 +24,7 @@ use jsonrpsee::{ }; use pallet_revive::evm::*; use sp_core::{keccak_256, H160, H256, U256}; -use sp_timestamp::{Timestamp}; +use sp_timestamp::Timestamp; use thiserror::Error; use tokio::time::Duration; @@ -165,7 +165,8 @@ impl EthRpcServer for EthRpcServerImpl { ) -> RpcResult { let block = block.unwrap_or_default(); let timestamp_override = match block { - BlockNumberOrTagOrHash::BlockTag(BlockTag::Pending) => Some(Timestamp::current().as_millis()), + BlockNumberOrTagOrHash::BlockTag(BlockTag::Pending) => + Some(Timestamp::current().as_millis()), _ => None, }; let hash = self.client.block_hash_for_tag(block).await?; diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 4c210e5eb630c..3ea87bf9f3ce9 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -956,7 +956,8 @@ where if exec_config.is_dry_run { block_number += 1u32.saturated_into(); if let Some(ts_override) = exec_config.dry_run_timestamp_override { - let moment_override: <::Time as Time>::Moment = ts_override.saturated_into(); + let moment_override: <::Time as Time>::Moment = + ts_override.saturated_into(); let delta: <::Time as Time>::Moment = 1000u64.saturated_into(); latest_timestamp = cmp::max(latest_timestamp + delta, moment_override); } @@ -967,7 +968,7 @@ where gas_meter, storage_meter, timestamp: latest_timestamp, - block_number: block_number, + block_number, first_frame, frames: Default::default(), transient_storage: TransientStorage::new(limits::TRANSIENT_STORAGE_BYTES), diff --git a/substrate/frame/revive/src/tests/sol/block_info.rs b/substrate/frame/revive/src/tests/sol/block_info.rs index 15c186b6df0bf..c9a3b5dc0a026 100644 --- a/substrate/frame/revive/src/tests/sol/block_info.rs +++ b/substrate/frame/revive/src/tests/sol/block_info.rs @@ -21,7 +21,7 @@ use crate::{ test_utils::{builder::Contract, ALICE}, tests::{builder, Contracts, ExtBuilder, System, Test, Timestamp}, vm::evm::DIFFICULTY, - Code, Config, Pallet, + Code, Config, ExecConfig, Pallet, }; use alloy_core::sol_types::{SolCall, SolInterface}; @@ -53,6 +53,28 @@ fn block_number_works(fixture_type: FixtureType) { }); } +#[test_case(FixtureType::Solc)] +#[test_case(FixtureType::Resolc)] +fn block_number_dry_run_works(fixture_type: FixtureType) { + let (code, _) = compile_module_with_type("BlockInfo", fixture_type).unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + System::set_block_number(42); + + let result = builder::bare_call(addr) + .data( + BlockInfo::BlockInfoCalls::blockNumber(BlockInfo::blockNumberCall {}).abi_encode(), + ) + .exec_config(ExecConfig::new_substrate_tx().with_dry_run()) + .build_and_unwrap_result(); + let decoded = BlockInfo::blockNumberCall::abi_decode_returns(&result.data).unwrap(); + assert_eq!(43u64, decoded); + }); +} + /// Tests that the blockauthor opcode works as expected. #[test_case(FixtureType::Solc)] #[test_case(FixtureType::Resolc)] @@ -113,6 +135,30 @@ fn timestamp_works(fixture_type: FixtureType) { }); } +#[test_case(FixtureType::Solc)] +#[test_case(FixtureType::Resolc)] +fn timestamp_dry_run_override_works(fixture_type: FixtureType) { + let (code, _) = compile_module_with_type("BlockInfo", fixture_type).unwrap(); + ExtBuilder::default().build().execute_with(|| { + Timestamp::set_timestamp(2000); + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + let override_ts = Timestamp::get() + 10_000; + let result: crate::ExecReturnValue = builder::bare_call(addr) + .data(BlockInfo::BlockInfoCalls::timestamp(BlockInfo::timestampCall {}).abi_encode()) + .exec_config(ExecConfig::new_substrate_tx().with_dry_run_timestamp_override(Some(override_ts))) + .build_and_unwrap_result(); + let decoded = BlockInfo::timestampCall::abi_decode_returns(&result.data).unwrap(); + assert_eq!( + // Solidity expects timestamps in seconds, whereas pallet_timestamp uses + // milliseconds. + (override_ts / 1000) as u64, + decoded + ); + }); +} + /// Tests that the gaslimit opcode works as expected. #[test_case(FixtureType::Solc)] #[test_case(FixtureType::Resolc)] From 8d39a29776520dc7303591fa6d536af5f6f30446 Mon Sep 17 00:00:00 2001 From: "cmd[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 13:07:36 +0000 Subject: [PATCH 09/32] Update from github-actions[bot] running command 'prdoc --audience runtime_dev --audience runtime_user --bump patch' --- prdoc/pr_10191.prdoc | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 prdoc/pr_10191.prdoc diff --git a/prdoc/pr_10191.prdoc b/prdoc/pr_10191.prdoc new file mode 100644 index 0000000000000..126f804186e3c --- /dev/null +++ b/prdoc/pr_10191.prdoc @@ -0,0 +1,32 @@ +title: 'pallet_revive: Add dry-run timestamp override support' +doc: +- audience: Runtime User + description: |- + # Description + + This PR updates `pallet-revive` to **support overriding the block timestamp during dry-run calls**. + The dry-run execution now uses the following configuration for `eth_estimateGas` and `eth_call` when the block tag is `pending`: + + ```text + block.timestamp = max(rpc_timestamp, latest_block.timestamp + 1) + block.number = latest_block.number + 1 + ``` + + Fixes [#153](https://github.com/paritytech/contract-issues/issues/153), [#205](https://github.com/paritytech/contract-issues/issues/205) + + ## Integration + + Downstream projects using the `ReviveApi::eth_transact` runtime API should either provide a `timestamp` or pass `None`. + + ## Review Notes + - Added dry run timestamp to `ExecConfig`. + - Added a new parameter to `ReviveApi::eth_transact` for passing the current RPC timestamp. + - `eth_estimateGas` defaults to the `pending` block tag. + - `eth_estimateGas` and `eth_call` with `pending` block tag will dry run the transaction with the block timestamp set to `max(rpc_timestamp, latest_block.timestamp + 1)` and block number set to `latest_block.number + 1`. +crates: +- name: pallet-revive-eth-rpc + bump: patch +- name: pallet-revive + bump: patch +- name: pallet-timestamp + bump: patch From fa6888b966bde869ef18154676649f7335bc952a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B3nica=20Jin?= Date: Mon, 3 Nov 2025 14:14:01 +0100 Subject: [PATCH 10/32] format with fmt --- substrate/frame/revive/src/tests/sol/block_info.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/substrate/frame/revive/src/tests/sol/block_info.rs b/substrate/frame/revive/src/tests/sol/block_info.rs index c9a3b5dc0a026..fde6babd25ffd 100644 --- a/substrate/frame/revive/src/tests/sol/block_info.rs +++ b/substrate/frame/revive/src/tests/sol/block_info.rs @@ -147,7 +147,9 @@ fn timestamp_dry_run_override_works(fixture_type: FixtureType) { let override_ts = Timestamp::get() + 10_000; let result: crate::ExecReturnValue = builder::bare_call(addr) .data(BlockInfo::BlockInfoCalls::timestamp(BlockInfo::timestampCall {}).abi_encode()) - .exec_config(ExecConfig::new_substrate_tx().with_dry_run_timestamp_override(Some(override_ts))) + .exec_config( + ExecConfig::new_substrate_tx().with_dry_run_timestamp_override(Some(override_ts)), + ) .build_and_unwrap_result(); let decoded = BlockInfo::timestampCall::abi_decode_returns(&result.data).unwrap(); assert_eq!( From 0b9825f56bf690162b116c8ac31b14d01c04cf08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B3nica=20Jin?= Date: Mon, 3 Nov 2025 15:16:14 +0100 Subject: [PATCH 11/32] fix prdoc bump --- prdoc/pr_10191.prdoc | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/prdoc/pr_10191.prdoc b/prdoc/pr_10191.prdoc index 126f804186e3c..65fcae310e423 100644 --- a/prdoc/pr_10191.prdoc +++ b/prdoc/pr_10191.prdoc @@ -25,8 +25,6 @@ doc: - `eth_estimateGas` and `eth_call` with `pending` block tag will dry run the transaction with the block timestamp set to `max(rpc_timestamp, latest_block.timestamp + 1)` and block number set to `latest_block.number + 1`. crates: - name: pallet-revive-eth-rpc - bump: patch + bump: major - name: pallet-revive - bump: patch -- name: pallet-timestamp - bump: patch + bump: major From e107143d51c95986ac2926a6e154d078909d9e7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B3nica=20Jin?= Date: Mon, 3 Nov 2025 20:33:41 +0100 Subject: [PATCH 12/32] refactor dry_run api --- .../frame/revive/rpc/src/client/runtime_api.rs | 11 +++++++++-- substrate/frame/revive/rpc/src/lib.rs | 18 ++++-------------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/substrate/frame/revive/rpc/src/client/runtime_api.rs b/substrate/frame/revive/rpc/src/client/runtime_api.rs index 80541af1c4d3d..9973e1d13b4df 100644 --- a/substrate/frame/revive/rpc/src/client/runtime_api.rs +++ b/substrate/frame/revive/rpc/src/client/runtime_api.rs @@ -22,12 +22,13 @@ use crate::{ }; use pallet_revive::{ evm::{ - decode_revert_reason, Block as EthBlock, GenericTransaction, ReceiptGasInfo, Trace, H160, + decode_revert_reason, Block as EthBlock, BlockTag, BlockNumberOrTagOrHash, GenericTransaction, ReceiptGasInfo, Trace, H160, U256, }, EthTransactError, EthTransactInfo, }; use sp_core::H256; +use sp_timestamp::Timestamp; use subxt::OnlineClient; const LOG_TARGET: &str = "eth-rpc::runtime_api"; @@ -68,8 +69,14 @@ impl RuntimeApi { pub async fn dry_run( &self, tx: GenericTransaction, - timestamp_override: Option, + block: BlockNumberOrTagOrHash, ) -> Result, ClientError> { + let timestamp_override = match block { + BlockNumberOrTagOrHash::BlockTag(BlockTag::Pending) => { + Some(Timestamp::current().as_millis()) + } + _ => None, + }; let payload = subxt_client::apis().revive_api().eth_transact(tx.into(), timestamp_override); let result = self.0.call(payload).await?; match result { diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index 66d27709bacd8..53fb1a98a14b2 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -24,7 +24,6 @@ use jsonrpsee::{ }; use pallet_revive::evm::*; use sp_core::{keccak_256, H160, H256, U256}; -use sp_timestamp::Timestamp; use thiserror::Error; use tokio::time::Duration; @@ -147,13 +146,9 @@ impl EthRpcServer for EthRpcServerImpl { ) -> RpcResult { log::trace!(target: LOG_TARGET, "estimate_gas transaction={transaction:?} block={block:?}"); let block = block.unwrap_or(BlockNumberOrTag::BlockTag(BlockTag::Pending)); - let timestamp_override = match block { - BlockNumberOrTag::BlockTag(BlockTag::Pending) => Some(Timestamp::current().as_millis()), - _ => None, - }; - let hash = self.client.block_hash_for_tag(block.into()).await?; + let hash = self.client.block_hash_for_tag(block.clone().into()).await?; let runtime_api = self.client.runtime_api(hash); - let dry_run = runtime_api.dry_run(transaction, timestamp_override).await?; + let dry_run = runtime_api.dry_run(transaction, block.into()).await?; log::trace!(target: LOG_TARGET, "estimate_gas result={dry_run:?}"); Ok(dry_run.eth_gas) } @@ -164,14 +159,9 @@ impl EthRpcServer for EthRpcServerImpl { block: Option, ) -> RpcResult { let block = block.unwrap_or_default(); - let timestamp_override = match block { - BlockNumberOrTagOrHash::BlockTag(BlockTag::Pending) => - Some(Timestamp::current().as_millis()), - _ => None, - }; - let hash = self.client.block_hash_for_tag(block).await?; + let hash = self.client.block_hash_for_tag(block.clone()).await?; let runtime_api = self.client.runtime_api(hash); - let dry_run = runtime_api.dry_run(transaction, timestamp_override).await?; + let dry_run = runtime_api.dry_run(transaction, block).await?; Ok(dry_run.data.into()) } From e4a3daec8f7e9c90bb04ffb77eb532bec3182614 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B3nica=20Jin?= Date: Mon, 3 Nov 2025 21:24:51 +0100 Subject: [PATCH 13/32] format with fmt --- substrate/frame/revive/rpc/src/client/runtime_api.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/substrate/frame/revive/rpc/src/client/runtime_api.rs b/substrate/frame/revive/rpc/src/client/runtime_api.rs index 9973e1d13b4df..d9af1ae2514b3 100644 --- a/substrate/frame/revive/rpc/src/client/runtime_api.rs +++ b/substrate/frame/revive/rpc/src/client/runtime_api.rs @@ -22,8 +22,8 @@ use crate::{ }; use pallet_revive::{ evm::{ - decode_revert_reason, Block as EthBlock, BlockTag, BlockNumberOrTagOrHash, GenericTransaction, ReceiptGasInfo, Trace, H160, - U256, + decode_revert_reason, Block as EthBlock, BlockNumberOrTagOrHash, BlockTag, + GenericTransaction, ReceiptGasInfo, Trace, H160, U256, }, EthTransactError, EthTransactInfo, }; @@ -72,9 +72,8 @@ impl RuntimeApi { block: BlockNumberOrTagOrHash, ) -> Result, ClientError> { let timestamp_override = match block { - BlockNumberOrTagOrHash::BlockTag(BlockTag::Pending) => { - Some(Timestamp::current().as_millis()) - } + BlockNumberOrTagOrHash::BlockTag(BlockTag::Pending) => + Some(Timestamp::current().as_millis()), _ => None, }; let payload = subxt_client::apis().revive_api().eth_transact(tx.into(), timestamp_override); From 4b353ed0771cf299f43018960ae6fb47963aa90f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B3nica=20Jin?= Date: Tue, 4 Nov 2025 09:48:08 +0100 Subject: [PATCH 14/32] remove log --- .../frame/revive/rpc/src/client/runtime_api.rs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/substrate/frame/revive/rpc/src/client/runtime_api.rs b/substrate/frame/revive/rpc/src/client/runtime_api.rs index d9af1ae2514b3..e77db4215433f 100644 --- a/substrate/frame/revive/rpc/src/client/runtime_api.rs +++ b/substrate/frame/revive/rpc/src/client/runtime_api.rs @@ -80,18 +80,9 @@ impl RuntimeApi { let result = self.0.call(payload).await?; match result { Err(err) => { - // Attempt to decode revert reason if available - let decoded_msg = match &err.0 { - EthTransactError::Data(data) => match decode_revert_reason(data) { - Some(reason) => format!("execution reverted: {reason}"), - None => "execution reverted".to_string(), - }, - EthTransactError::Message(msg) => msg.clone(), - }; - log::debug!(target: LOG_TARGET, "err = {:?}", err); - log::debug!(target: LOG_TARGET, "Dry run failed {decoded_msg}"); + log::debug!(target: LOG_TARGET, "Dry run failed {err:?}"); Err(ClientError::TransactError(err.0)) - }, + }, Ok(result) => Ok(result.0), } } From fa5d203adf6cc429a517305fdc3e54891ee95577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B3nica=20Jin?= Date: Tue, 4 Nov 2025 11:25:29 +0100 Subject: [PATCH 15/32] remove unused imports --- substrate/frame/revive/rpc/src/client/runtime_api.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/substrate/frame/revive/rpc/src/client/runtime_api.rs b/substrate/frame/revive/rpc/src/client/runtime_api.rs index e77db4215433f..10fd0c4e4854e 100644 --- a/substrate/frame/revive/rpc/src/client/runtime_api.rs +++ b/substrate/frame/revive/rpc/src/client/runtime_api.rs @@ -22,10 +22,10 @@ use crate::{ }; use pallet_revive::{ evm::{ - decode_revert_reason, Block as EthBlock, BlockNumberOrTagOrHash, BlockTag, + Block as EthBlock, BlockNumberOrTagOrHash, BlockTag, GenericTransaction, ReceiptGasInfo, Trace, H160, U256, }, - EthTransactError, EthTransactInfo, + EthTransactInfo, }; use sp_core::H256; use sp_timestamp::Timestamp; From 2fd4af1dffb73c3790b3a0b2dfecff02c3f99ed1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B3nica=20Jin?= Date: Tue, 4 Nov 2025 19:15:41 +0100 Subject: [PATCH 16/32] refactor with_dry_run --- substrate/frame/revive/rpc/src/client/runtime_api.rs | 6 +++--- substrate/frame/revive/src/lib.rs | 6 +++--- substrate/frame/revive/src/primitives.rs | 8 +------- substrate/frame/revive/src/tests/sol/block_info.rs | 6 ++---- 4 files changed, 9 insertions(+), 17 deletions(-) diff --git a/substrate/frame/revive/rpc/src/client/runtime_api.rs b/substrate/frame/revive/rpc/src/client/runtime_api.rs index 10fd0c4e4854e..84841ac6d6247 100644 --- a/substrate/frame/revive/rpc/src/client/runtime_api.rs +++ b/substrate/frame/revive/rpc/src/client/runtime_api.rs @@ -22,8 +22,8 @@ use crate::{ }; use pallet_revive::{ evm::{ - Block as EthBlock, BlockNumberOrTagOrHash, BlockTag, - GenericTransaction, ReceiptGasInfo, Trace, H160, U256, + Block as EthBlock, BlockNumberOrTagOrHash, BlockTag, GenericTransaction, ReceiptGasInfo, + Trace, H160, U256, }, EthTransactInfo, }; @@ -82,7 +82,7 @@ impl RuntimeApi { Err(err) => { log::debug!(target: LOG_TARGET, "Dry run failed {err:?}"); Err(ClientError::TransactError(err.0)) - }, + }, Ok(result) => Ok(result.0), } } diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index a468e8c0c67d7..c2c39d5eb5a07 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -1676,7 +1676,7 @@ impl Pallet { call_info.encoded_len, base_info.total_weight(), ) - .with_dry_run_timestamp_override(timestamp_override) + .with_dry_run(timestamp_override) }; // emulate transaction behavior @@ -2584,7 +2584,7 @@ macro_rules! impl_runtime_apis_plus_revive_traits { gas_limit.unwrap_or(blockweights.max_block), storage_deposit_limit.unwrap_or(u128::MAX), input_data, - $crate::ExecConfig::new_substrate_tx().with_dry_run(), + $crate::ExecConfig::new_substrate_tx().with_dry_run(None), ) } @@ -2610,7 +2610,7 @@ macro_rules! impl_runtime_apis_plus_revive_traits { code, data, salt, - $crate::ExecConfig::new_substrate_tx().with_dry_run(), + $crate::ExecConfig::new_substrate_tx().with_dry_run(None), ) } diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs index 6c4d5ce85d5e3..4f2f4ccea00ab 100644 --- a/substrate/frame/revive/src/primitives.rs +++ b/substrate/frame/revive/src/primitives.rs @@ -395,13 +395,7 @@ impl ExecConfig { } /// Set this config to be a dry-run. - pub fn with_dry_run(mut self) -> Self { - self.is_dry_run = true; - self - } - - /// Set a timestamp override for dry-run executions. - pub fn with_dry_run_timestamp_override(mut self, timestamp: Option) -> Self { + pub fn with_dry_run(mut self, timestamp: Option) -> Self { self.is_dry_run = true; self.dry_run_timestamp_override = timestamp; self diff --git a/substrate/frame/revive/src/tests/sol/block_info.rs b/substrate/frame/revive/src/tests/sol/block_info.rs index fde6babd25ffd..7553acb0baae6 100644 --- a/substrate/frame/revive/src/tests/sol/block_info.rs +++ b/substrate/frame/revive/src/tests/sol/block_info.rs @@ -68,7 +68,7 @@ fn block_number_dry_run_works(fixture_type: FixtureType) { .data( BlockInfo::BlockInfoCalls::blockNumber(BlockInfo::blockNumberCall {}).abi_encode(), ) - .exec_config(ExecConfig::new_substrate_tx().with_dry_run()) + .exec_config(ExecConfig::new_substrate_tx().with_dry_run(None)) .build_and_unwrap_result(); let decoded = BlockInfo::blockNumberCall::abi_decode_returns(&result.data).unwrap(); assert_eq!(43u64, decoded); @@ -147,9 +147,7 @@ fn timestamp_dry_run_override_works(fixture_type: FixtureType) { let override_ts = Timestamp::get() + 10_000; let result: crate::ExecReturnValue = builder::bare_call(addr) .data(BlockInfo::BlockInfoCalls::timestamp(BlockInfo::timestampCall {}).abi_encode()) - .exec_config( - ExecConfig::new_substrate_tx().with_dry_run_timestamp_override(Some(override_ts)), - ) + .exec_config(ExecConfig::new_substrate_tx().with_dry_run(Some(override_ts))) .build_and_unwrap_result(); let decoded = BlockInfo::timestampCall::abi_decode_returns(&result.data).unwrap(); assert_eq!( From d9f24fec8d499f282f86c700a9a11e7f819796b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B3nica=20Jin?= Date: Wed, 5 Nov 2025 11:03:45 +0100 Subject: [PATCH 17/32] fix: increment block iff timestamp_override is provided --- substrate/frame/revive/src/exec.rs | 3 ++- substrate/frame/revive/src/tests/sol/block_info.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 3ea87bf9f3ce9..4b635b5af9b9c 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -953,9 +953,10 @@ where let mut latest_timestamp = T::Time::now(); let mut block_number = >::block_number(); + // if dry run with timestamp override is provided we simulate the run in a `pending` block if exec_config.is_dry_run { - block_number += 1u32.saturated_into(); if let Some(ts_override) = exec_config.dry_run_timestamp_override { + block_number += 1u32.saturated_into(); let moment_override: <::Time as Time>::Moment = ts_override.saturated_into(); let delta: <::Time as Time>::Moment = 1000u64.saturated_into(); diff --git a/substrate/frame/revive/src/tests/sol/block_info.rs b/substrate/frame/revive/src/tests/sol/block_info.rs index 7553acb0baae6..ce11a4889a063 100644 --- a/substrate/frame/revive/src/tests/sol/block_info.rs +++ b/substrate/frame/revive/src/tests/sol/block_info.rs @@ -68,7 +68,7 @@ fn block_number_dry_run_works(fixture_type: FixtureType) { .data( BlockInfo::BlockInfoCalls::blockNumber(BlockInfo::blockNumberCall {}).abi_encode(), ) - .exec_config(ExecConfig::new_substrate_tx().with_dry_run(None)) + .exec_config(ExecConfig::new_substrate_tx().with_dry_run(Some(43))) .build_and_unwrap_result(); let decoded = BlockInfo::blockNumberCall::abi_decode_returns(&result.data).unwrap(); assert_eq!(43u64, decoded); From 1d1170f9c2dc296ab227fb0481d2d2035bf5d212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B3nica=20Jin?= Date: Wed, 5 Nov 2025 12:43:32 +0100 Subject: [PATCH 18/32] refactor: dry_run_override into is_dry_run --- substrate/frame/revive/rpc/src/lib.rs | 2 +- substrate/frame/revive/src/exec.rs | 18 +++++++-------- substrate/frame/revive/src/primitives.rs | 23 ++++++++++--------- .../frame/revive/src/tests/sol/block_info.rs | 3 ++- .../frame/revive/src/tests/sol/contract.rs | 9 +++----- 5 files changed, 27 insertions(+), 28 deletions(-) diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index 53fb1a98a14b2..1e0d9058af366 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -145,7 +145,7 @@ impl EthRpcServer for EthRpcServerImpl { block: Option, ) -> RpcResult { log::trace!(target: LOG_TARGET, "estimate_gas transaction={transaction:?} block={block:?}"); - let block = block.unwrap_or(BlockNumberOrTag::BlockTag(BlockTag::Pending)); + let block = block.unwrap_or_default(); let hash = self.client.block_hash_for_tag(block.clone().into()).await?; let runtime_api = self.client.runtime_api(hash); let dry_run = runtime_api.dry_run(transaction, block.into()).await?; diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 4b635b5af9b9c..bd0b228281d1e 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -954,14 +954,14 @@ where let mut latest_timestamp = T::Time::now(); let mut block_number = >::block_number(); // if dry run with timestamp override is provided we simulate the run in a `pending` block - if exec_config.is_dry_run { - if let Some(ts_override) = exec_config.dry_run_timestamp_override { - block_number += 1u32.saturated_into(); - let moment_override: <::Time as Time>::Moment = - ts_override.saturated_into(); - let delta: <::Time as Time>::Moment = 1000u64.saturated_into(); - latest_timestamp = cmp::max(latest_timestamp + delta, moment_override); - } + if let Some(moment_override) = + exec_config.is_dry_run.as_ref().and_then(|cfg| cfg.timestamp_override) + { + block_number += 1u32.saturated_into(); + let moment_override: <::Time as Time>::Moment = + moment_override.saturated_into(); + let delta: <::Time as Time>::Moment = 1000u64.saturated_into(); + latest_timestamp = cmp::max(latest_timestamp + delta, moment_override); } let stack = Self { @@ -1336,7 +1336,7 @@ where // When a dry-run simulates contract deployment, keep the execution result's // data. let data = if crate::tracing::if_tracing(|_| {}).is_none() && - !self.exec_config.is_dry_run + self.exec_config.is_dry_run.is_none() { core::mem::replace(&mut output.data, Default::default()) } else { diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs index 4f2f4ccea00ab..391ce6d20f99b 100644 --- a/substrate/frame/revive/src/primitives.rs +++ b/substrate/frame/revive/src/primitives.rs @@ -319,6 +319,13 @@ where } } +/// Configuration specific to a dry-run execution. +#[derive(Clone)] +pub struct DryRunConfig { + /// Optional timestamp override for dry-run in pending block. + pub timestamp_override: Option, +} + /// `Stack` wide configuration options. pub struct ExecConfig { /// Indicates whether the account nonce should be incremented after instantiating a new @@ -349,9 +356,7 @@ pub struct ExecConfig { pub effective_gas_price: Option, /// Whether this configuration was created for a dry-run execution. /// Use to enable logic that should only run in dry-run mode. - pub is_dry_run: bool, - /// An optional timestamp override for dry-run executions. - pub dry_run_timestamp_override: Option, + pub is_dry_run: Option, /// An optional mock handler that can be used to override certain behaviors. /// This is primarily used for testing purposes and should be `None` in production /// environments. @@ -365,8 +370,7 @@ impl ExecConfig { bump_nonce: true, collect_deposit_from_hold: None, effective_gas_price: None, - is_dry_run: false, - dry_run_timestamp_override: None, + is_dry_run: None, mock_handler: None, } } @@ -377,8 +381,7 @@ impl ExecConfig { collect_deposit_from_hold: None, effective_gas_price: None, mock_handler: None, - is_dry_run: false, - dry_run_timestamp_override: None, + is_dry_run: None, } } @@ -389,15 +392,13 @@ impl ExecConfig { collect_deposit_from_hold: Some((encoded_len, base_weight)), effective_gas_price: Some(effective_gas_price), mock_handler: None, - is_dry_run: false, - dry_run_timestamp_override: None, + is_dry_run: None, } } /// Set this config to be a dry-run. pub fn with_dry_run(mut self, timestamp: Option) -> Self { - self.is_dry_run = true; - self.dry_run_timestamp_override = timestamp; + self.is_dry_run = Some(DryRunConfig { timestamp_override: timestamp }); self } } diff --git a/substrate/frame/revive/src/tests/sol/block_info.rs b/substrate/frame/revive/src/tests/sol/block_info.rs index ce11a4889a063..9d41cde0981ce 100644 --- a/substrate/frame/revive/src/tests/sol/block_info.rs +++ b/substrate/frame/revive/src/tests/sol/block_info.rs @@ -63,12 +63,13 @@ fn block_number_dry_run_works(fixture_type: FixtureType) { builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); System::set_block_number(42); + let override_ts = Timestamp::get() + 10_000; let result = builder::bare_call(addr) .data( BlockInfo::BlockInfoCalls::blockNumber(BlockInfo::blockNumberCall {}).abi_encode(), ) - .exec_config(ExecConfig::new_substrate_tx().with_dry_run(Some(43))) + .exec_config(ExecConfig::new_substrate_tx().with_dry_run(Some(override_ts))) .build_and_unwrap_result(); let decoded = BlockInfo::blockNumberCall::abi_decode_returns(&result.data).unwrap(); assert_eq!(43u64, decoded); diff --git a/substrate/frame/revive/src/tests/sol/contract.rs b/substrate/frame/revive/src/tests/sol/contract.rs index e5a265b70bcc4..542bd41420425 100644 --- a/substrate/frame/revive/src/tests/sol/contract.rs +++ b/substrate/frame/revive/src/tests/sol/contract.rs @@ -400,8 +400,7 @@ fn mock_caller_hook_works(caller_type: FixtureType, callee_type: FixtureType) { bump_nonce: false, collect_deposit_from_hold: None, effective_gas_price: None, - is_dry_run: false, - dry_run_timestamp_override: None, + is_dry_run: None, mock_handler: Some(Box::new(MockHandlerImpl { mock_caller: Some(BOB_ADDR), mock_call: Default::default(), @@ -454,8 +453,7 @@ fn mock_call_hook_works(caller_type: FixtureType, callee_type: FixtureType) { bump_nonce: false, collect_deposit_from_hold: None, effective_gas_price: None, - is_dry_run: false, - dry_run_timestamp_override: None, + is_dry_run: None, mock_handler: Some(Box::new(MockHandlerImpl { mock_caller: None, mock_call: iter::once(( @@ -515,8 +513,7 @@ fn mock_delegatecall_hook_works(caller_type: FixtureType, callee_type: FixtureTy bump_nonce: false, collect_deposit_from_hold: None, effective_gas_price: None, - is_dry_run: false, - dry_run_timestamp_override: None, + is_dry_run: None, mock_handler: Some(Box::new(MockHandlerImpl { mock_caller: None, mock_call: Default::default(), From aa531f97eb35aacffc9e37bf04491df609c61230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B3nica=20Jin?= Date: Wed, 5 Nov 2025 13:13:50 +0100 Subject: [PATCH 19/32] update: tests-evm hash --- .github/workflows/tests-evm.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests-evm.yml b/.github/workflows/tests-evm.yml index 5b6886b4ebace..9afb8c0bb30cc 100644 --- a/.github/workflows/tests-evm.yml +++ b/.github/workflows/tests-evm.yml @@ -112,7 +112,7 @@ jobs: uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: repository: paritytech/evm-test-suite - ref: 460b2c9aa3a3019d3508bb5a34a2498ea86035ff + ref: 372e9b48d375a63297d6e553a70a89c4e9dfa14e path: evm-test-suite - uses: denoland/setup-deno@v1 From 71ff4fb7b1e6253e6ce9887d4c0f1c0db4d024e7 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 5 Nov 2025 12:44:55 +0000 Subject: [PATCH 20/32] slight adjustments --- substrate/frame/revive/src/evm/runtime.rs | 3 ++- substrate/frame/revive/src/exec.rs | 14 ++++++-------- substrate/frame/revive/src/lib.rs | 4 ++-- substrate/frame/revive/src/primitives.rs | 16 ++++++++-------- .../frame/revive/src/tests/sol/block_info.rs | 16 ++++++++++------ 5 files changed, 28 insertions(+), 25 deletions(-) diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index 604a03b036860..74e6e4b67d267 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -429,7 +429,8 @@ mod test { let account = Account::default(); Self::fund_account(&account); - let dry_run = crate::Pallet::::dry_run_eth_transact(self.tx.clone(), None); + let dry_run = + crate::Pallet::::dry_run_eth_transact(self.tx.clone(), Default::default()); self.tx.gas_price = Some(>::evm_base_fee()); match dry_run { diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index ca8e29545c2b5..d9ddbbe375397 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -951,24 +951,22 @@ where return Ok(None); }; - let mut latest_timestamp = T::Time::now(); + let mut timestamp = T::Time::now(); let mut block_number = >::block_number(); // if dry run with timestamp override is provided we simulate the run in a `pending` block - if let Some(moment_override) = + if let Some(timestamp_override) = exec_config.is_dry_run.as_ref().and_then(|cfg| cfg.timestamp_override) { - block_number += 1u32.saturated_into(); - let moment_override: <::Time as Time>::Moment = - moment_override.saturated_into(); - let delta: <::Time as Time>::Moment = 1000u64.saturated_into(); - latest_timestamp = cmp::max(latest_timestamp + delta, moment_override); + block_number += 1u32.into(); + let delta = 1000u32.into(); + timestamp = cmp::max(timestamp + delta, timestamp_override); } let stack = Self { origin, gas_meter, storage_meter, - timestamp: latest_timestamp, + timestamp, block_number, first_frame, frames: Default::default(), diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index d42c2b177cb0f..6e4d237081ba4 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -1608,7 +1608,7 @@ impl Pallet { /// - `tx`: The Ethereum transaction to simulate. pub fn dry_run_eth_transact( mut tx: GenericTransaction, - timestamp_override: Option, + dry_run_config: DryRunConfig, ) -> Result>, EthTransactError> where T::Nonce: Into, @@ -1673,7 +1673,7 @@ impl Pallet { call_info.encoded_len, base_info.total_weight(), ) - .with_dry_run(timestamp_override) + .with_dry_run(dry_run_config) }; // emulate transaction behavior diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs index 159bd12a88652..00c34fc555537 100644 --- a/substrate/frame/revive/src/primitives.rs +++ b/substrate/frame/revive/src/primitives.rs @@ -17,10 +17,10 @@ //! A crate that hosts a common definitions that are relevant for the pallet-revive. -use crate::{mock::MockHandler, storage::WriteOutcome, BalanceOf, Config, H160, U256}; +use crate::{mock::MockHandler, storage::WriteOutcome, BalanceOf, Config, Time, H160, U256}; use alloc::{boxed::Box, fmt::Debug, string::String, vec::Vec}; use codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::weights::Weight; +use frame_support::{weights::Weight, DefaultNoBound}; use pallet_revive_uapi::ReturnFlags; use scale_info::TypeInfo; use sp_core::Get; @@ -331,10 +331,10 @@ where } /// Configuration specific to a dry-run execution. -#[derive(Clone)] -pub struct DryRunConfig { +#[derive(Clone, DefaultNoBound)] +pub struct DryRunConfig { /// Optional timestamp override for dry-run in pending block. - pub timestamp_override: Option, + pub timestamp_override: Option<<::Time as Time>::Moment>, } /// `Stack` wide configuration options. @@ -367,7 +367,7 @@ pub struct ExecConfig { pub effective_gas_price: Option, /// Whether this configuration was created for a dry-run execution. /// Use to enable logic that should only run in dry-run mode. - pub is_dry_run: Option, + pub is_dry_run: Option>, /// An optional mock handler that can be used to override certain behaviors. /// This is primarily used for testing purposes and should be `None` in production /// environments. @@ -408,8 +408,8 @@ impl ExecConfig { } /// Set this config to be a dry-run. - pub fn with_dry_run(mut self, timestamp: Option) -> Self { - self.is_dry_run = Some(DryRunConfig { timestamp_override: timestamp }); + pub fn with_dry_run(mut self, dry_run_config: DryRunConfig) -> Self { + self.is_dry_run = Some(dry_run_config); self } } diff --git a/substrate/frame/revive/src/tests/sol/block_info.rs b/substrate/frame/revive/src/tests/sol/block_info.rs index 9d41cde0981ce..d741c0902cd44 100644 --- a/substrate/frame/revive/src/tests/sol/block_info.rs +++ b/substrate/frame/revive/src/tests/sol/block_info.rs @@ -21,7 +21,7 @@ use crate::{ test_utils::{builder::Contract, ALICE}, tests::{builder, Contracts, ExtBuilder, System, Test, Timestamp}, vm::evm::DIFFICULTY, - Code, Config, ExecConfig, Pallet, + Code, Config, DryRunConfig, ExecConfig, Pallet, }; use alloy_core::sol_types::{SolCall, SolInterface}; @@ -63,13 +63,15 @@ fn block_number_dry_run_works(fixture_type: FixtureType) { builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); System::set_block_number(42); - let override_ts = Timestamp::get() + 10_000; + let timestamp_override = Some(Timestamp::get() + 10_000); let result = builder::bare_call(addr) .data( BlockInfo::BlockInfoCalls::blockNumber(BlockInfo::blockNumberCall {}).abi_encode(), ) - .exec_config(ExecConfig::new_substrate_tx().with_dry_run(Some(override_ts))) + .exec_config( + ExecConfig::new_substrate_tx().with_dry_run(DryRunConfig { timestamp_override }), + ) .build_and_unwrap_result(); let decoded = BlockInfo::blockNumberCall::abi_decode_returns(&result.data).unwrap(); assert_eq!(43u64, decoded); @@ -145,16 +147,18 @@ fn timestamp_dry_run_override_works(fixture_type: FixtureType) { let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - let override_ts = Timestamp::get() + 10_000; + let timestamp_override = Some(Timestamp::get() + 10_000); let result: crate::ExecReturnValue = builder::bare_call(addr) .data(BlockInfo::BlockInfoCalls::timestamp(BlockInfo::timestampCall {}).abi_encode()) - .exec_config(ExecConfig::new_substrate_tx().with_dry_run(Some(override_ts))) + .exec_config( + ExecConfig::new_substrate_tx().with_dry_run(DryRunConfig { timestamp_override }), + ) .build_and_unwrap_result(); let decoded = BlockInfo::timestampCall::abi_decode_returns(&result.data).unwrap(); assert_eq!( // Solidity expects timestamps in seconds, whereas pallet_timestamp uses // milliseconds. - (override_ts / 1000) as u64, + (timestamp_override.unwrap() / 1000) as u64, decoded ); }); From e1a46e57bee6f70a8b6d419c47b6291271b74136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B3nica=20Jin?= Date: Wed, 5 Nov 2025 14:51:58 +0100 Subject: [PATCH 21/32] fix dry run config --- substrate/frame/revive/src/lib.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 6e4d237081ba4..3016025ce481c 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -2563,7 +2563,10 @@ macro_rules! impl_runtime_apis_plus_revive_traits { sp_runtime::traits::TransactionExtension, sp_runtime::traits::Block as BlockT }; - $crate::Pallet::::dry_run_eth_transact(tx, timestamp) + let dry_run_config = $crate::DryRunConfig:: { + timestamp_override: timestamp, + }; + $crate::Pallet::::dry_run_eth_transact(tx, dry_run_config) } fn call( @@ -2586,7 +2589,9 @@ macro_rules! impl_runtime_apis_plus_revive_traits { gas_limit.unwrap_or(blockweights.max_block), storage_deposit_limit.unwrap_or(u128::MAX), input_data, - $crate::ExecConfig::new_substrate_tx().with_dry_run(None), + $crate::ExecConfig::new_substrate_tx().with_dry_run( + Default::default(), + ), ) } @@ -2612,7 +2617,9 @@ macro_rules! impl_runtime_apis_plus_revive_traits { code, data, salt, - $crate::ExecConfig::new_substrate_tx().with_dry_run(None), + $crate::ExecConfig::new_substrate_tx().with_dry_run( + Default::default(), + ), ) } From de9475c7ac89f425ba3269ef7300bc8680dafdb0 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 5 Nov 2025 14:43:50 +0000 Subject: [PATCH 22/32] fix --- .../frame/revive/dev-node/runtime/src/lib.rs | 3 +- .../revive/rpc/src/client/runtime_api.rs | 5 +- substrate/frame/revive/src/lib.rs | 51 ++++++++++++------- substrate/frame/revive/src/primitives.rs | 13 +++-- 4 files changed, 47 insertions(+), 25 deletions(-) diff --git a/substrate/frame/revive/dev-node/runtime/src/lib.rs b/substrate/frame/revive/dev-node/runtime/src/lib.rs index 43adcb24a04a1..243e4e7ba6a80 100644 --- a/substrate/frame/revive/dev-node/runtime/src/lib.rs +++ b/substrate/frame/revive/dev-node/runtime/src/lib.rs @@ -357,7 +357,8 @@ impl pallet_revive::Config for Runtime { type FeeInfo = FeeInfo; type DebugEnabled = ConstBool; } - +use crate::polkadot_sdk_frame::traits::Time; +type Moment = <::Time as Time>::Moment; pallet_revive::impl_runtime_apis_plus_revive_traits!( Runtime, Revive, diff --git a/substrate/frame/revive/rpc/src/client/runtime_api.rs b/substrate/frame/revive/rpc/src/client/runtime_api.rs index 84841ac6d6247..f51a033e0fdd5 100644 --- a/substrate/frame/revive/rpc/src/client/runtime_api.rs +++ b/substrate/frame/revive/rpc/src/client/runtime_api.rs @@ -71,12 +71,13 @@ impl RuntimeApi { tx: GenericTransaction, block: BlockNumberOrTagOrHash, ) -> Result, ClientError> { - let timestamp_override = match block { + let _timestamp_override = match block { BlockNumberOrTagOrHash::BlockTag(BlockTag::Pending) => Some(Timestamp::current().as_millis()), _ => None, }; - let payload = subxt_client::apis().revive_api().eth_transact(tx.into(), timestamp_override); + // TODO use eth_transact_with_config when available + let payload = subxt_client::apis().revive_api().eth_transact(tx.into()); let result = self.0.call(payload).await?; match result { Err(err) => { diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 3016025ce481c..5f9b30aa51eff 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -74,7 +74,7 @@ use frame_support::{ traits::{ fungible::{Balanced, Inspect, Mutate, MutateHold}, tokens::Balance, - ConstU32, ConstU64, EnsureOrigin, Get, IsSubType, IsType, OriginTrait, Time, + ConstU32, ConstU64, EnsureOrigin, Get, IsSubType, IsType, OriginTrait, }, weights::WeightMeter, BoundedVec, RuntimeDebugNoBound, @@ -100,9 +100,10 @@ pub use crate::{ pallet::{genesis, *}, storage::{AccountInfo, ContractInfo}, vm::{BytecodeType, ContractBlob}, + DryRunConfig, }; pub use codec; -pub use frame_support::{self, dispatch::DispatchInfo, weights::Weight}; +pub use frame_support::{self, dispatch::DispatchInfo, traits::Time, weights::Weight}; pub use frame_system::{self, limits::BlockWeights}; pub use primitives::*; pub use sp_core::{keccak_256, H160, H256, U256}; @@ -1608,7 +1609,7 @@ impl Pallet { /// - `tx`: The Ethereum transaction to simulate. pub fn dry_run_eth_transact( mut tx: GenericTransaction, - dry_run_config: DryRunConfig, + dry_run_config: DryRunConfig<<::Time as Time>::Moment>, ) -> Result>, EthTransactError> where T::Nonce: Into, @@ -2336,11 +2337,12 @@ environmental!(executing_contract: bool); sp_api::decl_runtime_apis! { /// The API used to dry-run contract interactions. #[api_version(1)] - pub trait ReviveApi where + pub trait ReviveApi where AccountId: Codec, Balance: Codec, Nonce: Codec, BlockNumber: Codec, + Moment: Codec, { /// Returns the current ETH block. /// @@ -2397,8 +2399,17 @@ sp_api::decl_runtime_apis! { /// Perform an Ethereum call. /// + /// Deprecated use `v2` version instead. /// See [`crate::Pallet::dry_run_eth_transact`] - fn eth_transact(tx: GenericTransaction, timestamp: Option) -> Result, EthTransactError>; + fn eth_transact(tx: GenericTransaction) -> Result, EthTransactError>; + + /// Perform an Ethereum call. + /// + /// See [`crate::Pallet::dry_run_eth_transact`] + fn eth_transact_with_config( + tx: GenericTransaction, + config: DryRunConfig, + ) -> Result, EthTransactError>; /// Upload new code without instantiating a contract from it. /// @@ -2514,7 +2525,9 @@ macro_rules! impl_runtime_apis_plus_revive_traits { impl_runtime_apis! { $($rest)* - impl pallet_revive::ReviveApi for $Runtime { + + impl pallet_revive::ReviveApi for $Runtime + { fn eth_block() -> $crate::EthBlock { $crate::Pallet::::eth_block() } @@ -2556,17 +2569,25 @@ macro_rules! impl_runtime_apis_plus_revive_traits { fn eth_transact( tx: $crate::evm::GenericTransaction, - timestamp: Option ) -> Result<$crate::EthTransactInfo, $crate::EthTransactError> { use $crate::{ codec::Encode, evm::runtime::EthExtra, frame_support::traits::Get, sp_runtime::traits::TransactionExtension, sp_runtime::traits::Block as BlockT }; - let dry_run_config = $crate::DryRunConfig:: { - timestamp_override: timestamp, + $crate::Pallet::::dry_run_eth_transact(tx, Default::default()) + } + + fn eth_transact_with_config( + tx: $crate::evm::GenericTransaction, + config: $crate::DryRunConfig, + ) -> Result<$crate::EthTransactInfo, $crate::EthTransactError> { + use $crate::{ + codec::Encode, evm::runtime::EthExtra, frame_support::traits::Get, + sp_runtime::traits::TransactionExtension, + sp_runtime::traits::Block as BlockT }; - $crate::Pallet::::dry_run_eth_transact(tx, dry_run_config) + $crate::Pallet::::dry_run_eth_transact(tx, config) } fn call( @@ -2589,9 +2610,7 @@ macro_rules! impl_runtime_apis_plus_revive_traits { gas_limit.unwrap_or(blockweights.max_block), storage_deposit_limit.unwrap_or(u128::MAX), input_data, - $crate::ExecConfig::new_substrate_tx().with_dry_run( - Default::default(), - ), + $crate::ExecConfig::new_substrate_tx().with_dry_run(Default::default()), ) } @@ -2617,9 +2636,7 @@ macro_rules! impl_runtime_apis_plus_revive_traits { code, data, salt, - $crate::ExecConfig::new_substrate_tx().with_dry_run( - Default::default(), - ), + $crate::ExecConfig::new_substrate_tx().with_dry_run(Default::default()), ) } @@ -2703,7 +2720,7 @@ macro_rules! impl_runtime_apis_plus_revive_traits { t.watch_address(&tx.from.unwrap_or_default()); t.watch_address(&$crate::Pallet::::block_author()); - let result = trace(t, || Self::eth_transact(tx, None)); + let result = trace(t, || Self::eth_transact(tx)); if let Some(trace) = tracer.collect_trace() { Ok(trace) diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs index 00c34fc555537..deb8dfefac149 100644 --- a/substrate/frame/revive/src/primitives.rs +++ b/substrate/frame/revive/src/primitives.rs @@ -331,10 +331,10 @@ where } /// Configuration specific to a dry-run execution. -#[derive(Clone, DefaultNoBound)] -pub struct DryRunConfig { +#[derive(Encode, Decode, TypeInfo, Clone, DefaultNoBound)] +pub struct DryRunConfig { /// Optional timestamp override for dry-run in pending block. - pub timestamp_override: Option<<::Time as Time>::Moment>, + pub timestamp_override: Option, } /// `Stack` wide configuration options. @@ -367,7 +367,7 @@ pub struct ExecConfig { pub effective_gas_price: Option, /// Whether this configuration was created for a dry-run execution. /// Use to enable logic that should only run in dry-run mode. - pub is_dry_run: Option>, + pub is_dry_run: Option::Time as Time>::Moment>>, /// An optional mock handler that can be used to override certain behaviors. /// This is primarily used for testing purposes and should be `None` in production /// environments. @@ -408,7 +408,10 @@ impl ExecConfig { } /// Set this config to be a dry-run. - pub fn with_dry_run(mut self, dry_run_config: DryRunConfig) -> Self { + pub fn with_dry_run( + mut self, + dry_run_config: DryRunConfig<<::Time as Time>::Moment>, + ) -> Self { self.is_dry_run = Some(dry_run_config); self } From 7b7765998acafc06507d3770b0b7277c18e5a662 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 5 Nov 2025 16:06:30 +0100 Subject: [PATCH 23/32] move things around --- .../frame/revive/rpc/src/client/runtime_api.rs | 12 ++++++++---- substrate/frame/revive/rpc/src/subxt_client.rs | 4 ++++ substrate/frame/revive/src/evm/api.rs | 2 ++ substrate/frame/revive/src/evm/api/rpc_types.rs | 10 ++++++++++ substrate/frame/revive/src/lib.rs | 6 ++++-- substrate/frame/revive/src/primitives.rs | 14 +++++--------- 6 files changed, 33 insertions(+), 15 deletions(-) diff --git a/substrate/frame/revive/rpc/src/client/runtime_api.rs b/substrate/frame/revive/rpc/src/client/runtime_api.rs index f51a033e0fdd5..5702565910c66 100644 --- a/substrate/frame/revive/rpc/src/client/runtime_api.rs +++ b/substrate/frame/revive/rpc/src/client/runtime_api.rs @@ -25,7 +25,7 @@ use pallet_revive::{ Block as EthBlock, BlockNumberOrTagOrHash, BlockTag, GenericTransaction, ReceiptGasInfo, Trace, H160, U256, }, - EthTransactInfo, + DryRunConfig, EthTransactInfo, }; use sp_core::H256; use sp_timestamp::Timestamp; @@ -71,13 +71,17 @@ impl RuntimeApi { tx: GenericTransaction, block: BlockNumberOrTagOrHash, ) -> Result, ClientError> { - let _timestamp_override = match block { + let timestamp_override = match block { BlockNumberOrTagOrHash::BlockTag(BlockTag::Pending) => Some(Timestamp::current().as_millis()), _ => None, }; - // TODO use eth_transact_with_config when available - let payload = subxt_client::apis().revive_api().eth_transact(tx.into()); + + // TODO fallback to eth_transact when eth_transact_with_config not available + let payload = subxt_client::apis() + .revive_api() + .eth_transact_with_config(tx.into(), DryRunConfig { timestamp_override }.into()); + // let payload = subxt_client::apis().revive_api().eth_transact(tx.into()); let result = self.0.call(payload).await?; match result { Err(err) => { diff --git a/substrate/frame/revive/rpc/src/subxt_client.rs b/substrate/frame/revive/rpc/src/subxt_client.rs index 3daa45df36b32..bd9666aba8b3e 100644 --- a/substrate/frame/revive/rpc/src/subxt_client.rs +++ b/substrate/frame/revive/rpc/src/subxt_client.rs @@ -47,6 +47,10 @@ pub use subxt::config::PolkadotConfig as SrcChainConfig; path = "pallet_revive::evm::api::rpc_types_gen::GenericTransaction", with = "::subxt::utils::Static<::pallet_revive::evm::GenericTransaction>" ), + substitute_type( + path = "pallet_revive::evm::api::rpc_types::DryRunConfig", + with = "::subxt::utils::Static<::pallet_revive::evm::DryRunConfig>" + ), substitute_type( path = "pallet_revive::primitives::EthTransactInfo", with = "::subxt::utils::Static<::pallet_revive::EthTransactInfo>" diff --git a/substrate/frame/revive/src/evm/api.rs b/substrate/frame/revive/src/evm/api.rs index 7a34fdc83f9a5..95cfcdda0e835 100644 --- a/substrate/frame/revive/src/evm/api.rs +++ b/substrate/frame/revive/src/evm/api.rs @@ -31,6 +31,8 @@ mod debug_rpc_types; pub use debug_rpc_types::*; mod rpc_types; +pub use rpc_types::DryRunConfig; + mod rpc_types_gen; pub use rpc_types_gen::*; diff --git a/substrate/frame/revive/src/evm/api/rpc_types.rs b/substrate/frame/revive/src/evm/api/rpc_types.rs index 73681573107cf..c54138f1c3195 100644 --- a/substrate/frame/revive/src/evm/api/rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/rpc_types.rs @@ -17,8 +17,18 @@ //! Utility impl for the RPC types. use super::*; use alloc::vec::Vec; +use codec::{Decode, Encode}; +use frame_support::DefaultNoBound; +use scale_info::TypeInfo; use sp_core::{H160, U256}; +/// Configuration specific to a dry-run execution. +#[derive(Debug, Encode, Decode, TypeInfo, Clone, DefaultNoBound)] +pub struct DryRunConfig { + /// Optional timestamp override for dry-run in pending block. + pub timestamp_override: Option, +} + impl From for BlockNumberOrTagOrHash { fn from(b: BlockNumberOrTag) -> Self { match b { diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 5f9b30aa51eff..6eec6b00a1c56 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -95,12 +95,14 @@ pub use crate::{ create1, create2, is_eth_derived, AccountId32Mapper, AddressMapper, TestAccountMapper, }, debug::DebugSettings, - evm::{block_hash::ReceiptGasInfo, Address as EthAddress, Block as EthBlock, ReceiptInfo}, + evm::{ + block_hash::ReceiptGasInfo, Address as EthAddress, Block as EthBlock, DryRunConfig, + ReceiptInfo, + }, exec::{DelegateInfo, Executable, Key, MomentOf, Origin as ExecOrigin}, pallet::{genesis, *}, storage::{AccountInfo, ContractInfo}, vm::{BytecodeType, ContractBlob}, - DryRunConfig, }; pub use codec; pub use frame_support::{self, dispatch::DispatchInfo, traits::Time, weights::Weight}; diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs index deb8dfefac149..3f96e4495617c 100644 --- a/substrate/frame/revive/src/primitives.rs +++ b/substrate/frame/revive/src/primitives.rs @@ -17,10 +17,13 @@ //! A crate that hosts a common definitions that are relevant for the pallet-revive. -use crate::{mock::MockHandler, storage::WriteOutcome, BalanceOf, Config, Time, H160, U256}; +use crate::{ + evm::DryRunConfig, mock::MockHandler, storage::WriteOutcome, BalanceOf, Config, Time, H160, + U256, +}; use alloc::{boxed::Box, fmt::Debug, string::String, vec::Vec}; use codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::{weights::Weight, DefaultNoBound}; +use frame_support::weights::Weight; use pallet_revive_uapi::ReturnFlags; use scale_info::TypeInfo; use sp_core::Get; @@ -330,13 +333,6 @@ where } } -/// Configuration specific to a dry-run execution. -#[derive(Encode, Decode, TypeInfo, Clone, DefaultNoBound)] -pub struct DryRunConfig { - /// Optional timestamp override for dry-run in pending block. - pub timestamp_override: Option, -} - /// `Stack` wide configuration options. pub struct ExecConfig { /// Indicates whether the account nonce should be incremented after instantiating a new From 2e6c18643aefae228d24ba4ab6d7f89085eaff4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B3nica=20Jin?= Date: Wed, 5 Nov 2025 16:52:24 +0100 Subject: [PATCH 24/32] add: fallback to eth_transact if eth_transact_config not available --- .../revive/rpc/src/client/runtime_api.rs | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/substrate/frame/revive/rpc/src/client/runtime_api.rs b/substrate/frame/revive/rpc/src/client/runtime_api.rs index 5702565910c66..b9c8cc586a822 100644 --- a/substrate/frame/revive/rpc/src/client/runtime_api.rs +++ b/substrate/frame/revive/rpc/src/client/runtime_api.rs @@ -77,11 +77,27 @@ impl RuntimeApi { _ => None, }; - // TODO fallback to eth_transact when eth_transact_with_config not available - let payload = subxt_client::apis() - .revive_api() - .eth_transact_with_config(tx.into(), DryRunConfig { timestamp_override }.into()); - // let payload = subxt_client::apis().revive_api().eth_transact(tx.into()); + let payload = subxt_client::apis().revive_api().eth_transact_with_config( + tx.clone().into(), + DryRunConfig { timestamp_override }.into(), + ); + + // check if eth_transact_with_config is available + if let Err(err) = self.0.validate(&payload) { + log::debug!(target: LOG_TARGET, "Validate failed {err:?}"); + // Fallback to eth_transact if validation fails + let payload = subxt_client::apis().revive_api().eth_transact(tx.into()); + let result = self.0.call(payload).await?; + return match result { + Err(err) => { + log::debug!(target: LOG_TARGET, "Dry run failed {err:?}"); + Err(ClientError::TransactError(err.0)) + }, + Ok(result) => Ok(result.0), + }; + } + + // Validation passed, call dry run let result = self.0.call(payload).await?; match result { Err(err) => { From fd0746faceeb3cc70b97938d7492820bb8065c38 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 5 Nov 2025 17:02:59 +0100 Subject: [PATCH 25/32] fix --- .../revive/rpc/src/client/runtime_api.rs | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/substrate/frame/revive/rpc/src/client/runtime_api.rs b/substrate/frame/revive/rpc/src/client/runtime_api.rs index b9c8cc586a822..754d39ab2ef3c 100644 --- a/substrate/frame/revive/rpc/src/client/runtime_api.rs +++ b/substrate/frame/revive/rpc/src/client/runtime_api.rs @@ -81,24 +81,15 @@ impl RuntimeApi { tx.clone().into(), DryRunConfig { timestamp_override }.into(), ); + let result = match self.0.validate(&payload) { + Ok(_) => self.0.call(payload).await?, + Err(err) => { + log::debug!(target: LOG_TARGET, "Dry run payload validation failed {err:?}, falling back to eth_transact"); + let payload = subxt_client::apis().revive_api().eth_transact(tx.into()); + self.0.call(payload).await? + }, + }; - // check if eth_transact_with_config is available - if let Err(err) = self.0.validate(&payload) { - log::debug!(target: LOG_TARGET, "Validate failed {err:?}"); - // Fallback to eth_transact if validation fails - let payload = subxt_client::apis().revive_api().eth_transact(tx.into()); - let result = self.0.call(payload).await?; - return match result { - Err(err) => { - log::debug!(target: LOG_TARGET, "Dry run failed {err:?}"); - Err(ClientError::TransactError(err.0)) - }, - Ok(result) => Ok(result.0), - }; - } - - // Validation passed, call dry run - let result = self.0.call(payload).await?; match result { Err(err) => { log::debug!(target: LOG_TARGET, "Dry run failed {err:?}"); From 68bb7ff6e5799d50d7b9308d803373a298ca5f8f Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Wed, 5 Nov 2025 17:07:14 +0100 Subject: [PATCH 26/32] Update substrate/frame/revive/src/evm/api/rpc_types.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alexander Theißen --- substrate/frame/revive/src/evm/api/rpc_types.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/substrate/frame/revive/src/evm/api/rpc_types.rs b/substrate/frame/revive/src/evm/api/rpc_types.rs index c54138f1c3195..fb810b75c3873 100644 --- a/substrate/frame/revive/src/evm/api/rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/rpc_types.rs @@ -27,6 +27,8 @@ use sp_core::{H160, U256}; pub struct DryRunConfig { /// Optional timestamp override for dry-run in pending block. pub timestamp_override: Option, + /// Used for future extensions without breaking encoding. + pub reserved: Option<()>, } impl From for BlockNumberOrTagOrHash { From 73c7bcfb133a61375c8dde7bd2a95322ff56521a Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 5 Nov 2025 17:12:54 +0100 Subject: [PATCH 27/32] fix clippy --- substrate/frame/revive/src/tests/sol/block_info.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/substrate/frame/revive/src/tests/sol/block_info.rs b/substrate/frame/revive/src/tests/sol/block_info.rs index d741c0902cd44..74856aa28e77b 100644 --- a/substrate/frame/revive/src/tests/sol/block_info.rs +++ b/substrate/frame/revive/src/tests/sol/block_info.rs @@ -147,18 +147,19 @@ fn timestamp_dry_run_override_works(fixture_type: FixtureType) { let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - let timestamp_override = Some(Timestamp::get() + 10_000); + let timestamp_override = Timestamp::get() + 10_000; let result: crate::ExecReturnValue = builder::bare_call(addr) .data(BlockInfo::BlockInfoCalls::timestamp(BlockInfo::timestampCall {}).abi_encode()) .exec_config( - ExecConfig::new_substrate_tx().with_dry_run(DryRunConfig { timestamp_override }), + ExecConfig::new_substrate_tx() + .with_dry_run(DryRunConfig { timestamp_override: Some(timestamp_override) }), ) .build_and_unwrap_result(); let decoded = BlockInfo::timestampCall::abi_decode_returns(&result.data).unwrap(); assert_eq!( // Solidity expects timestamps in seconds, whereas pallet_timestamp uses // milliseconds. - (timestamp_override.unwrap() / 1000) as u64, + (timestamp_override / 1000) as u64, decoded ); }); From 239edb316bb65f57b08282fa1702507aba714eb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B3nica=20Jin?= Date: Wed, 5 Nov 2025 17:56:59 +0100 Subject: [PATCH 28/32] refactor to saturating add & add DryRunConfig constructor --- substrate/frame/revive/rpc/src/client/runtime_api.rs | 2 +- substrate/frame/revive/src/evm/api/rpc_types.rs | 9 +++++++++ substrate/frame/revive/src/exec.rs | 5 +++-- substrate/frame/revive/src/tests/sol/block_info.rs | 4 ++-- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/substrate/frame/revive/rpc/src/client/runtime_api.rs b/substrate/frame/revive/rpc/src/client/runtime_api.rs index 754d39ab2ef3c..db6bb9efa333d 100644 --- a/substrate/frame/revive/rpc/src/client/runtime_api.rs +++ b/substrate/frame/revive/rpc/src/client/runtime_api.rs @@ -79,7 +79,7 @@ impl RuntimeApi { let payload = subxt_client::apis().revive_api().eth_transact_with_config( tx.clone().into(), - DryRunConfig { timestamp_override }.into(), + DryRunConfig::new(timestamp_override).into(), ); let result = match self.0.validate(&payload) { Ok(_) => self.0.call(payload).await?, diff --git a/substrate/frame/revive/src/evm/api/rpc_types.rs b/substrate/frame/revive/src/evm/api/rpc_types.rs index fb810b75c3873..aa2dd04defcdf 100644 --- a/substrate/frame/revive/src/evm/api/rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/rpc_types.rs @@ -30,6 +30,15 @@ pub struct DryRunConfig { /// Used for future extensions without breaking encoding. pub reserved: Option<()>, } +impl DryRunConfig { + /// Create a new `DryRunConfig` with an optional timestamp override. + pub fn new(timestamp_override: Option) -> Self { + Self { + timestamp_override, + reserved: None, // default value + } + } +} impl From for BlockNumberOrTagOrHash { fn from(b: BlockNumberOrTag) -> Self { diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index d9ddbbe375397..617c45ec2b099 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -957,9 +957,10 @@ where if let Some(timestamp_override) = exec_config.is_dry_run.as_ref().and_then(|cfg| cfg.timestamp_override) { - block_number += 1u32.into(); + block_number = block_number.saturating_add(1u32.into()); + // Delta is in milliseconds; increment timestamp by one second let delta = 1000u32.into(); - timestamp = cmp::max(timestamp + delta, timestamp_override); + timestamp = cmp::max(timestamp.saturating_add(delta), timestamp_override); } let stack = Self { diff --git a/substrate/frame/revive/src/tests/sol/block_info.rs b/substrate/frame/revive/src/tests/sol/block_info.rs index 74856aa28e77b..628010044f5f3 100644 --- a/substrate/frame/revive/src/tests/sol/block_info.rs +++ b/substrate/frame/revive/src/tests/sol/block_info.rs @@ -70,7 +70,7 @@ fn block_number_dry_run_works(fixture_type: FixtureType) { BlockInfo::BlockInfoCalls::blockNumber(BlockInfo::blockNumberCall {}).abi_encode(), ) .exec_config( - ExecConfig::new_substrate_tx().with_dry_run(DryRunConfig { timestamp_override }), + ExecConfig::new_substrate_tx().with_dry_run(DryRunConfig::new(timestamp_override)), ) .build_and_unwrap_result(); let decoded = BlockInfo::blockNumberCall::abi_decode_returns(&result.data).unwrap(); @@ -152,7 +152,7 @@ fn timestamp_dry_run_override_works(fixture_type: FixtureType) { .data(BlockInfo::BlockInfoCalls::timestamp(BlockInfo::timestampCall {}).abi_encode()) .exec_config( ExecConfig::new_substrate_tx() - .with_dry_run(DryRunConfig { timestamp_override: Some(timestamp_override) }), + .with_dry_run(DryRunConfig::new(Some(timestamp_override))), ) .build_and_unwrap_result(); let decoded = BlockInfo::timestampCall::abi_decode_returns(&result.data).unwrap(); From 056c19d2beddd95bac92b6c0db4427d085606ed3 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 5 Nov 2025 20:43:28 +0100 Subject: [PATCH 29/32] apply suggestion --- .../revive/rpc/src/client/runtime_api.rs | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/substrate/frame/revive/rpc/src/client/runtime_api.rs b/substrate/frame/revive/rpc/src/client/runtime_api.rs index db6bb9efa333d..f2c41bd9e1474 100644 --- a/substrate/frame/revive/rpc/src/client/runtime_api.rs +++ b/substrate/frame/revive/rpc/src/client/runtime_api.rs @@ -20,6 +20,7 @@ use crate::{ subxt_client::{self, SrcChainConfig}, ClientError, }; +use futures::TryFutureExt; use pallet_revive::{ evm::{ Block as EthBlock, BlockNumberOrTagOrHash, BlockTag, GenericTransaction, ReceiptGasInfo, @@ -29,7 +30,7 @@ use pallet_revive::{ }; use sp_core::H256; use sp_timestamp::Timestamp; -use subxt::OnlineClient; +use subxt::{error::MetadataError, Error::Metadata, OnlineClient}; const LOG_TARGET: &str = "eth-rpc::runtime_api"; @@ -77,18 +78,28 @@ impl RuntimeApi { _ => None, }; - let payload = subxt_client::apis().revive_api().eth_transact_with_config( - tx.clone().into(), - DryRunConfig::new(timestamp_override).into(), - ); - let result = match self.0.validate(&payload) { - Ok(_) => self.0.call(payload).await?, - Err(err) => { - log::debug!(target: LOG_TARGET, "Dry run payload validation failed {err:?}, falling back to eth_transact"); - let payload = subxt_client::apis().revive_api().eth_transact(tx.into()); - self.0.call(payload).await? - }, - }; + let payload = subxt_client::apis() + .revive_api() + .eth_transact_with_config( + tx.clone().into(), + DryRunConfig::new(timestamp_override).into(), + ) + .unvalidated(); + + let result = self + .0 + .call(payload) + .or_else(|err| async { + match err { + Metadata(MetadataError::RuntimeMethodNotFound(_)) => { + log::debug!(target: LOG_TARGET, "Method not found falling back to eth_transact"); + let payload = subxt_client::apis().revive_api().eth_transact(tx.into()); + self.0.call(payload).await + }, + e => Err(e), + } + }) + .await?; match result { Err(err) => { From d5b5a0bda571d27fb026521c740ef693833707d8 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 5 Nov 2025 20:48:39 +0100 Subject: [PATCH 30/32] add name to print --- substrate/frame/revive/rpc/src/client/runtime_api.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/substrate/frame/revive/rpc/src/client/runtime_api.rs b/substrate/frame/revive/rpc/src/client/runtime_api.rs index f2c41bd9e1474..a100ff07a5b0d 100644 --- a/substrate/frame/revive/rpc/src/client/runtime_api.rs +++ b/substrate/frame/revive/rpc/src/client/runtime_api.rs @@ -91,8 +91,8 @@ impl RuntimeApi { .call(payload) .or_else(|err| async { match err { - Metadata(MetadataError::RuntimeMethodNotFound(_)) => { - log::debug!(target: LOG_TARGET, "Method not found falling back to eth_transact"); + Metadata(MetadataError::RuntimeMethodNotFound(name)) => { + log::debug!(target: LOG_TARGET, "Method {name:?} not found falling back to eth_transact"); let payload = subxt_client::apis().revive_api().eth_transact(tx.into()); self.0.call(payload).await }, From 906f12e6c222895e6534a0388455683867e5bfd1 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 5 Nov 2025 21:49:09 +0000 Subject: [PATCH 31/32] fix --- substrate/frame/revive/dev-node/runtime/src/lib.rs | 3 +-- substrate/frame/revive/src/lib.rs | 6 ++++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/substrate/frame/revive/dev-node/runtime/src/lib.rs b/substrate/frame/revive/dev-node/runtime/src/lib.rs index 243e4e7ba6a80..43adcb24a04a1 100644 --- a/substrate/frame/revive/dev-node/runtime/src/lib.rs +++ b/substrate/frame/revive/dev-node/runtime/src/lib.rs @@ -357,8 +357,7 @@ impl pallet_revive::Config for Runtime { type FeeInfo = FeeInfo; type DebugEnabled = ConstBool; } -use crate::polkadot_sdk_frame::traits::Time; -type Moment = <::Time as Time>::Moment; + pallet_revive::impl_runtime_apis_plus_revive_traits!( Runtime, Revive, diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 6eec6b00a1c56..fc2be4aac0b7e 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -2507,6 +2507,8 @@ sp_api::decl_runtime_apis! { macro_rules! impl_runtime_apis_plus_revive_traits { ($Runtime: ty, $Revive: ident, $Executive: ty, $EthExtra: ty, $($rest:tt)*) => { + type __ReviveMacroMoment = <<$Runtime as $crate::Config>::Time as $crate::Time>::Moment; + impl $crate::evm::runtime::SetWeightLimit for RuntimeCall { fn set_weight_limit(&mut self, weight_limit: Weight) -> Weight { use $crate::pallet::Call as ReviveCall; @@ -2528,7 +2530,7 @@ macro_rules! impl_runtime_apis_plus_revive_traits { $($rest)* - impl pallet_revive::ReviveApi for $Runtime + impl pallet_revive::ReviveApi for $Runtime { fn eth_block() -> $crate::EthBlock { $crate::Pallet::::eth_block() @@ -2582,7 +2584,7 @@ macro_rules! impl_runtime_apis_plus_revive_traits { fn eth_transact_with_config( tx: $crate::evm::GenericTransaction, - config: $crate::DryRunConfig, + config: $crate::DryRunConfig<__ReviveMacroMoment>, ) -> Result<$crate::EthTransactInfo, $crate::EthTransactError> { use $crate::{ codec::Encode, evm::runtime::EthExtra, frame_support::traits::Get, From 82c57140d818a48cef2a194649dc865a82b4a436 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Thu, 6 Nov 2025 11:59:02 +0100 Subject: [PATCH 32/32] address historical block use case --- .../frame/revive/rpc/src/client/runtime_api.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/substrate/frame/revive/rpc/src/client/runtime_api.rs b/substrate/frame/revive/rpc/src/client/runtime_api.rs index a100ff07a5b0d..20a787c4d0479 100644 --- a/substrate/frame/revive/rpc/src/client/runtime_api.rs +++ b/substrate/frame/revive/rpc/src/client/runtime_api.rs @@ -30,7 +30,7 @@ use pallet_revive::{ }; use sp_core::H256; use sp_timestamp::Timestamp; -use subxt::{error::MetadataError, Error::Metadata, OnlineClient}; +use subxt::{error::MetadataError, ext::subxt_rpcs::UserError, Error::Metadata, OnlineClient}; const LOG_TARGET: &str = "eth-rpc::runtime_api"; @@ -91,11 +91,22 @@ impl RuntimeApi { .call(payload) .or_else(|err| async { match err { + // This will be hit if subxt metadata (subxt uses the latest finalized block + // metadata when the eth-rpc starts) does not contain the new method Metadata(MetadataError::RuntimeMethodNotFound(name)) => { log::debug!(target: LOG_TARGET, "Method {name:?} not found falling back to eth_transact"); let payload = subxt_client::apis().revive_api().eth_transact(tx.into()); self.0.call(payload).await }, + // This will be hit if we are trying to hit a block where the runtime did not + // have this new runtime `eth_transact_with_config` defined + subxt::Error::Rpc(subxt::error::RpcError::ClientError( + subxt::ext::subxt_rpcs::Error::User(UserError { message, .. }), + )) if message.contains("eth_transact_with_config is not found") => { + log::debug!(target: LOG_TARGET, "{message:?} not found falling back to eth_transact"); + let payload = subxt_client::apis().revive_api().eth_transact(tx.into()); + self.0.call(payload).await + }, e => Err(e), } })