core: fix the incorrect regular gas used

This commit is contained in:
Gary Rong 2026-05-21 20:48:02 +08:00
parent 22bfedc4b3
commit 324ccdffe1
4 changed files with 28 additions and 21 deletions

View file

@ -737,8 +737,12 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
)
if contractCreation {
var result vm.GasBudget
// Capture the forwarded regular-gas amount BEFORE ForwardAll consumes
// it, so Absorb can back out state-gas spillover from
// UsedRegularGas per EIP-8037 (EELS semantics).
forwardedR := st.gasRemaining.RegularGas
ret, _, result, vmerr = st.evm.Create(msg.From, msg.Data, st.gasRemaining.ForwardAll(), value)
st.gasRemaining.Absorb(result)
st.gasRemaining.Absorb(result, forwardedR)
} else {
// Increment the nonce for the next transaction.
st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeEoACall)
@ -756,8 +760,9 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
}
// Execute the transaction's call.
var result vm.GasBudget
forwardedR := st.gasRemaining.RegularGas
ret, result, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining.ForwardAll(), value)
st.gasRemaining.Absorb(result)
st.gasRemaining.Absorb(result, forwardedR)
}
// If this was a failed contract creation, refund the account creation costs.
if rules.IsAmsterdam {

View file

@ -162,11 +162,10 @@ func (c *Contract) refundState(s uint64, logger *tracing.Hooks, reason tracing.G
}
}
// refundGas absorbs a sub-call's leftover GasBudget into this contract's gas
// state. Thin wrapper around GasBudget.Absorb with tracer integration.
func (c *Contract) refundGas(child GasBudget, logger *tracing.Hooks, reason tracing.GasChangeReason) {
// refundGas absorbs a sub-call's leftover GasBudget into this contract's gas state.
func (c *Contract) refundGas(child GasBudget, forwarded uint64, logger *tracing.Hooks, reason tracing.GasChangeReason) {
prior := c.Gas
c.Gas.Absorb(child)
c.Gas.Absorb(child, forwarded)
if logger.HasGasHook() && reason != tracing.GasChangeIgnored {
logger.EmitGasChange(prior.AsTracing(), c.Gas.AsTracing(), reason)
}

View file

@ -293,18 +293,21 @@ func (g GasBudget) Exit(err error) GasBudget {
}
// Absorb merges a sub-call's leftover GasBudget into this (caller's) running
// budget. The caller's UsedRegularGas is reclaimed by the unused forwarded
// regular gas (which was pre-charged in full at call entry); the state
// reservoir is overwritten with the child's leftover; and the child's signed
// net state-gas usage is added to the caller's accumulator.
// budget. Additionally, it does an EIP-8037 spillover correction:
// state-gas that spilled into the regular pool inside the child frame is
// excluded from the UsedRegularGas.
//
// Invariant maintained by all callers: at the moment of this call, the
// caller's UsedRegularGas already accounts for the FULL forwarded regular
// gas (as if the child had consumed all of it). On halt, child.RegularGas
// is 0 so the reclaim is a no-op.
func (g *GasBudget) Absorb(child GasBudget) {
// spillover = forwarded - child.RegularGas - child.UsedRegularGas
//
// forwarded is the regular-gas amount that was passed to the child at call
// entry (i.e., the regular initial of the child's GasBudget).
func (g *GasBudget) Absorb(child GasBudget, forwarded uint64) {
spillover := forwarded - child.RegularGas - child.UsedRegularGas
g.UsedRegularGas -= child.RegularGas
g.RegularGas += child.RegularGas
g.StateGas = child.StateGas
g.UsedStateGas += child.UsedStateGas
g.UsedRegularGas -= spillover
}

View file

@ -675,7 +675,7 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
}
scope.Stack.push(&stackvalue)
scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
scope.Contract.refundGas(result, forward, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
if evm.chainRules.IsAmsterdam && suberr != nil {
scope.Contract.Gas.RefundState(params.AccountCreationSize * evm.Context.CostPerStateByte)
}
@ -710,7 +710,7 @@ func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
stackvalue.SetBytes(addr.Bytes())
}
scope.Stack.push(&stackvalue)
scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
scope.Contract.refundGas(result, forward, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
// If the creation frame reverts or halts exceptionally, the charged state-gas
// is refilled back to the state reservoir in Amsterdam.
@ -760,7 +760,7 @@ func opCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
if err == nil || err == ErrExecutionReverted {
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
}
scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
scope.Contract.refundGas(result, gas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
// If the call frame reverts or halts exceptionally, the charged state-gas
// is refilled back to the state reservoir in Amsterdam.
@ -807,7 +807,7 @@ func opCallCode(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
}
scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
scope.Contract.refundGas(result, gas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
evm.returnData = ret
return ret, nil
@ -836,7 +836,7 @@ func opDelegateCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
if err == nil || err == ErrExecutionReverted {
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
}
scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
scope.Contract.refundGas(result, gas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
evm.returnData = ret
return ret, nil
@ -866,7 +866,7 @@ func opStaticCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
}
scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
scope.Contract.refundGas(result, gas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
evm.returnData = ret
return ret, nil