Skip to content

feat(frames): two-dimensional gas limits per EIP-8141 - #12847

Closed
AnkushinDaniil wants to merge 2 commits into
daniil/frames-familyfrom
daniil/eip8141-2d-gas-limits
Closed

feat(frames): two-dimensional gas limits per EIP-8141#12847
AnkushinDaniil wants to merge 2 commits into
daniil/frames-familyfrom
daniil/eip8141-2d-gas-limits

Conversation

@AnkushinDaniil

@AnkushinDaniil AnkushinDaniil commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

What

Implements EIP-8141 two-dimensional frame gas limits = [execution, state] (ethereum/EIPs#12062) end to end, in one change: the field split plus the runtime semantics that give it meaning.

Field split

  • TxFrame carries ExecutionGasLimit and StateGasLimit; GasLimit stays as their sum for the combined budget (max-cost reservation, overflow checks).
  • RLP encodes limits as a nested [execution, state] list; the decoder reads both.
  • Static validation sums execution + state per frame with overflow guards; an expiry-verifier frame must keep limits.state == 0.
  • Intrinsic/standard_gas_limit accounts for both dimensions.
  • FRAMEPARAM: 0x01 returns limits.execution, new 0x09 returns limits.state.
  • JSON-RPC exposes executionGasLimit / stateGasLimit.

Runtime two-pool semantics

  • Each frame is now rented with gas_left = limits.execution and its state-gas reservoir seeded from limits.state, so state work is drawn from the state dimension independently of the execution budget. Before this, a frame rented an empty reservoir and every state charge spilled into execution.
  • The per-frame charge folded into the transaction total is execution gas used plus the reservoir-funded state gas. This equals GetPreRefundGas(gas, execution + state) on the success/revert path and execution + state − unspent_reservoir on an exceptional halt, mirroring the standard EIP-8037 halt formula (gas_left is burned; only the unspent reservoir escapes the charge).
  • Because the folded charge still includes the state gas each frame used — whether reservoir-funded or spilled — the payer charge and the block state/regular split (blockStateGas, CalculateBlockExecutionGas) are unchanged. The change is behavioral only where a frame's state budget now lets it do state work its execution budget could not have absorbed.

Public-mempool bound

  • Adds MAX_VERIFY_STATE_GAS (default 500,000, config FrameTxMaxVerifyStateGas, 0 lifts it): the sum of the validation prefix's limits.state must not exceed it, enforced at ingress alongside MAX_VERIFY_GAS. State gas does not measure node validation work, so it stays out of the MAX_VERIFY_GAS budget.

Testing

  • New regression: a fresh-slot write with an execution budget too small to absorb its SSetState charge succeeds when limits.state covers it and bills the same state dimension; the negative control with state = 0 spills into execution and halts out of gas, committing nothing.
  • New regression: the MAX_VERIFY_STATE_GAS ingress bound (at, over, and behind a recognized prefix).
  • Full frame + EIP-8037 suites green: Core 104, Evm 334, TxPool 57, Blockchain 145.

Behavior on the settlement and halt paths is consensus-affecting, so this is queued for the reproducible stand and a cross-client Kurtosis run before it leaves the family branch.

Open question

The reference spec lists FRAMEPARAM 0x0A / 0x0B (gas_used.execution / gas_used.state) and a two-field frame receipt. Those are gas-used introspection rather than the limits split, and are not implemented here — should they land in this change or a follow-up? Happy to fold them in if you'd prefer one card.

Split each frame's single gas_limit into limits = [execution, state] to
match the merged ethereum/EIPs#12062 spec update. Encoding, static
validation, intrinsic budget, FRAMEPARAM introspection and the JSON-RPC
view now carry both dimensions; runtime remains combined-pool
(execution + state) pending the explicit dual-pool follow-up.
@claude

claude Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Claude finished @AnkushinDaniil's task in 4m 37s —— View job


Review: two-dimensional gas limits per EIP-8141

Read the full diff against origin/daniil/frames-family, plus the surrounding call sites (TransactionProcessorBase.FrameTx.cs, TxDecoders/FrameTxDecoder.cs, FrameTxVerifyGasFilter, RlpReader.DecodeArray).

  • Gather context (diff, related files)
  • Review RLP encode/decode changes
  • Review validation & gas accounting
  • Review EVM instruction + RPC changes
  • Post review

The shape of the change is good: the overflow guard frameGas < frame.ExecutionGasLimit || accumulated < totalFrameGas is the correct two-step check, the nested-list encoding is length-consistent (GetLimitsContentLength feeds both StartSequence and LengthOfSequence), and the convenience constructor is well justified given ~76 new TxFrame(...) call sites. The staged approach is sensible — but one half of the split leaks a security-relevant inconsistency.

Findings

High — MAX_VERIFY_GAS becomes bypassable · FrameTxValidation.cs:275

ValidationWorkGas now counts limits.execution only, but the runtime still seeds the frame's VM pool with the combined budget:

// TransactionProcessorBase.FrameTx.cs:594
TGasPolicy.FromULong(frame.GasLimit),   // == execution + state

A prefix frame declaring limits = [1, 100_000_000] prices at ~1 gas, passes FrameTxVerifyGasFilter (default FrameTxMaxVerifyGas = 100_000), and still executes against a 100M pool of arbitrary work — nothing constrains state gas to state growth until the dual-pool seeding lands. Affordability isn't a mitigation: MAX_VERIFY_GAS exists exactly for the case where the tx is never included and the sender never pays.

Either keep this on the combined GasLimit until gas_left = limits.execution is real, or land MAX_VERIFY_STATE_GAS here. Both halves need to move together.
Fix this →

Medium — inner limits check gated on AllowExtraBytes · TxFrameDecoder.cs:36-39

AllowExtraBytes means "trailing bytes after the payload are fine", which is why gating the outer check (line 44) is harmless — it's the last read. This one is mid-stream: skipping it leaves Position misaligned, so value and data are read from the wrong offsets and limits = [exec, state, junk] silently decodes to a different frame with a different tx hash. Currently unreachable (RlpReader.DecodeArray calls Decode(ref this) with RlpBehaviors.None), but KeyValueStoreRlpExtensions ORs the flag in on DB paths, so it's a live trap for the next call site. Make it unconditional.

Medium — test coverage for the changed behaviours

The added tests cover the round-trip, the expiry rejection, and FRAMEPARAM 0x09, but three behaviour changes are untested:

  • ValidationWorkGas ignoring limits.stateValidationWorkCases still only exercises frames with state == 0, so the semantic change is invisible to the suite.
  • Malformed limits — no negative case for a 1- or 3-element list, or for a scalar where a list is expected (the pre-split encoding). These are the decode paths external input actually reaches.
  • No fixed-hex vector pinning the nested-list wire shape; a round-trip test passes even if encoder and decoder drift from the spec together.

Low — TxFrame.GasLimit wraps silently · TxFrame.cs:51

The remark holds for frames reaching execution, but TxDecoders/FrameTxDecoder.cs:148 reads it at decode time, before IsWellFormed. With execution = ulong.MaxValue, state = 10 the property returns 9, defeating the saturating sum there. Validation rejects the tx afterwards so there's no execution path — but a saturating property fails closed for one extra comparison. See inline suggestion.

Low — new consensus rule without an EIP citation · FrameTxValidation.cs:158

frame.StateGasLimit != 0 on the expiry verifier frame is a new validity condition; .agents/rules/coding-style.md requires an EIP/section reference for non-obvious consensus rules. Also worth confirming against the merged spec whether it constrains limits.execution for that frame too.

Low — RPC surface

gasLimitexecutionGasLimit + stateGasLimit is a breaking rename of the JSON field on both output and input; ToFrame() silently defaults an absent stateGasLimit to 0. Fine for an unshipped tx type, but since the RLP and the spec both model this as limits = [execution, state], consider mirroring that as a limits array in JSON rather than two flat scalars — otherwise the RPC view is the one place the tuple is destructured.

Low — naming and duplication

  • The convenience constructor's parameter is still named gasLimit while it now means execution-only. Renaming it executionGasLimit costs nothing and prevents someone porting a combined value into it.
  • Execute_FrameParam_StateGasLimit (the [TestCase((byte)0x09, 0UL, …)] row) and Execute_FrameParam_StateGasLimit_ReadsDeclaredStateBudget overlap; the former only adds a zero-default case. Per the repo's DRY-in-tests rule, consider folding the declared-budget assertion into the parameterized fixture instead.

Note (not a finding): FRAMEPARAM 0x01 now reports execution-only while the frame runs on a combined pool, so a contract using 0x01 to reason about its own headroom sees a value inconsistent with reality until the dual-pool PR lands. Deliberate per the PR description, but worth a EIP8141-GAP:-style marker next to the opcode so the follow-up doesn't miss it.

Verdict

Not mergeable as-is: the MAX_VERIFY_GAS gap is a real mempool-DoS regression created by splitting the accounting ahead of the runtime. The RLP check gating and the missing negative decode tests should follow. Everything else is minor.

I did not build or run tests — this environment blocks dotnet build, so the assessment is from reading the code and its call sites.
· branch daniil/eip8141-2d-gas-limits

for (int i = 0; i < counted; i++)
{
total = Saturating(total, frames[i].GasLimit);
total = Saturating(total, frames[i].ExecutionGasLimit);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

High — MAX_VERIFY_GAS becomes bypassable while the runtime still uses a combined pool.

This now prices the validation prefix at limits.execution only, but the runtime seeds the frame's VM pool with the combined budget (TransactionProcessorBase.FrameTx.cs:594TGasPolicy.FromULong(frame.GasLimit) where GasLimit == execution + state), and the state-gas reservoir is unchanged. So a prefix frame declaring limits = [1, 100_000_000] scores ~1 gas here, sails past FrameTxVerifyGasFilter (FrameTxMaxVerifyGas = 100_000 by default), and still gets a 100M-gas pool of arbitrary work when the pool simulates the prefix. The declared state gas does not constrain what the frame spends it on until the dual-pool seeding lands.

The affordability check is not a mitigation: MAX_VERIFY_GAS exists precisely for the case where the attacker never pays (tx never included), so a funded sender can spam unbounded free validation work.

Since both the dual-pool seeding and MAX_VERIFY_STATE_GAS are explicitly out of scope here, the two halves of the split need to stay consistent. Options:

  • keep this on the combined frames[i].GasLimit until the runtime actually seeds gas_left = limits.execution, or
  • land the MAX_VERIFY_STATE_GAS bound in this PR alongside the execution-only change.

Comment on lines +36 to +39
if (!rlpBehaviors.HasFlag(RlpBehaviors.AllowExtraBytes))
{
decoderContext.Check(limitsCheck);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium — the inner limits check should not be gated on AllowExtraBytes.

AllowExtraBytes means "tolerate trailing bytes after the payload", which is why gating the outer check on line 44 is harmless — it is the last read. Here the check sits mid-stream: skipping it does not tolerate trailing data, it leaves Position inside/after the limits list and makes the subsequent value and data reads land at the wrong offsets, silently producing a different frame (and therefore a different tx hash) from limits = [exec, state, junk].

Today this is latent — RlpReader.DecodeArray calls decoder.Decode(ref this) with RlpBehaviors.None, so frames never see the flag — but any future call site (Rlp.Decode<TxFrame>(…, AllowExtraBytes), a DB path via KeyValueStoreRlpExtensions, which ORs the flag in) would hit it. Make it unconditional:

Suggested change
if (!rlpBehaviors.HasFlag(RlpBehaviors.AllowExtraBytes))
{
decoderContext.Check(limitsCheck);
}
decoderContext.Check(limitsCheck);

/// <summary>The combined gas the frame reserves against the payer, <c>limits.execution + limits.state</c>.</summary>
/// <remarks>Static validation rejects a transaction whose frame reservations overflow, so this sum never
/// wraps for a frame reaching execution.</remarks>
public ulong GasLimit => ExecutionGasLimit + StateGasLimit;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Low — GasLimit wraps silently, and it is read before validation.

The remark is true for frames that reach execution, but FrameTxDecoder.DecodeInternal reads frame.GasLimit while summing transaction.GasLimit (TxDecoders/FrameTxDecoder.cs:148) — i.e. at decode time, before IsWellFormed runs. With execution = ulong.MaxValue, state = 10 the property returns 9, so the saturating sum there is defeated and the tx is briefly visible with an absurdly small GasLimit. Validation does reject it afterwards, so there is no path to execution, but failing closed is cheap here:

Suggested change
public ulong GasLimit => ExecutionGasLimit + StateGasLimit;
public ulong GasLimit => ExecutionGasLimit > ulong.MaxValue - StateGasLimit ? ulong.MaxValue : ExecutionGasLimit + StateGasLimit;

(If you prefer to keep the plain sum, at least widen the remark to say the property may wrap for a transaction that has not yet passed static validation.)

if (frame.Mode == TxFrame.ModeVerify && frame.Target == Eip8141Constants.ExpiryVerifierAddress)
{
if (frame.Flags != 0 || !frame.Value.IsZero || frame.Data.Length != Eip8141Constants.ExpiryDataLength)
if (frame.Flags != 0 || !frame.Value.IsZero || frame.StateGasLimit != 0 || frame.Data.Length != Eip8141Constants.ExpiryDataLength)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Low — new consensus rule without a spec citation.

frame.StateGasLimit != 0 is a new validity condition on the expiry verifier frame. Per .agents/rules/coding-style.md ("Non-obvious consensus rules or algorithms must reference the EIP number or Yellow Paper section"), please anchor it — the surrounding conditions predate the split and carry their justification in the enclosing docs, but this one is new. Also worth double-checking against the merged spec text whether the expiry frame is required to have limits.state == 0 specifically, or whether the spec constrains limits.execution too (in which case a fixed execution budget should be asserted here as well).

@github-actions

github-actions Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

EVM Opcode Benchmark Diff

Aggregated runs: base=3, pr=3
Noisy rerun opcodes: SELFDESTRUCT

Regressions (1)

Opcode Base Median (ns) PR Median (ns) Delta Abs Δ (ns) Base CV PR CV Threshold Uncertainty Effective
ADDMOD 427.800 461.500 +7.88% 33.700 1.2% 2.5% ±5.0% ±2.8% ±5.0%

Improvements (1)

Opcode Base Median (ns) PR Median (ns) Delta Abs Δ (ns) Base CV PR CV Threshold Uncertainty Effective
SMOD 286.430 257.310 -10.17% 29.120 1.4% 1.5% ±5.0% ±1.9% ±5.0%

Seed each frame's execution pool from limits.execution and its state-gas
reservoir from limits.state, so state work is drawn independently of the
execution budget. The per-frame charge folds execution gas used plus the
reservoir-funded state gas, keeping the payer charge and the block state/
regular split unchanged. Add the MAX_VERIFY_STATE_GAS public-mempool bound
(sum of the validation prefix limits.state) alongside MAX_VERIFY_GAS.
@AnkushinDaniil AnkushinDaniil changed the title feat(frames): carry two-dimensional gas limits per EIP-8141 feat(frames): two-dimensional gas limits per EIP-8141 Aug 17, 2026

@flcl42 flcl42 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Found 7 issues (6 critical and 1 high) in src/Nethermind/Nethermind.Evm/GasPolicy/EthereumGasPolicy.cs, src/Nethermind/Nethermind.Core/TxFrame.cs, src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessorBase.FrameTx.cs, and src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.FrameTx.cs.


[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EthereumGasPolicy FromFrameLimits(ulong executionGasLimit, ulong stateGasLimit) =>
new() { Value = executionGasLimit, StateReservoir = (long)stateGasLimit };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[CRITICAL] Frame state charges can spill into the execution budget

FromFrameLimits seeds the existing EIP-8037 reservoir policy, whose ConsumeStateGas deducts any reservoir shortfall from Value. EIP-8141's explicit frame pools are independent and exclude this spill behavior. A state charge above StateGasLimit therefore succeeds whenever ExecutionGasLimit covers the difference, whereas a conforming client exceptionally halts the frame; committed state and receipts then diverge.

/// <summary>The combined gas the frame reserves against the payer, <c>limits.execution + limits.state</c>.</summary>
/// <remarks>Static validation rejects a transaction whose frame reservations overflow, so this sum never
/// wraps for a frame reaching execution.</remarks>
public ulong GasLimit => ExecutionGasLimit + StateGasLimit;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[CRITICAL] The combined GasLimit alias collapses independent reservations

Transaction.GasLimit is populated by summing this alias, while the unchanged EIP-8037 inclusion check feeds that scalar into both dimensions and the block picker compares the combined budget with one remaining-gas value. A transaction whose execution and state reservations each fit independently can therefore be rejected solely because their sum exceeds the block limit. The same alias lets the default-code keyed-nonce surcharge treat limits.state as execution headroom, affecting both block validity and frame execution.

@@ -428,9 +428,10 @@ private TransactionResult ExecuteFrameTx(Transaction tx, ITxTracer tracer, Execu
ulong grossGas = intrinsicGas + totalFrameGasUsed;
ulong spentGas = Math.Max(grossGas - RefundHelper.CalculateClaimableRefund(grossGas, (ulong)refundCounter, spec), floorGas);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[CRITICAL] The calldata floor absorbs frame state gas

For a floor-bound frame transaction, this applies Math.Max to the combined execution-and-state charge. EIP-8141 instead floors the execution component and then adds state gas. With 20k execution gas, 30k state gas, and a 100k floor, this path charges and reports 100k instead of 130k, changing the payer refund and cumulative receipt gas.

gasUsed = frame.GasLimit - remainingGas;
ulong combinedLimit = frame.ExecutionGasLimit + frame.StateGasLimit;
gasUsed = substate.IsError
? combinedLimit - (ulong)Math.Max(0, TGasPolicy.GetStateReservoir(in state.Gas))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[CRITICAL] Exceptional halts retain rolled-back state charges

The top-level VM does not call ResetForHalt, so this error branch subtracts the remaining reservoir before the frame snapshot is restored. If a frame consumes state gas and later executes INVALID, gasUsed becomes the full execution limit plus the consumed state gas. EIP-8141 restores the state pool to frame entry and records zero state usage on an exceptional halt, burning only the execution pool, so payer balance and cumulative gas diverge.


ulong remainingGas = substate.IsError ? 0 : TGasPolicy.GetRemainingGas(in state.Gas);
gasUsed = frame.GasLimit - remainingGas;
ulong combinedLimit = frame.ExecutionGasLimit + frame.StateGasLimit;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[CRITICAL] Frame receipts still encode gas usage as one scalar

The two budgets are collapsed into gasUsed here and passed to the existing scalar TxFrameReceipt.GasUsed; the receipt encoders consequently emit one RLP integer. The amended receipt format is gas_used = [execution, state] for every frame receipt, including zero-state frames. Any block containing a frame transaction therefore gets a different receipt payload and receipts root from a conforming client.

if (!stack.PopUInt256(out UInt256 frameIndex, out UInt256 param)) return EvmExceptionType.StackUnderflow;
if (frameIndex >= (UInt256)ctx.Frames.Length) return EvmExceptionType.BadInstruction;
if (param > 0x08) return EvmExceptionType.BadInstruction;
if (param > 0x09) return EvmExceptionType.BadInstruction;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[CRITICAL] FRAMEPARAM rejects the required gas-usage selectors

The updated EIP-8141 table defines 0x0A and 0x0B for a completed frame's execution and state usage. This bound returns BadInstruction for both. A later frame that introspects either usage therefore exceptionally halts instead of receiving the recorded value, which can change subsequent approvals and state.

ulong combinedLimit = frame.ExecutionGasLimit + frame.StateGasLimit;
gasUsed = substate.IsError
? combinedLimit - (ulong)Math.Max(0, TGasPolicy.GetStateReservoir(in state.Gas))
: TGasPolicy.GetPreRefundGas(in state.Gas, combinedLimit);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[HIGH] Reverted frames keep their rolled-back state gas in the payer charge

For a frame that consumes state gas and then exits via REVERT (ShouldRevert, not an exceptional halt), gasUsed is computed as GetPreRefundGas(state.Gas, combinedLimit) = combinedLimit - remainingGas - stateReservoir, which equals consumed execution gas plus consumed state gas. The consumed state gas therefore stays in totalFrameGasUsed and is charged to the payer even though the frame's writes were rolled back. Two behaviors in the same codebase indicate that rolled-back state owes no state gas: the regular EIP-8037 path restores a reverted child's state gas (RestoreChildStateGas: "On explicit REVERT, restore the child's remaining state reservoir plus its reverted state gas usage"), and the atomic-batch unroll in this very method resets totalFrameStateGasUsed to its batch-start value because "the unrolled frames' writes are gone with the snapshot, so their state charges are not owed either." The block dimension already excludes the reverted frame (totalFrameStateGasUsed only accumulates on success, and Execute_PayloadFrameReverts_OwesNoStateGas asserts BlockStateGas == 0), but the payer dimension still bills it, so a frame such as SSTORE followed by REVERT leaves the payer charged for state growth that never committed. A client that restores the state pool to frame entry on revert would compute a smaller payer charge and cumulative gas, so payer balances and receipts diverge.


[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EthereumGasPolicy FromFrameLimits(ulong executionGasLimit, ulong stateGasLimit) =>
new() { Value = executionGasLimit, StateReservoir = (long)stateGasLimit };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit (long)stateGasLimit is an unchecked narrowing cast: a stateGasLimit > long.MaxValue silently becomes a negative StateReservoir, and ConsumeStateGas/CalculateStateGasSpill treat a non-positive reservoir as "spill everything into execution". The sibling default IGasPolicy.FromFrameLimits handles the same overflow domain by saturating at ulong.MaxValue, so the two implementations disagree on out-of-range input.

Not reachable today (a frame reaching execution has passed affordability and block-gas-limit checks, which bound the limits far below long.MaxValue), hence a nit — but the inconsistency is a latent trap if a future caller invokes this off the execution path. Consider clamping the reservoir the same way the default does. (Distinct from the already-raised spill-semantics concern on this method.)

[ConfigItem(DefaultValue = "100000", Description = "EIP-8141 `MAX_VERIFY_GAS`: the max gas a frame transaction's validation prefix and signature verification may cost for the transaction to be accepted into the public mempool. `0` to lift the limit. Raise it only on a test network.")]
ulong FrameTxMaxVerifyGas { get; set; }

[ConfigItem(DefaultValue = "500000", Description = "EIP-8141 `MAX_VERIFY_STATE_GAS`: the max state gas a frame transaction's validation prefix may budget across its `limits.state` for the transaction to be accepted into the public mempool. `0` to lift the limit. Raise it only on a test network.")]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit Worth confirming the default 500000 matches the merged EIP-8141 MAX_VERIFY_STATE_GAS value — the description asserts equality with the spec constant, and per coding-style.md config keys should document defaults accurately. MAX_VERIFY_GAS above defaults to 100000; a quick check that the state bound's magnitude relative to it is intentional (5×) would avoid pinning a placeholder.

@AnkushinDaniil

Copy link
Copy Markdown
Contributor Author

Superseded by #12850, which carries the same two-dimensional gas change against feature/frames-devnet-0. Continuing the review there.

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.

3 participants