Skip to content
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
da1baf8
add eth_transact_at_time
mokita-j Oct 31, 2025
bd927ad
add: local timestamp in estimate_gas
mokita-j Oct 31, 2025
b75f9ff
refactor: merge eth_transact_at_time into eth_transact
mokita-j Oct 31, 2025
c4ea78f
change estimate_gas block default to pending
mokita-j Oct 31, 2025
de1a294
add: timestamp to eth_call
mokita-j Oct 31, 2025
3ec16d5
refactor timestamp source & fix tests
mokita-j Nov 3, 2025
ae99522
remove redundant logs and comments
mokita-j Nov 3, 2025
0db67ae
add: dry_run timestamp override tests
mokita-j Nov 3, 2025
8d39a29
Update from github-actions[bot] running command 'prdoc --audience run…
github-actions[bot] Nov 3, 2025
fa6888b
format with fmt
mokita-j Nov 3, 2025
0b9825f
fix prdoc bump
mokita-j Nov 3, 2025
e107143
refactor dry_run api
mokita-j Nov 3, 2025
e4a3dae
format with fmt
mokita-j Nov 3, 2025
4b353ed
remove log
mokita-j Nov 4, 2025
fa5d203
remove unused imports
mokita-j Nov 4, 2025
2fd4af1
refactor with_dry_run
mokita-j Nov 4, 2025
d9f24fe
fix: increment block iff timestamp_override is provided
mokita-j Nov 5, 2025
1d1170f
refactor: dry_run_override into is_dry_run
mokita-j Nov 5, 2025
8ca05c5
Merge remote-tracking branch 'origin/master' into mj/fix-dry-run-time…
mokita-j Nov 5, 2025
aa531f9
update: tests-evm hash
mokita-j Nov 5, 2025
71ff4fb
slight adjustments
pgherveou Nov 5, 2025
e1a46e5
fix dry run config
mokita-j Nov 5, 2025
de9475c
fix
pgherveou Nov 5, 2025
7b77659
move things around
pgherveou Nov 5, 2025
2e6c186
add: fallback to eth_transact if eth_transact_config not available
mokita-j Nov 5, 2025
fd0746f
fix
pgherveou Nov 5, 2025
68bb7ff
Update substrate/frame/revive/src/evm/api/rpc_types.rs
pgherveou Nov 5, 2025
73c7bcf
fix clippy
pgherveou Nov 5, 2025
239edb3
refactor to saturating add & add DryRunConfig constructor
mokita-j Nov 5, 2025
056c19d
apply suggestion
pgherveou Nov 5, 2025
d5b5a0b
add name to print
pgherveou Nov 5, 2025
906f12e
fix
pgherveou Nov 5, 2025
82c5714
address historical block use case
pgherveou Nov 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/tests-evm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions prdoc/pr_10191.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
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: major
- name: pallet-revive
bump: major
3 changes: 2 additions & 1 deletion substrate/frame/revive/dev-node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,8 @@ impl pallet_revive::Config for Runtime {
type FeeInfo = FeeInfo<Address, Signature, EthExtraImpl>;
type DebugEnabled = ConstBool<false>;
}

use crate::polkadot_sdk_frame::traits::Time;
type Moment = <<Runtime as pallet_revive::Config>::Time as Time>::Moment;
pallet_revive::impl_runtime_apis_plus_revive_traits!(
Runtime,
Revive,
Expand Down
1 change: 1 addition & 0 deletions substrate/frame/revive/rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
37 changes: 34 additions & 3 deletions substrate/frame/revive/rpc/src/client/runtime_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@ use crate::{
ClientError,
};
use pallet_revive::{
evm::{Block as EthBlock, GenericTransaction, ReceiptGasInfo, Trace, H160, U256},
EthTransactInfo,
evm::{
Block as EthBlock, BlockNumberOrTagOrHash, BlockTag, GenericTransaction, ReceiptGasInfo,
Trace, H160, U256,
},
DryRunConfig, EthTransactInfo,
};
use sp_core::H256;
use sp_timestamp::Timestamp;
use subxt::OnlineClient;

const LOG_TARGET: &str = "eth-rpc::runtime_api";
Expand Down Expand Up @@ -65,8 +69,35 @@ impl RuntimeApi {
pub async fn dry_run(
&self,
tx: GenericTransaction,
block: BlockNumberOrTagOrHash,
) -> Result<EthTransactInfo<Balance>, ClientError> {
let payload = subxt_client::apis().revive_api().eth_transact(tx.into());
let timestamp_override = match block {
BlockNumberOrTagOrHash::BlockTag(BlockTag::Pending) =>
Some(Timestamp::current().as_millis()),
_ => None,
};

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) {
Comment thread
pgherveou marked this conversation as resolved.
Outdated
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) => {
Expand Down
10 changes: 6 additions & 4 deletions substrate/frame/revive/rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,10 @@ impl EthRpcServer for EthRpcServerImpl {
block: Option<BlockNumberOrTag>,
) -> RpcResult<U256> {
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 block = block.unwrap_or_default();

@athei athei Nov 5, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK this defaults to latest. But estimate_gas should default to pending. call should continue to default to latest.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PG and I tried this test here and it seems that geth is defaulting to latest

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).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)
}
Expand All @@ -157,9 +158,10 @@ impl EthRpcServer for EthRpcServerImpl {
transaction: GenericTransaction,
block: Option<BlockNumberOrTagOrHash>,
) -> RpcResult<Bytes> {
let hash = self.client.block_hash_for_tag(block.unwrap_or_default()).await?;
let block = block.unwrap_or_default();
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).await?;
let dry_run = runtime_api.dry_run(transaction, block).await?;
Ok(dry_run.data.into())
}

Expand Down
4 changes: 4 additions & 0 deletions substrate/frame/revive/rpc/src/subxt_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<M>",
with = "::subxt::utils::Static<::pallet_revive::evm::DryRunConfig<M>>"
),
substitute_type(
path = "pallet_revive::primitives::EthTransactInfo<B>",
with = "::subxt::utils::Static<::pallet_revive::EthTransactInfo<B>>"
Expand Down
2 changes: 2 additions & 0 deletions substrate/frame/revive/src/evm/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;

Expand Down
10 changes: 10 additions & 0 deletions substrate/frame/revive/src/evm/api/rpc_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Moment> {
/// Optional timestamp override for dry-run in pending block.
pub timestamp_override: Option<Moment>,
Comment thread
pgherveou marked this conversation as resolved.
}

impl From<BlockNumberOrTag> for BlockNumberOrTagOrHash {
fn from(b: BlockNumberOrTag) -> Self {
match b {
Expand Down
3 changes: 2 additions & 1 deletion substrate/frame/revive/src/evm/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,8 @@ mod test {
let account = Account::default();
Self::fund_account(&account);

let dry_run = crate::Pallet::<Test>::dry_run_eth_transact(self.tx.clone());
let dry_run =
crate::Pallet::<Test>::dry_run_eth_transact(self.tx.clone(), Default::default());
self.tx.gas_price = Some(<Pallet<Test>>::evm_base_fee());

match dry_run {
Expand Down
21 changes: 15 additions & 6 deletions substrate/frame/revive/src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,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,
Expand Down Expand Up @@ -951,12 +951,23 @@ where
return Ok(None);
};

let mut timestamp = T::Time::now();
let mut block_number = <frame_system::Pallet<T>>::block_number();
// if dry run with timestamp override is provided we simulate the run in a `pending` block
if let Some(timestamp_override) =
exec_config.is_dry_run.as_ref().and_then(|cfg| cfg.timestamp_override)
{
block_number += 1u32.into();
let delta = 1000u32.into();
timestamp = cmp::max(timestamp + delta, timestamp_override);
Comment thread
pgherveou marked this conversation as resolved.
Outdated
}

let stack = Self {
origin,
gas_meter,
storage_meter,
timestamp: T::Time::now(),
block_number: <frame_system::Pallet<T>>::block_number(),
timestamp,
block_number,
first_frame,
frames: Default::default(),
transient_storage: TransientStorage::new(limits::TRANSIENT_STORAGE_BYTES),
Expand All @@ -965,7 +976,6 @@ where
contracts_to_be_destroyed: BTreeMap::new(),
_phantom: Default::default(),
};

Ok(Some((stack, executable)))
}

Expand Down Expand Up @@ -1324,7 +1334,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 {
Expand Down Expand Up @@ -1411,7 +1421,6 @@ where
} else {
self.transient_storage.rollback_transaction();
}

log::trace!(target: LOG_TARGET, "frame finished with: {output:?}");

self.pop_frame(success);
Expand Down
48 changes: 38 additions & 10 deletions substrate/frame/revive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -95,14 +95,17 @@ 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},
};
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};
Expand Down Expand Up @@ -1608,6 +1611,7 @@ impl<T: Config> Pallet<T> {
/// - `tx`: The Ethereum transaction to simulate.
pub fn dry_run_eth_transact(
mut tx: GenericTransaction,
dry_run_config: DryRunConfig<<<T as Config>::Time as Time>::Moment>,
) -> Result<EthTransactInfo<BalanceOf<T>>, EthTransactError>
where
T::Nonce: Into<U256>,
Expand Down Expand Up @@ -1672,7 +1676,7 @@ impl<T: Config> Pallet<T> {
call_info.encoded_len,
base_info.total_weight(),
)
.with_dry_run()
.with_dry_run(dry_run_config)
};

// emulate transaction behavior
Expand All @@ -1688,7 +1692,7 @@ impl<T: Config> Pallet<T> {
}

// 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| {
Expand Down Expand Up @@ -2335,11 +2339,12 @@ environmental!(executing_contract: bool);
sp_api::decl_runtime_apis! {
/// The API used to dry-run contract interactions.
#[api_version(1)]
pub trait ReviveApi<AccountId, Balance, Nonce, BlockNumber> where
pub trait ReviveApi<AccountId, Balance, Nonce, BlockNumber, Moment> where
AccountId: Codec,
Balance: Codec,
Nonce: Codec,
BlockNumber: Codec,
Moment: Codec,
{
/// Returns the current ETH block.
///
Expand Down Expand Up @@ -2396,9 +2401,18 @@ 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) -> Result<EthTransactInfo<Balance>, EthTransactError>;

/// Perform an Ethereum call.
///
/// See [`crate::Pallet::dry_run_eth_transact`]
fn eth_transact_with_config(
tx: GenericTransaction,
config: DryRunConfig<Moment>,
) -> Result<EthTransactInfo<Balance>, EthTransactError>;

/// Upload new code without instantiating a contract from it.
///
/// See [`crate::Pallet::bare_upload_code`].
Expand Down Expand Up @@ -2513,7 +2527,9 @@ macro_rules! impl_runtime_apis_plus_revive_traits {
impl_runtime_apis! {
$($rest)*

impl pallet_revive::ReviveApi<Block, AccountId, Balance, Nonce, BlockNumber> for $Runtime {

impl pallet_revive::ReviveApi<Block, AccountId, Balance, Nonce, BlockNumber, Moment> for $Runtime
{
fn eth_block() -> $crate::EthBlock {
$crate::Pallet::<Self>::eth_block()
}
Expand Down Expand Up @@ -2561,7 +2577,19 @@ macro_rules! impl_runtime_apis_plus_revive_traits {
sp_runtime::traits::TransactionExtension,
sp_runtime::traits::Block as BlockT
};
$crate::Pallet::<Self>::dry_run_eth_transact(tx)
$crate::Pallet::<Self>::dry_run_eth_transact(tx, Default::default())
}

fn eth_transact_with_config(
tx: $crate::evm::GenericTransaction,
config: $crate::DryRunConfig<Moment>,
) -> Result<$crate::EthTransactInfo<Balance>, $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::<Self>::dry_run_eth_transact(tx, config)
}

fn call(
Expand All @@ -2584,7 +2612,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(Default::default()),
)
}

Expand All @@ -2610,7 +2638,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(Default::default()),
)
}

Expand Down
Loading
Loading