From bfa0bfdc7c2b2827e34079d5de2b4df32296f43b Mon Sep 17 00:00:00 2001 From: jonny rhea <5555162+jrhea@users.noreply.github.com> Date: Mon, 29 Jun 2026 17:45:01 -0500 Subject: [PATCH] core/vm, core/vm/gen: charge dynamic gas through a shared chargeDynamic helper --- core/vm/gascosts.go | 13 +++++++++ core/vm/gen/main.go | 66 ++++++++++++++++++++++-------------------- core/vm/interp_gen.go | 40 +++++++------------------ core/vm/interpreter.go | 8 ++--- 4 files changed, 59 insertions(+), 68 deletions(-) diff --git a/core/vm/gascosts.go b/core/vm/gascosts.go index b73732daa3..9111bdf856 100644 --- a/core/vm/gascosts.go +++ b/core/vm/gascosts.go @@ -94,6 +94,19 @@ func (g *GasBudget) chargeRegularOnly(r uint64) error { return nil } +// chargeDynamic charges a dynamic gas cost: a regular-only deduction when there +// is no state gas, otherwise the full multidimensional charge through the +// reservoir. +func (g *GasBudget) chargeDynamic(cost GasCosts) error { + if cost.StateGas == 0 { + return g.chargeRegularOnly(cost.RegularGas) + } + if !g.charge(cost) { + return ErrOutOfGas + } + return nil +} + // CanAfford reports whether the running budget can cover the given cost vector // without going out of gas. func (g GasBudget) CanAfford(cost GasCosts) bool { diff --git a/core/vm/gen/main.go b/core/vm/gen/main.go index 0c8f805e17..43cecd07a9 100644 --- a/core/vm/gen/main.go +++ b/core/vm/gen/main.go @@ -665,7 +665,10 @@ func (g *generator) emitStackChecks(spec opSpec) { } } -func (g *generator) emitGasCheck(spec opSpec) { +// emitStaticGas charges an opcode's constant gas by splicing the chargeRegularOnly +// body inline (call-free, for the hot path); a zero constant emits nothing. It is +// the static-gas counterpart to emitDynamicGas. +func (g *generator) emitStaticGas(spec opSpec) { if spec.constGas == 0 { return } @@ -677,7 +680,7 @@ func (g *generator) emitGasCheck(spec opSpec) { func (g *generator) emitOpBody(code byte) { spec := g.specs[code] g.emitStackChecks(spec) - g.emitGasCheck(spec) + g.emitStaticGas(spec) // PUSH1-PUSH32 swap their execute function under EIP-4762 (verkle) to charge // code-chunk gas on the immediate bytes. Defer to the table handler there. @@ -724,6 +727,26 @@ func (g *generator) emitInlineOp(code byte) { `) } +// emitDynamicGas emits the dynamic-gas computation and charge shared by the +// direct-call and default cases. gasFn is the dynamic-gas function to invoke: +// a name like "gasKeccak256" for a direct call, or "operation.dynamicGas" for +// the table case. A computation error is wrapped as ErrOutOfGas, then the cost +// is charged through GasBudget.chargeDynamic. The doubled %%w/%%v are not +// generator verbs: Fprintf collapses each %% to one %, leaving a literal +// fmt.Errorf("%w: %v", ...) in the generated code. +func (g *generator) emitDynamicGas(gasFn string) { + g.p(` + var dynamicCost GasCosts + dynamicCost, err = %s(evm, contract, stack, mem, memorySize) + if err != nil { + return nil, fmt.Errorf("%%w: %%v", ErrOutOfGas, err) + } + if err := contract.Gas.chargeDynamic(dynamicCost); err != nil { + return nil, err + } + `, gasFn) +} + // emitDirectCallOp emits an opcode case identical to the default case, except // the handler, dynamic-gas, and memory-size functions are called by name // rather than through the indirect operation.* table pointers. Valid only for @@ -733,11 +756,8 @@ func (g *generator) emitDirectCallOp(code byte) { fns := directCallOps[code] g.p("case %s:\n", spec.name) g.emitStackChecks(spec) - g.emitGasCheck(spec) - // The three %s are the memory-size, dynamic-gas and handler names, in the - // order fns[2], fns[1], fns[0]. The doubled %%w and %%v are not generator - // verbs: Fprintf collapses each %% to a single %, so the generated code ends - // up with a literal fmt.Errorf("%w: %v", ...). + g.emitStaticGas(spec) + // fns[2], fns[1], fns[0] are the memory-size, dynamic-gas and handler names. g.p(` var memorySize uint64 { @@ -749,18 +769,9 @@ func (g *generator) emitDirectCallOp(code byte) { return nil, ErrGasUintOverflow } } - var dynamicCost GasCosts - dynamicCost, err = %s(evm, contract, stack, mem, memorySize) - if err != nil { - return nil, fmt.Errorf("%%w: %%v", ErrOutOfGas, err) - } - if dynamicCost.StateGas == 0 { - if err := contract.Gas.chargeRegularOnly(dynamicCost.RegularGas); err != nil { - return nil, err - } - } else if !contract.Gas.charge(dynamicCost) { - return nil, ErrOutOfGas - } + `, fns[2]) + g.emitDynamicGas(fns[1]) + g.p(` if memorySize > 0 { mem.Resize(memorySize) } @@ -770,7 +781,7 @@ func (g *generator) emitDirectCallOp(code byte) { } pc++ continue mainLoop - `, fns[2], fns[1], fns[0]) + `, fns[0]) } func (g *generator) emitDefault() { @@ -800,18 +811,9 @@ func (g *generator) emitDefault() { return nil, ErrGasUintOverflow } } - var dynamicCost GasCosts - dynamicCost, err = operation.dynamicGas(evm, contract, stack, mem, memorySize) - if err != nil { - return nil, fmt.Errorf("%%w: %%v", ErrOutOfGas, err) - } - if dynamicCost.StateGas == 0 { - if err := contract.Gas.chargeRegularOnly(dynamicCost.RegularGas); err != nil { - return nil, err - } - } else if !contract.Gas.charge(dynamicCost) { - return nil, ErrOutOfGas - } + `) + g.emitDynamicGas("operation.dynamicGas") + g.p(` } if memorySize > 0 { mem.Resize(memorySize) diff --git a/core/vm/interp_gen.go b/core/vm/interp_gen.go index 3a8cd501cc..79c01a5a38 100644 --- a/core/vm/interp_gen.go +++ b/core/vm/interp_gen.go @@ -595,12 +595,8 @@ mainLoop: if err != nil { return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err) } - if dynamicCost.StateGas == 0 { - if err := contract.Gas.chargeRegularOnly(dynamicCost.RegularGas); err != nil { - return nil, err - } - } else if !contract.Gas.charge(dynamicCost) { - return nil, ErrOutOfGas + if err := contract.Gas.chargeDynamic(dynamicCost); err != nil { + return nil, err } if memorySize > 0 { mem.Resize(memorySize) @@ -652,12 +648,8 @@ mainLoop: if err != nil { return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err) } - if dynamicCost.StateGas == 0 { - if err := contract.Gas.chargeRegularOnly(dynamicCost.RegularGas); err != nil { - return nil, err - } - } else if !contract.Gas.charge(dynamicCost) { - return nil, ErrOutOfGas + if err := contract.Gas.chargeDynamic(dynamicCost); err != nil { + return nil, err } if memorySize > 0 { mem.Resize(memorySize) @@ -694,12 +686,8 @@ mainLoop: if err != nil { return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err) } - if dynamicCost.StateGas == 0 { - if err := contract.Gas.chargeRegularOnly(dynamicCost.RegularGas); err != nil { - return nil, err - } - } else if !contract.Gas.charge(dynamicCost) { - return nil, ErrOutOfGas + if err := contract.Gas.chargeDynamic(dynamicCost); err != nil { + return nil, err } if memorySize > 0 { mem.Resize(memorySize) @@ -736,12 +724,8 @@ mainLoop: if err != nil { return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err) } - if dynamicCost.StateGas == 0 { - if err := contract.Gas.chargeRegularOnly(dynamicCost.RegularGas); err != nil { - return nil, err - } - } else if !contract.Gas.charge(dynamicCost) { - return nil, ErrOutOfGas + if err := contract.Gas.chargeDynamic(dynamicCost); err != nil { + return nil, err } if memorySize > 0 { mem.Resize(memorySize) @@ -2561,12 +2545,8 @@ mainLoop: if err != nil { return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err) } - if dynamicCost.StateGas == 0 { - if err := contract.Gas.chargeRegularOnly(dynamicCost.RegularGas); err != nil { - return nil, err - } - } else if !contract.Gas.charge(dynamicCost) { - return nil, ErrOutOfGas + if err := contract.Gas.chargeDynamic(dynamicCost); err != nil { + return nil, err } } if memorySize > 0 { diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 60d0888bd6..56d38c0bbd 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -247,12 +247,8 @@ func (evm *EVM) execTraced(scope *ScopeContext) (ret []byte, err error) { if err != nil { return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err) } - if dynamicCost.StateGas == 0 { - if err := contract.Gas.chargeRegularOnly(dynamicCost.RegularGas); err != nil { - return nil, err - } - } else if !contract.Gas.charge(dynamicCost) { - return nil, ErrOutOfGas + if err := contract.Gas.chargeDynamic(dynamicCost); err != nil { + return nil, err } }