mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-19 13:21:37 +00:00
core/vm: fix EIP-8037 CALL state gas ordering
Charge new-account state gas BEFORE the 63/64 child gas allocation rather than after. When state gas spills from an empty reservoir to regular gas, it must reduce the gas available for callGasTemp. Otherwise the spillover exceeds the 1/64 remainder after child gas allocation, causing Underflow → OOG on CALLs that should succeed. This matches nethermind's implementation which calls ConsumeNewAccountCreation() before the 63/64 calculation (EvmInstructions.Call.cs:187-213). Verified: geth+besu in sync through 159 blocks with spamoor load, geth+nethermind in sync through 50+ post-Amsterdam blocks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
51018a536f
commit
c33c10cb1b
2 changed files with 26 additions and 2 deletions
|
|
@ -783,11 +783,13 @@ func (st *stateTransition) blockGasUsed(intrinsicGas, execGasStart vm.GasCosts)
|
|||
execStateUsed := st.gasRemaining.TotalStateGasCharged
|
||||
// Exclude state gas that was charged from regular gas but then reverted.
|
||||
// This gas was consumed from the regular pool but was for state operations
|
||||
// that didn't persist, so it shouldn't count in the regular dimension.
|
||||
// that didn't persist, so it shouldn't count in either dimension for block
|
||||
// accounting (invisible to the block). This matches nethermind's approach.
|
||||
execRegularUsed := totalExecUsed - execStateUsed - st.gasRemaining.RevertedStateGasSpill
|
||||
|
||||
txRegular := intrinsicGas.RegularGas + execRegularUsed
|
||||
txState := intrinsicGas.StateGas + execStateUsed - st.stateGasRefund
|
||||
|
||||
return txRegular, txState
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -295,6 +295,21 @@ func makeCallVariantGasCall(oldCalculatorStateful, oldCalculatorStateless gasFun
|
|||
}
|
||||
}
|
||||
|
||||
// EIP-8037: Charge state gas for new account creation BEFORE the 63/64
|
||||
// child gas allocation. State gas that spills from an empty reservoir to
|
||||
// regular gas must reduce the gas available for callGasTemp, otherwise
|
||||
// the Underflow check in UseGas will fail when the spillover exceeds the
|
||||
// tiny 1/64 remainder after child gas allocation.
|
||||
var stateGasCharged uint64
|
||||
if evm.chainRules.IsAmsterdam && oldStateful.StateGas > 0 {
|
||||
stateGasCharged = oldStateful.StateGas
|
||||
stateGasCost := GasCosts{StateGas: stateGasCharged}
|
||||
if contract.Gas.Underflow(stateGasCost) {
|
||||
return GasCosts{}, ErrOutOfGas
|
||||
}
|
||||
contract.Gas.Sub(stateGasCost)
|
||||
}
|
||||
|
||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, eip150BaseGas.RegularGas, stack.Back(0))
|
||||
if err != nil {
|
||||
return GasCosts{}, err
|
||||
|
|
@ -331,6 +346,13 @@ func makeCallVariantGasCall(oldCalculatorStateful, oldCalculatorStateless gasFun
|
|||
return GasCosts{}, ErrGasUintOverflow
|
||||
}
|
||||
|
||||
return GasCosts{RegularGas: totalCost, StateGas: oldStateful.StateGas}, nil
|
||||
// If state gas was already charged directly (Amsterdam), don't include
|
||||
// it in the returned cost — it would be double-charged by the
|
||||
// interpreter's UseGas/Sub which increments TotalStateGasCharged again.
|
||||
returnedStateGas := oldStateful.StateGas
|
||||
if stateGasCharged > 0 {
|
||||
returnedStateGas = 0
|
||||
}
|
||||
return GasCosts{RegularGas: totalCost, StateGas: returnedStateGas}, nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue