Skip to content

Conversation

@TorstenStueber
Copy link
Contributor

@TorstenStueber TorstenStueber commented Oct 30, 2025

This PR implements the general gas tracking spec.

Follow-up PR to address gas scale: #10393

This PR ballooned into something much bigger than I expected. Many of the changes are due to the fact that all tests and a lot of the other logic has some touch points with the resource management logic. Most of the actual changes in logic are just in the folder metering of pallet-revive.

The main changes are that

  • Metering now works differently depending on whether the transaction as a whole defines weight and deposit limits ("Substrate execution mode") or just an Ethereum gas limit ("Ethereum execution mode"). The Ethereum execution mode is used for all eth_transact extrinsics.
  • There is a third resource (in addition to weight and storage deposits): Ethereum gas. In the Ethereum execution mode this is a shared resource (consumable through weight and through storage deposits).

Metering logic

Almost all changes in this PR are confined to the folder metering of pallet-revive. Before this PR there were two meters: a weight meter and a gas meter. They have now been combined into a main meter called ResourceMeter. Outside code only interacts with the ResourceMeter and not individually with the gas or storage meter. The reason is that in Ethereum execution mode gas is a shared resource and interacting with one meter influences the limits of the other meter.

Here are some finer points:

  • The previous code of the gas and deposit meters has been moved to the metering folder
  • Since outside code interacts only with the ResourceMeter, most functions now don't use a separate gas meter and deposit meter anymore but just a ResourceMeter
  • Similar to the two two kinds of deposits meters (Root and Nested), there are two kind of ResourceMeter: the top-level TransactionMeter used at the beginning of a transaction and a FrameMeter used once per frame
  • The limits of a TransactionMeter are specified through the TransactionLimits type, which distinguishes between Substrate and Ethereum execution mode.
  • The limits of a FrameMeter is specified through the type CallResources, which can either be a) no limits (e.g., in the case of contract creation), or b) a weight and deposit, or c) a gas limit.
  • The top level name of functions in the meters has been changed to be a bit more explicit about their purpose.
    • This applied particularly to the methods at the end of the lifecycle:
      • enforce_limit has been renamed to finalize as that describes the semantics better
      • try_into_deposit has been renamed to execute_postponed_deposits
  • For absorbing a frame meter into its parent meter, there are two different absorption functions:
    • absorb_weight_meter_only: when a frame reverts. In this case we ignore all storage deposits from the reverting frame. We still need to absorb the observed maximum deposit so that we determine the correct maximum deposit during dry running.
    • absorb_all_meters: when a frame was successful
  • The weight meter now has an effective_weight_limit, which needs to be recalculated whenever the deposit meter changes and is for optimization purposes.
  • The limits of the gas meter and deposit meters are now an Option<...>. When it is None, then this represents unlimited meters and this is only used for Ethereum style executions (the meters are not really unlimited, there will be a gas limit that effectively limits the resource usages of the weight and deposit meters).
  • In the weight meters, the sync_to_executor and sync_from_executor are a bit simplified and there is no need for engine_fuel_left anymore.

Other Changes

  • The old name gas for weights has been consistently replaced by weight
  • eth_call and eth_instantiate_with_code now take a weight_limit (used to ensure that weight does not exceed the max extrinsic weight) and an eth_gas_limit (the new externally defined limit)
  • The numeric calculation in compute_max_integer_quotient and compute_max_integer_pair_quotient (defined in substrate/frame/revive/src/evm/frees.rs) are meant to divide a number by the next fee multiplier
  • The call tracer does not take a GasMapper anymore as it will now be fed directly with the Ethereum gas values instead of weights
  • Re-entrancy protection now has three modes: no protection, Strict protection and AllowNext
    • AllowNext allows to re-enter the same contract but only for the next frame. This is required to implement reentrancy protection for simple transfers with call stipends
    • For Strict protection we set allows_reentry of the caller to false before the creation of the new frame, for AllowNext we to it after the creation
  • We define the max block gas as u64::MAX (as discussed with @pgherveou)
  • I now calculate the maximal required storage deposits during dry running (called max_storage_deposit in the deposit meter). For example, if a transaction encounters a storage deposit that is later refunded, then the total storage deposit is zero. However, the caller needs to provide enough resources so that temporarily the execution does not run out of gas and terminates the call prematurely.
  • The function try_upload_code now always takes a meter and records the storage deposit charge there
  • In this PR I added logic to correctly handle call stipends (this fixes [v2-periphery] OutOfGas due to eth transfer gas limit contract-issues#215)

Fixes

This fixes a couple of issues

TODOs

  • Ignore deposit refunds for dry running
  • Properly enforce weight limits
  • Fix gas mapping in the tracer
  • Fix (?) gas mapping in block storage (with_ethereum_context)
  • Check dry running logic again, and create_call, also in ExecConfig
  • Introduce SignedGas
  • use effective_gas_price instead of next fee multiplier
  • ensure that deducted amount is effective_gas_price * used gas
  • check logic of ensure_not_overdrawn
  • Optimize calculations
  • Check whether rounding is done correctly
  • add debug logging
  • Scale gas amounts charged in revm

Other TODOs

  • fix tests and benchmarks
  • add new tests
  • add code docs
  • resolve merge conflicts
  • run benchmarks
  • add PR description

@TorstenStueber TorstenStueber added the T7-smart_contracts This PR/Issue is related to smart contracts. label Oct 30, 2025
@TorstenStueber TorstenStueber requested review from a team as code owners October 30, 2025 11:42
@TorstenStueber TorstenStueber marked this pull request as draft October 30, 2025 11:43
athei and others added 3 commits October 30, 2025 09:49
Fix maxPriorityFee RPC

Change the EVM call opcodes to use proper gas for subcalls

Update tests-evm.yml

Update from github-actions[bot] running command 'prdoc --audience runtime_dev'

fix

fix

[pallet-revive] fix subxt submit & add debug statments (#10016)

- Fix subxt submit by default it's using
`author_submitAndWatchExtrinsic` even though we just want to fire and
forget
- Add debug instructions to log the signer & nonce of new eth
transactions when the node validate the transaction

---------

Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>

Version bumps and prdocs reordering from stable2509 (#9974)

This PR backports regular version bumps and prdocs reordering from the
stable2509 branch back to master

---------

Co-authored-by: ParityReleases <[email protected]>

Update .github/workflows/tests-evm.yml

[Release|CI/CD] Fix polkadot prod docker image (#9975)

This PR introduces a workaround to fix failing polkadot production image
flow.
The initial issue is that, for some reason, our key that used to sign
the deb `InRelease` repo noted as expired on the first `apt update` run.
But reimport of the same key fixes is it. Until the reason for this
issue is fixed, this work around helps to keep the flow working

Introduce `/cmd label` for labelling pull requests (#9915)

This allows external contributors to set label for their pull request.

Closes: #9873

Use parity-large-persistent-test for merge queue (#10025)

Investigating issue with removing persistent runners from merge queue

cc paritytech/devops#3875

pallet_revive: Lower the deposit costs for child trie items (#10027)

Fixes #9246

---------

Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>

pallet_revive: Fix incorrect `block.gaslimit` (#10026)

Fixes paritytech/contract-issues#112

---------

Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>

update tests-evm
@TorstenStueber TorstenStueber marked this pull request as ready for review November 5, 2025 15:05
@TorstenStueber TorstenStueber requested a review from a team as a code owner November 5, 2025 15:05
@sekisamu
Copy link
Contributor

sekisamu commented Nov 6, 2025

@TorstenStueber Hello, thanks for the quick fix. I tested this by running the Uniswap v2 periphery test suite with the latest code from the torsten/gas-fixes branch. The result was 22 failing test cases.

For comparison, the same test suite has only 9 failing cases when run against the latest master branch.

It's worth noting that although the error messages in Hardhat might differ between the failing tests, I've observed on the node that the root cause for all these failed transactions is an OutOfGas error.

@TorstenStueber
Copy link
Contributor Author

@sekisamu thanks for the message. Can you add instructions how to run the tests in that repository? E.g.,

  • Did you just compile the revive-dev-node and the pallet-revive-eth-rpc from my branch?
  • Any setup required for Hardhat? Environment variables?
  • What command do you use to run the tests?

@sekisamu
Copy link
Contributor

@TorstenStueber Hello, I've updated the issue description and include the info you require.

  • yes, I've used the branch torsten/gas-fixes, the commit is: 947a492
  • No extra setup for hardhat, I've already included the account setup in hardhat config.
  • included in the updated issue.

And I've also tried again on the latest commit of torsten/gas-fixes, you can find the result here:paritytech/contract-issues#215 (comment)

@TorstenStueber
Copy link
Contributor Author

@sekisamu it is fixed on the latest commit of this PR and both test suites (https://github.com/papermoonio/v2-periphery-polkadot/tree/revm and https://github.com/papermoonio/eth-transfer-test) are 100% successful.

@TorstenStueber
Copy link
Contributor Author

@TorstenStueber is this in a state where we could use it in anvil to run our integration tests to ensure compatibility? (after rebasing on top of latest master and fixing the conflicts)

@alindima Yes, it is now!

@paritytech-workflow-stopper
Copy link

All GitHub workflows were cancelled due to failure one of the required jobs.
Failed workflow url: https://github.com/paritytech/polkadot-sdk/actions/runs/19728822752
Failed job name: test-linux-stable

Copy link
Member

@athei athei left a comment

Choose a reason for hiding this comment

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

Really like the refactorings here, too. Couldn't find anything obviously wrong. Most of my comments are nits (we should really put a lot of error into explaining the fields of the ResourceMeter). Except for some questions around the gas stipend.

Comment on lines -111 to +125
storage_bytes: u32,
pub storage_bytes: u32,
/// How many items of storage are accumulated in this contract's child trie.
storage_items: u32,
pub storage_items: u32,
/// This records to how much deposit the accumulated `storage_bytes` amount to.
pub storage_byte_deposit: BalanceOf<T>,
/// This records to how much deposit the accumulated `storage_items` amount to.
storage_item_deposit: BalanceOf<T>,
pub storage_item_deposit: BalanceOf<T>,
/// This records how much deposit is put down in order to pay for the contract itself.
///
/// We need to store this information separately so it is not used when calculating any refunds
/// since the base deposit can only ever be refunded on contract termination.
storage_base_deposit: BalanceOf<T>,
pub storage_base_deposit: BalanceOf<T>,
/// The size of the immutable data of this contract.
immutable_data_len: u32,
pub immutable_data_len: u32,
Copy link
Member

Choose a reason for hiding this comment

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

I was hoping we could down on the public fields here and not increase them :D Where do we need to access them?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's a bit annoying. Since I moved all metering code into its own folder, the storage meter is not part of the storage module anymore but needed access to these entries.

I can make them crate public or do you propose a better pattern?

Copy link
Member

Choose a reason for hiding this comment

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

I would have suggested proper accessor to control what can be mutated from outside the module. But that might be shaving the yak. Not sure of pub crate makes a difference since this type shouldn't be exported from the crate. But its a good pattern to mitigate the damage.

@pgherveou
Copy link
Contributor

@TorstenStueber is this in a state where we could use it in anvil to run our integration tests to ensure compatibility? (after rebasing on top of latest master and fixing the conflicts)

@alindima Yes, it is now!

fyi @mokita-j created a branch in foundry polkadot that compile against this branch

Copy link
Member

@xermicus xermicus left a comment

Choose a reason for hiding this comment

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

This looks very good. Thanks!

I ran the revive compiler integration test suite against this branch and saw no regressions introduced vs. master.

@TorstenStueber
Copy link
Contributor Author

I will merge this now as it is required for the audit. I will address the two outstanding points in a follow-up PR:

@TorstenStueber TorstenStueber added this pull request to the merge queue Dec 3, 2025
Merged via the queue into master with commit 685d8cf Dec 3, 2025
300 of 311 checks passed
@TorstenStueber TorstenStueber deleted the torsten/gas-fixes branch December 3, 2025 09:19
github-merge-queue bot pushed a commit that referenced this pull request Dec 4, 2025
This PR is based on
#10166 (i.e., it merges
into the branch `torsten/gas-fixes`).

This PR adds a new configuration parameter (`GasScale`) to pallet-revive
that allows to change the scale of the Ethereum gas and of the Ethereum
gas price.

Before this PR, the Ethereum gas price is simply the next fee multiplier
of pallet-transaction-payment multiplied by `NativeToEthRatio`. Thus, on
Polkadot this is 100_000_000 when the multiplier has its default value
of 1.

The required gas of a transaction is its total cost divided by the gas
price, where the total cost is the sum of the transaction fee and the
storage deposit.

This leads to a situation where the required gas for a transaction on
revive is usually orders of magnitude larger than the required amount of
gas on Ethereum. This can lead to issues with tools or systems that
interact with revive and hard code expected gas amounts or upper limits
of gas amounts.

Setting `GasScale` has two effects:
- revive's Ethereum gas price is scaled up by the factor `GasScale`
- resulting used/estimated gas amounts get scaled down by the factor
`GasScale`.

## Technical Details
Internally, revive uses exactly the same gas price and gas units as
before. Only at the interface these amounts and prices get scaled by
`GasScale`.

**The actual logical changes are almost trivial and I use `GasScale` at
only three places: in `evm_base_fee` to scale the gas price and in the
functions `from_ethereum_gas` and `to_ethereum_gas`.**

## Recommended
This PR sets `GasScale` for the dev-node to 50_000.

This is motivated by the fact that storing a value in a contract storage
slot costs `DepositPerChildTrieItem + DepositPerByte * 32`, which is
`2_000_000_000 + 10_000_000 * 32` (= `2_320_000_000`) plancks. Before
this change the gas price was 1_000_000 wei, so that this
equated to 2_320_000_000 gas units. In EVM this operation requires
22_100 gas only.

Thus, `GasScale` would need to be about 100_000 in order for `SSTORE` to
have similar worst case gas requirements.

## Resolved Issues
- fixes paritytech/contract-issues#221

This PR addresses
paritytech/contract-issues#18 but we also need
to find an appropriate `GasScale` for a mainnet installment of
pallet-revive (see [this
comment](paritytech/contract-issues#18 (comment))).

---------

Co-authored-by: Alexander Theißen <[email protected]>
Co-authored-by: PG Herveou <[email protected]>
Co-authored-by: Omar Abdulla <[email protected]>
Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
github-merge-queue bot pushed a commit that referenced this pull request Dec 4, 2025
This PR is based on
#10166 (i.e., it merges
into the branch `torsten/gas-fixes`).

This PR adds a new configuration parameter (`GasScale`) to pallet-revive
that allows to change the scale of the Ethereum gas and of the Ethereum
gas price.

Before this PR, the Ethereum gas price is simply the next fee multiplier
of pallet-transaction-payment multiplied by `NativeToEthRatio`. Thus, on
Polkadot this is 100_000_000 when the multiplier has its default value
of 1.

The required gas of a transaction is its total cost divided by the gas
price, where the total cost is the sum of the transaction fee and the
storage deposit.

This leads to a situation where the required gas for a transaction on
revive is usually orders of magnitude larger than the required amount of
gas on Ethereum. This can lead to issues with tools or systems that
interact with revive and hard code expected gas amounts or upper limits
of gas amounts.

Setting `GasScale` has two effects:
- revive's Ethereum gas price is scaled up by the factor `GasScale`
- resulting used/estimated gas amounts get scaled down by the factor
`GasScale`.

## Technical Details
Internally, revive uses exactly the same gas price and gas units as
before. Only at the interface these amounts and prices get scaled by
`GasScale`.

**The actual logical changes are almost trivial and I use `GasScale` at
only three places: in `evm_base_fee` to scale the gas price and in the
functions `from_ethereum_gas` and `to_ethereum_gas`.**

## Recommended
This PR sets `GasScale` for the dev-node to 50_000.

This is motivated by the fact that storing a value in a contract storage
slot costs `DepositPerChildTrieItem + DepositPerByte * 32`, which is
`2_000_000_000 + 10_000_000 * 32` (= `2_320_000_000`) plancks. Before
this change the gas price was 1_000_000 wei, so that this
equated to 2_320_000_000 gas units. In EVM this operation requires
22_100 gas only.

Thus, `GasScale` would need to be about 100_000 in order for `SSTORE` to
have similar worst case gas requirements.

## Resolved Issues
- fixes paritytech/contract-issues#221

This PR addresses
paritytech/contract-issues#18 but we also need
to find an appropriate `GasScale` for a mainnet installment of
pallet-revive (see [this
comment](paritytech/contract-issues#18 (comment))).

---------

Co-authored-by: Alexander Theißen <[email protected]>
Co-authored-by: PG Herveou <[email protected]>
Co-authored-by: Omar Abdulla <[email protected]>
Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@mmostafas mmostafas moved this from In progress to Waiting for fix in Security Audit (PRs) - SRLabs Dec 5, 2025
TorstenStueber added a commit that referenced this pull request Dec 6, 2025
Backport all pallet-revive related changes into `unstable2507`.

These are all the changes we want to get onto the next Kusama release.
Main changes include
- EVM backend
- Ethereum block storage
- Generalized gas mapping

The complete list of PRs in this backport is
- #9482
- #9455
- #9454
- #9501
- #9177
- #9285
- #9606
- #9414
- #9557
- #9617
- #9385
- #9679
- #9705
- #9561
- #9744
- #9736
- #9701
- #9517
- #9771
- #9683
- #9791
- #9717
- #9759
- #9823
- #9768
- #9853
- #9801
- #9780
- #9796
- #9878
- #9841
- #9670
- #9865
- #9803
- #9928
- #9818
- #9911
- #9942
- #9831
- #9945
- #9603
- #9968
- #9939
- #9991
- #9914
- #9997
- #9985
- #10016
- #10027
- #10026
- #9418
- #9988
- #10041
- #10047
- #10032
- #10065
- #10089
- #10080
- #10090
- #10106
- #10020
- #9512
- #10109
- #9699
- #10100
- #9909
- #10120
- #10146
- #10157
- #10168
- #10169
- #10160
- #10129
- #10175
- #10186
- #10192
- #10148
- #10193
- #10220
- #10233
- #10191
- #10225
- #10246
- #10239
- #10159
- #10252
- #10224
- #10267
- #10271
- #10214
- #10297
- #10290
- #10281
- #10272
- #10303
- #10336
- #10244
- #10366
- #10380
- #10383
- #10387
- #10302
- #10309
- #10427
- #10385
- #10451
- #10471
- #10166
- #10510
- #10393
- #10540
- #9587
- #10071
- #10558
- #10554
- #10325

---------

Signed-off-by: xermicus <[email protected]>
Co-authored-by: Pavlo Khrystenko <[email protected]>
Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Javier Viola <[email protected]>
Co-authored-by: Bastian Köcher <[email protected]>
Co-authored-by: Bastian Köcher <[email protected]>
Co-authored-by: pgherveou <[email protected]>
Co-authored-by: Omar <[email protected]>
Co-authored-by: 0xRVE <[email protected]>
Co-authored-by: xermicus <[email protected]>
Co-authored-by: Alexander Samusev <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T7-smart_contracts This PR/Issue is related to smart contracts.

Projects

Status: Waiting for fix

8 participants