core/vm, core/vm/gen: charge dynamic gas through a shared chargeDynamic helper

This commit is contained in:
jonny rhea 2026-06-29 17:45:01 -05:00
parent 2e0035a63a
commit bfa0bfdc7c
4 changed files with 59 additions and 68 deletions

View file

@ -94,6 +94,19 @@ func (g *GasBudget) chargeRegularOnly(r uint64) error {
return nil 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 // CanAfford reports whether the running budget can cover the given cost vector
// without going out of gas. // without going out of gas.
func (g GasBudget) CanAfford(cost GasCosts) bool { func (g GasBudget) CanAfford(cost GasCosts) bool {

View file

@ -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 { if spec.constGas == 0 {
return return
} }
@ -677,7 +680,7 @@ func (g *generator) emitGasCheck(spec opSpec) {
func (g *generator) emitOpBody(code byte) { func (g *generator) emitOpBody(code byte) {
spec := g.specs[code] spec := g.specs[code]
g.emitStackChecks(spec) g.emitStackChecks(spec)
g.emitGasCheck(spec) g.emitStaticGas(spec)
// PUSH1-PUSH32 swap their execute function under EIP-4762 (verkle) to charge // 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. // 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 // emitDirectCallOp emits an opcode case identical to the default case, except
// the handler, dynamic-gas, and memory-size functions are called by name // the handler, dynamic-gas, and memory-size functions are called by name
// rather than through the indirect operation.* table pointers. Valid only for // rather than through the indirect operation.* table pointers. Valid only for
@ -733,11 +756,8 @@ func (g *generator) emitDirectCallOp(code byte) {
fns := directCallOps[code] fns := directCallOps[code]
g.p("case %s:\n", spec.name) g.p("case %s:\n", spec.name)
g.emitStackChecks(spec) g.emitStackChecks(spec)
g.emitGasCheck(spec) g.emitStaticGas(spec)
// The three %s are the memory-size, dynamic-gas and handler names, in the // fns[2], fns[1], fns[0] are the memory-size, dynamic-gas and handler names.
// 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.p(` g.p(`
var memorySize uint64 var memorySize uint64
{ {
@ -749,18 +769,9 @@ func (g *generator) emitDirectCallOp(code byte) {
return nil, ErrGasUintOverflow return nil, ErrGasUintOverflow
} }
} }
var dynamicCost GasCosts `, fns[2])
dynamicCost, err = %s(evm, contract, stack, mem, memorySize) g.emitDynamicGas(fns[1])
if err != nil { g.p(`
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 memorySize > 0 { if memorySize > 0 {
mem.Resize(memorySize) mem.Resize(memorySize)
} }
@ -770,7 +781,7 @@ func (g *generator) emitDirectCallOp(code byte) {
} }
pc++ pc++
continue mainLoop continue mainLoop
`, fns[2], fns[1], fns[0]) `, fns[0])
} }
func (g *generator) emitDefault() { func (g *generator) emitDefault() {
@ -800,18 +811,9 @@ func (g *generator) emitDefault() {
return nil, ErrGasUintOverflow return nil, ErrGasUintOverflow
} }
} }
var dynamicCost GasCosts `)
dynamicCost, err = operation.dynamicGas(evm, contract, stack, mem, memorySize) g.emitDynamicGas("operation.dynamicGas")
if err != nil { g.p(`
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 memorySize > 0 { if memorySize > 0 {
mem.Resize(memorySize) mem.Resize(memorySize)

View file

@ -595,12 +595,8 @@ mainLoop:
if err != nil { if err != nil {
return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err) return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
} }
if dynamicCost.StateGas == 0 { if err := contract.Gas.chargeDynamic(dynamicCost); err != nil {
if err := contract.Gas.chargeRegularOnly(dynamicCost.RegularGas); err != nil { return nil, err
return nil, err
}
} else if !contract.Gas.charge(dynamicCost) {
return nil, ErrOutOfGas
} }
if memorySize > 0 { if memorySize > 0 {
mem.Resize(memorySize) mem.Resize(memorySize)
@ -652,12 +648,8 @@ mainLoop:
if err != nil { if err != nil {
return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err) return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
} }
if dynamicCost.StateGas == 0 { if err := contract.Gas.chargeDynamic(dynamicCost); err != nil {
if err := contract.Gas.chargeRegularOnly(dynamicCost.RegularGas); err != nil { return nil, err
return nil, err
}
} else if !contract.Gas.charge(dynamicCost) {
return nil, ErrOutOfGas
} }
if memorySize > 0 { if memorySize > 0 {
mem.Resize(memorySize) mem.Resize(memorySize)
@ -694,12 +686,8 @@ mainLoop:
if err != nil { if err != nil {
return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err) return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
} }
if dynamicCost.StateGas == 0 { if err := contract.Gas.chargeDynamic(dynamicCost); err != nil {
if err := contract.Gas.chargeRegularOnly(dynamicCost.RegularGas); err != nil { return nil, err
return nil, err
}
} else if !contract.Gas.charge(dynamicCost) {
return nil, ErrOutOfGas
} }
if memorySize > 0 { if memorySize > 0 {
mem.Resize(memorySize) mem.Resize(memorySize)
@ -736,12 +724,8 @@ mainLoop:
if err != nil { if err != nil {
return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err) return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
} }
if dynamicCost.StateGas == 0 { if err := contract.Gas.chargeDynamic(dynamicCost); err != nil {
if err := contract.Gas.chargeRegularOnly(dynamicCost.RegularGas); err != nil { return nil, err
return nil, err
}
} else if !contract.Gas.charge(dynamicCost) {
return nil, ErrOutOfGas
} }
if memorySize > 0 { if memorySize > 0 {
mem.Resize(memorySize) mem.Resize(memorySize)
@ -2561,12 +2545,8 @@ mainLoop:
if err != nil { if err != nil {
return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err) return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
} }
if dynamicCost.StateGas == 0 { if err := contract.Gas.chargeDynamic(dynamicCost); err != nil {
if err := contract.Gas.chargeRegularOnly(dynamicCost.RegularGas); err != nil { return nil, err
return nil, err
}
} else if !contract.Gas.charge(dynamicCost) {
return nil, ErrOutOfGas
} }
} }
if memorySize > 0 { if memorySize > 0 {

View file

@ -247,12 +247,8 @@ func (evm *EVM) execTraced(scope *ScopeContext) (ret []byte, err error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err) return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
} }
if dynamicCost.StateGas == 0 { if err := contract.Gas.chargeDynamic(dynamicCost); err != nil {
if err := contract.Gas.chargeRegularOnly(dynamicCost.RegularGas); err != nil { return nil, err
return nil, err
}
} else if !contract.Gas.charge(dynamicCost) {
return nil, ErrOutOfGas
} }
} }