Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
1dfef66
Binary search in estimate fee
0xOmarA Feb 1, 2026
e8928fd
Updated binary search
0xOmarA Feb 4, 2026
445276a
Update the gas estimation logic
0xOmarA Feb 4, 2026
a324840
Add tests for case that needs binary search
0xOmarA Feb 4, 2026
97012e4
Fix issue in gas estimation
0xOmarA Feb 5, 2026
3bce69c
Update from github-actions[bot] running command 'prdoc --audience run…
github-actions[bot] Feb 5, 2026
eb77362
Move binary search estimation to runtime API
0xOmarA Feb 6, 2026
3212131
Remove unneeded import
0xOmarA Feb 6, 2026
4e3868f
Switch SharedResources logging back to debug
0xOmarA Feb 6, 2026
18b0020
Remove the gas limit override in differential tests
0xOmarA Feb 6, 2026
e15f0c9
Merge remote-tracking branch 'origin/master' into 0xOmarA/revive-gas-…
0xOmarA Feb 6, 2026
7cf8e3e
Fix formatting
0xOmarA Feb 6, 2026
965b882
Add test for estimation without enough gas
0xOmarA Feb 6, 2026
11e3310
Fix `eth_estimate with insufficient funds to cover gas` test
0xOmarA Feb 9, 2026
69d5d55
Merge remote-tracking branch 'origin/master' into 0xOmarA/revive-gas-…
0xOmarA Feb 9, 2026
6a75a24
Simplify runtime API estimate gas
0xOmarA Feb 9, 2026
ac8ed63
Control when balance checks are performed
0xOmarA Feb 9, 2026
94b5cd4
Rename one of the fixtures
0xOmarA Feb 9, 2026
c7d5d80
Change the default value of perform_balance_checks
0xOmarA Feb 9, 2026
6e550e5
Increase margin
0xOmarA Feb 9, 2026
a73e9b3
Modify the balance checking behavior to match
0xOmarA Feb 10, 2026
6be2888
Correct estimation logic
0xOmarA Feb 10, 2026
435e698
Various fixes to the estimation logic
0xOmarA Feb 10, 2026
b689e81
Remove comments from fixture contract
0xOmarA Feb 10, 2026
a22a4d7
Merge remote-tracking branch 'origin/master' into 0xOmarA/revive-gas-…
0xOmarA Feb 10, 2026
900b935
fix clippy lints
0xOmarA Feb 10, 2026
8c9c1fe
Add affected pallets to the pr doc
0xOmarA Feb 23, 2026
9757bfa
Remove accidentally committed claude file.
0xOmarA Feb 23, 2026
91b7730
Rename test to a more accurate name
0xOmarA Feb 23, 2026
1f82fc6
Better log message, including user's allowance.
0xOmarA Feb 23, 2026
06b3f91
Merge remote-tracking branch 'origin/master' into 0xOmarA/revive-gas-…
0xOmarA Feb 23, 2026
8948c3c
Fix pr doc
0xOmarA Feb 23, 2026
1461132
Revert "Simplify runtime API estimate gas"
0xOmarA Feb 26, 2026
87818be
Merge remote-tracking branch 'origin/master' into 0xOmarA/revive-gas-…
0xOmarA Mar 20, 2026
0879f2a
Fix tests
0xOmarA Mar 20, 2026
dab8fd9
Merge branch 'master' into 0xOmarA/revive-gas-estimation-binary-search
0xOmarA Mar 20, 2026
4ca3205
Update the commit hash of dt
0xOmarA Mar 20, 2026
793beab
Update retester expectations
0xOmarA Mar 20, 2026
93ff629
Merge remote-tracking branch 'origin/master' into 0xOmarA/revive-gas-…
0xOmarA Mar 20, 2026
dc3ed85
Fix formatting
0xOmarA Mar 20, 2026
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
460 changes: 2 additions & 458 deletions .github/assets/revive-dev-node-polkavm-resolc.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ rls*.log
runtime/wasm/target/
substrate.code-workspace
target/
target.noindex/
*.scale
rustc-ice-*
/.claude/settings.local.json
*.local.md
*.local.md
16 changes: 16 additions & 0 deletions prdoc/pr_11000.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
title: Revive, Estimate Gas with Binary Search
doc:
- audience: Runtime Dev
description: |-
# Description

This PR implements binary search for the gas estimation logic in the eth-rpc which means that gas estimations are no longer just simple dry runs but that binary search is now used to find the smallest gas limit at which the transaction would run.

This PR closes https://github.com/paritytech/contract-issues/issues/217 and also _kind of_ fixes https://github.com/paritytech/contract-issues/issues/259 or at least makes it harder to trigger the case in which we observe it, but the underlying issue still exists.

The binary search algorithm implemented in this PR is as close as possible to that used in Geth
crates:
- name: pallet-revive
bump: minor
- name: pallet-revive-eth-rpc
bump: minor
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.4;

contract ContractRequiringBinarySearchForGasEstimation {
function main() public view {
this.expensive_operation{gas: gasleft() / 2}();
}

function expensive_operation() external pure returns (uint256 sum) {
for (uint256 i = 0; i < 500; i++) {
sum += i * i;
}
}
}
Comment thread
0xOmarA marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: MIT

pragma solidity >=0.4.21;

contract ContractWithConsumeAllGas {
function test() external {
assembly {
mstore(0, 0xcc572cf9) // main selector
mstore(32, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
mstore(64, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
let gas_value := div(mul(gas(), 1), 100)
let success := call(gas_value, address(), 0, 28, 68, 0, 0)

mstore(0, success)
return(0, 32)
}
}

function main(uint256 offset, uint256 len) external pure {
assembly {
// nullify memory ptr slot
mstore(0x40, 0)
revert(offset, len)
}
}
}
4 changes: 4 additions & 0 deletions substrate/frame/revive/rpc/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,10 @@ pub enum ClientError {
/// Transaction submission timeout.
#[error("Transaction submission timeout")]
Timeout,
/// All of the estimation methods `eth_estimate`, `eth_transact_with_config`, and
/// `eth_transact` were not found and therefore none of the estimation methods succeeded.
#[error("None of the estimation methods were found")]
NoEstimationMethodSucceeded,
/// Chain identity mismatch between stored genesis and connected node.
#[error("Genesis hash mismatch")]
ChainMismatch,
Expand Down
64 changes: 63 additions & 1 deletion substrate/frame/revive/rpc/src/client/runtime_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::{
client::Balance,
subxt_client::{self, SrcChainConfig},
};
use futures::TryFutureExt;
use futures::{StreamExt, TryFutureExt, stream};
use pallet_revive::{
DryRunConfig, EthTransactInfo,
evm::{
Expand Down Expand Up @@ -66,6 +66,68 @@ impl RuntimeApi {
Ok(result)
}

/// Estimates the minimum gas limit required for the transaction execution. Returns a [`U256`]
Comment thread
pgherveou marked this conversation as resolved.
/// of the gas limit.
pub async fn estimate_gas(
&self,
tx: GenericTransaction,
block: BlockNumberOrTagOrHash,
) -> Result<U256, ClientError> {
let timestamp_override = match block {
BlockNumberOrTagOrHash::BlockTag(BlockTag::Pending) => {
Some(Timestamp::current().as_millis())
},
_ => None,
};

// Not all versions of pallet-revive have all of the runtime functions that we require. Thus
// we need to be able to perform the gas estimation through any of the runtime functions
// that the pallet may have available which is why we make use of this stream. The functions
// with higher priority are put at the start while the functions with lower priority are at
// the end.
let mut stream =
// Estimate through the `estimate_gas` function
stream::once(Box::pin(async {
let payload = subxt_client::apis().revive_api().eth_estimate_gas(
tx.clone().into(),
DryRunConfig::new(timestamp_override).into(),
);
self.0.call(payload).await.map(|value| value.map(|value| value.0))
}))
// Otherwise, estimate through `eth_transact_with_config`
.chain(stream::once(Box::pin(async {
let payload = subxt_client::apis().revive_api().eth_transact_with_config(
tx.clone().into(),
DryRunConfig::new(timestamp_override).into(),
);
self.0.call(payload).await.map(|value| value.map(|value| value.eth_gas))
})))
// Otherwise, estimate through `eth_transact`
.chain(stream::once(Box::pin(async {
let payload = subxt_client::apis().revive_api().eth_transact(tx.clone().into());
self.0.call(payload).await.map(|value| value.map(|value| value.eth_gas))
})));

while let Some(result) = stream.next().await {
match result {
Ok(estimation) => {
return estimation.map_err(|err| ClientError::TransactError(err.0));
},
Err(Metadata(MetadataError::RuntimeMethodNotFound(name))) => {
log::debug!(target: LOG_TARGET, "Method {name:?} not found falling back");
},
Err(subxt::Error::Rpc(subxt::error::RpcError::ClientError(
subxt::ext::subxt_rpcs::Error::User(UserError { message, .. }),
))) if message.contains("is not found") => {
log::debug!(target: LOG_TARGET, "{message:?} not found falling back")
},
Err(err) => return Err(err.into()),
}
}

Err(ClientError::NoEstimationMethodSucceeded.into())
}

/// Dry run a transaction and returns the [`EthTransactInfo`] for the transaction.
pub async fn dry_run(
&self,
Expand Down
19 changes: 14 additions & 5 deletions substrate/frame/revive/rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,24 +164,33 @@ impl EthRpcServer for EthRpcServerImpl {
Ok(receipt)
}

/// Performs gas estimations to find the lowest gas limit required to run the transaction.
///
/// This method implements the same gas estimation logic found in Geth which performs binary
/// search with some simple heuristics to find the smallest gas limit for the transaction.
async fn estimate_gas(
&self,
transaction: GenericTransaction,
block: Option<BlockNumberOrTag>,
) -> RpcResult<U256> {
log::trace!(target: LOG_TARGET, "estimate_gas transaction={transaction:?} block={block:?}");

let block = block.unwrap_or_else(|| {
if self.use_pending_for_estimate_gas {
BlockTag::Pending.into()
} else {
Default::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?;
log::trace!(target: LOG_TARGET, "estimate_gas result={dry_run:?}");
Ok(dry_run.eth_gas)
let hash = self.client.block_hash_for_tag(block.into()).await?;
let gas_estimate =
self.client.runtime_api(hash).estimate_gas(transaction, block.into()).await?;

log::trace!(
target: LOG_TARGET,
"estimate_gas result={gas_estimate:?}",
);
Ok(gas_estimate)
}

async fn call(
Expand Down
Loading
Loading