Skip to content

fix(evm): don't pay the beneficiary for the unused EIP-8037 reservoir - #395

Closed
Xowiek wants to merge 1 commit into
circlefin:mainfrom
Xowiek:fix/beneficiary-reservoir
Closed

Xowiek wants to merge 1 commit into
circlefin:mainfrom
Xowiek:fix/beneficiary-reservoir

Conversation

@Xowiek

@Xowiek Xowiek commented Sep 12, 2026

Copy link
Copy Markdown

ArcEvmHandler::reward_beneficiary pays the beneficiary for:

let gas_used = exec_result.gas().used();

Under EIP-8037 that also counts gas the caller gets back. last_frame_result rebuilds the top-level gas over the whole tx gas limit, returns only the regular remaining, and keeps the unused state gas reservoir separately:

*gas = Gas::new_spent(evm.ctx().tx().gas_limit());
gas.erase_cost(remaining);
gas.set_reservoir(reservoir);

so used()tx.gas_limit - remaining - refunded — still includes the reservoir. Arc doesn't override reimburse_caller, and revm's version hands remaining + reservoir + refunded back to the caller. The reservoir ends up both refunded and paid to the beneficiary.

revm's own post_execution::reward_beneficiary leaves it out for exactly this reason:

// Exclude reservoir gas (EIP-8037) from the used gas — reservoir is unused and reimbursed.
let effective_used = gas.used().saturating_sub(gas.reservoir());

EIP-8037 support landed in revm-handler 18.0.0. The Arc override was written before that and didn't pick the subtraction up when revm was bumped.

With a 20M gas limit, 1M regular gas left, a 3M reservoir and a price of 10, the caller is charged 200,000,000. On main, reimbursement plus reward come to:

  left: 230000000
 right: 200000000

The extra 30,000,000 is reservoir × price of native coin that was never debited from anyone.

This isn't reachable on any network today. The reservoir is only non-zero with EIP-8037 enabled, revm enables it at SpecId::AMSTERDAM, and no Arc chain spec schedules Amsterdam — mainnet and localdev stop at Osaka, testnet and devnet at Prague. So the change is a no-op for every existing block. create_evm already turns off revm's Amsterdam EIP-7708 logs ahead of the spec being enabled; this is the same kind of change for the fee path.

The added test runs revm's reimburse_caller and Arc's reward_beneficiary on the gas last_frame_result produces, and checks the two credits equal what the caller was charged. It fails on main with the output above. cargo test -p arc-evm passes (150 tests), as do fmt and clippy.

ArcEvmHandler overrides reward_beneficiary to pay the full effective gas price
to the beneficiary, and takes the gas it pays for from exec_result.gas().used().

Under EIP-8037 the tx-level Gas built by last_frame_result spans the whole
tx.gas_limit and keeps the unused state gas reservoir outside `remaining`, so
used() still counts it. revm's reimburse_caller, which Arc does not override,
returns remaining + reservoir + refunded to the caller. Paying the beneficiary
for used() as well credits the reservoir twice, and the two credits add up to
more than the caller was charged.

revm's own post_execution::reward_beneficiary subtracts gas.reservoir() for
this reason. The Arc override predates EIP-8037 support in revm-handler 18.0.0
and did not pick the subtraction up when revm was bumped.

No current network is affected: the reservoir is only non-zero once EIP-8037
is enabled, which revm does at Amsterdam, and no Arc chain spec schedules
Amsterdam. The change is a no-op until then.
@ZhiyuCircle

Copy link
Copy Markdown
Contributor

Hi @Xowiek,

Thank you for your interest in contributing to Arc Node, and apologies for the delay in getting back to this PR.

We're closing out the pull request backlog that predates our current contribution policy. This PR is being closed because it does not reference a GitHub issue. All PRs must reference an existing issue using the format Closes: #XXX, and the author must be assigned to that issue before the PR is opened.

This is not a judgement on the change itself. If you'd still like to land it:

  1. Open an issue describing the problem, or find the existing one
  2. Comment on the issue requesting assignment, and wait for maintainer approval
  3. Open a fresh PR once you have been assigned

Please see CONTRIBUTING.md for details. Thanks again for taking the time to contribute.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants