Skip to content

[codex] add dynamic custom precompile gas meter#3644

Open
codchen wants to merge 1 commit into
codex/evmonly-staking-precompilefrom
codex/evmonly-staking-dynamic-gas
Open

[codex] add dynamic custom precompile gas meter#3644
codchen wants to merge 1 commit into
codex/evmonly-staking-precompilefrom
codex/evmonly-staking-dynamic-gas

Conversation

@codchen

@codchen codchen commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds dynamic gas accounting for evm-only custom precompiles, with the staking precompile using the shared meter through the existing store, balance-transfer, and log boundaries.

Changes

  • Add a reusable precompile gas meter for base gas, SLOAD/SSTORE-style storage costs, keccak slot derivation, native value transfers, and log emission.
  • Wrap storageBackedStore, nativeBalanceTransfer, and log emission in the custom precompile adapter so gas is charged based on the actual execution path.
  • Reduce staking RequiredGas to only base/input gas, avoiding double charging now that state access is dynamically metered.
  • Add executor coverage for dynamic store gas and update staking lifecycle tests to use realistic gas limits.

Validation

  • go test ./giga/evmonly/...
  • golangci-lint v2.8.0 run ./giga/evmonly/...

@codecov

codecov Bot commented Jun 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 59.66851% with 73 lines in your changes missing coverage. Please review.
✅ Project coverage is 58.15%. Comparing base (accacbf) to head (7c6c44c).

Files with missing lines Patch % Lines
giga/evmonly/precompile_gas.go 68.46% 23 Missing and 18 partials ⚠️
giga/evmonly/precompile_adapter.go 34.69% 19 Missing and 13 partials ⚠️
Additional details and impacted files

Impacted file tree graph

@@                         Coverage Diff                          @@
##           codex/evmonly-staking-precompile    #3644      +/-   ##
====================================================================
- Coverage                             58.15%   58.15%   -0.01%     
====================================================================
  Files                                  2187     2185       -2     
  Lines                                178461   178564     +103     
====================================================================
+ Hits                                 103781   103836      +55     
- Misses                                65383    65413      +30     
- Partials                               9297     9315      +18     
Flag Coverage Δ
sei-chain-pr 56.29% <59.66%> (-0.12%) ⬇️
sei-db 70.41% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
giga/evmonly/precompiles/staking/staking.go 23.91% <100.00%> (-0.40%) ⬇️
giga/evmonly/precompile_adapter.go 66.35% <34.69%> (-8.65%) ⬇️
giga/evmonly/precompile_gas.go 68.46% <68.46%> (ø)

... and 5 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown

The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedJun 30, 2026, 6:12 AM

@codchen codchen marked this pull request as ready for review June 26, 2026 03:42
@cursor

cursor Bot commented Jun 26, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes consensus-relevant gas and refund behavior for custom precompiles and staking; incorrect metering could cause unexpected OOG or undercharging, though coverage targets store OOG and staking e2e paths.

Overview
Introduces dynamic gas accounting for evm-only custom precompiles so state work is paid from the transaction gas budget instead of a flat upfront fee.

RunAndCalculateGas now uses a shared precompileGasMeter: it charges RequiredGas first, then meters Keccak for store key/slot derivation, EIP-2929/2200-style SLOAD/SSTORE (including refunds and access lists), native value transfers, and log emission. The adapter wires the meter into storageBackedStore, nativeBalanceTransfer, and a meteredLogSink; on exhaustion the precompile fails with ErrOutOfGas and store paths stop persisting changes.

The staking precompile drops fixed read/write gas in RequiredGas (now 700 + 16×input bytes only) so storage and transfers are not double-charged.

Tests assert higher gas use on successful store writes, OOG with no storage when gas is too low, and staking flows use realistic gas limits (8M).

Reviewed by Cursor Bugbot for commit bd57e4c. Bugbot is set up for automated code reviews on this repo. Configure here.

@codchen codchen force-pushed the codex/evmonly-staking-precompile branch from 27e3acc to 550f6fa Compare June 29, 2026 12:34
@codchen codchen force-pushed the codex/evmonly-staking-dynamic-gas branch from 56bf186 to bd57e4c Compare June 29, 2026 12:35

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit bd57e4c. Configure here.

Comment thread giga/evmonly/precompile_gas.go

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The dynamic precompile gas meter is implemented carefully and faithfully mirrors go-ethereum's EIP-2929/2200/3529 SLOAD/SSTORE/refund accounting, with refund state being snapshot-safe and OOG correctly surfaced and discarded; tests cover the new metering. The only issue is a now-orphaned helper function and a behavioral note about read-method gas.

Findings: 0 blocking | 4 non-blocking | 1 posted inline

Blockers

  • None at the file/PR level.

Non-blocking

  • isTransaction in giga/evmonly/precompiles/staking/helpers.go was the only consumer of the removed read/write gas split and is now unused dead code (no remaining references in the package). Remove it — golangci-lint's unused check may flag this and fail CI even though the PR notes lint passed.
  • Behavior change worth noting: read-only query methods (validators, delegatorDelegations, etc.) previously cost a flat readGas (3000) and now meter per-SLOAD dynamically. Read-heavy paged queries (up to pageLimit=100 validators, each a multi-chunk read) can consume substantially more gas than before; callers/eth_call gas caps must account for this. This is more accurate, just a notable semantics change.
  • Codex and Cursor second-opinion passes both produced no material findings.
  • 1 suggestion(s)/nit(s) flagged inline on specific lines.

gas = writeGas
}
return gas + inputByteGas*uint64(len(input)) //nolint:gosec // input length is bounded by memory.
return baseGas + inputByteGas*uint64(len(input)) //nolint:gosec // input length is bounded by memory.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] With the read/write gas split removed here, isTransaction in helpers.go is no longer referenced anywhere in the package and is now dead code. Consider removing it — the unused linter may otherwise fail CI.

@codchen codchen force-pushed the codex/evmonly-staking-precompile branch from 550f6fa to accacbf Compare June 30, 2026 06:11
@codchen codchen force-pushed the codex/evmonly-staking-dynamic-gas branch from bd57e4c to 7c6c44c Compare June 30, 2026 06:11

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The PR adds a dynamic gas meter for evm-only custom precompiles whose SLOAD/SSTORE/access-list/keccak/log/transfer pricing faithfully mirrors go-ethereum (EIP-2200/2929/3529), with refund and access-list mutations correctly rolled back on OOG via RevertToSnapshot and good test coverage. The metering relies on GetCommittedState as the SSTORE "original" value, but nativeStateDB.base is block-start state that Finalise never advances, so SSTORE cost/refunds are mispriced for slots written across multiple transactions in the same block.

Findings: 1 blocking | 2 non-blocking | 1 posted inline

Blockers

  • None at the file/PR level.
  • 1 blocking issue(s) flagged inline on specific lines.

Non-blocking

  • cursor-review.md was empty — the Cursor second-opinion pass produced no output, so its perspective is not reflected here. (REVIEW_GUIDELINES.md was also empty; reviewed without repo-specific guidance.)
  • Minor: chargeNativeTransfer charges account-access (cold/warm) for both from and to. Standard EVM CALL gas does not recharge access for the caller/from (it is already warm), so transfers slightly overcharge relative to geth. Likely acceptable as a custom model, but worth confirming it is intentional.

if current == value {
return m.charge(gasAdd(cost, params.WarmStorageReadCostEIP2929), tracing.GasChangeCallPrecompiledContract)
}
original := db.GetCommittedState(addr, slot)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[blocker] GetCommittedState is used as the EIP-2200 "original" (transaction-start) value, but in this nativeStateDB the base map holds block-start state and is never advanced between transactions — Finalise (state_db.go:492) only clears selfdestruct/created flags and resets refund, and account()/baseAccount() populate base lazily from source (block start). The executor reuses one stateDB for the whole block (executor.go:80), so for a slot written in tx N and re-written/cleared in tx N+1 of the same block, original here is the block-start value rather than the tx N+1-start value.

This diverges from go-ethereum (where per-tx Finalise moves dirty storage into pending storage so GetCommittedState reflects prior txs in the block), causing SSTORE to take the wrong SET/RESET/dirty branch and emit incorrect AddRefund/SubRefund amounts (under/over-charging and bogus clear refunds). Before this PR the flat read/write gas hid the issue; the dynamic meter now makes it consensus-relevant for gas accounting. Consider tracking a separate transaction-start snapshot (advanced in Finalise) distinct from the block-start base used by ChangeSet.

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.

1 participant