core: apply fixes for 8037

This commit is contained in:
Marius van der Wijden 2026-04-22 20:19:23 +02:00 committed by MariusVanDerWijden
parent a91cb20ae9
commit 0cb107dfd6
4 changed files with 66 additions and 38 deletions

View file

@ -322,7 +322,7 @@ func (st *stateTransition) to() common.Address {
return *st.msg.To return *st.msg.To
} }
func (st *stateTransition) buyGas() error { func (st *stateTransition) buyGas() (uint64, error) {
mgval := new(big.Int).SetUint64(st.msg.GasLimit) mgval := new(big.Int).SetUint64(st.msg.GasLimit)
mgval.Mul(mgval, st.msg.GasPrice) mgval.Mul(mgval, st.msg.GasPrice)
balanceCheck := new(big.Int).Set(mgval) balanceCheck := new(big.Int).Set(mgval)
@ -346,13 +346,10 @@ func (st *stateTransition) buyGas() error {
} }
balanceCheckU256, overflow := uint256.FromBig(balanceCheck) balanceCheckU256, overflow := uint256.FromBig(balanceCheck)
if overflow { if overflow {
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex()) return 0, fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
} }
if have, want := st.state.GetBalance(st.msg.From), balanceCheckU256; have.Cmp(want) < 0 { if have, want := st.state.GetBalance(st.msg.From), balanceCheckU256; have.Cmp(want) < 0 {
return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want) return 0, fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want)
}
if err := st.gp.SubGas(st.msg.GasLimit); err != nil {
return err
} }
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil { if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil {
@ -368,23 +365,23 @@ func (st *stateTransition) buyGas() error {
st.gasRemaining = st.initialBudget.Copy() st.gasRemaining = st.initialBudget.Copy()
mgvalU256, _ := uint256.FromBig(mgval) mgvalU256, _ := uint256.FromBig(mgval)
st.state.SubBalance(st.msg.From, mgvalU256, tracing.BalanceDecreaseGasBuy) st.state.SubBalance(st.msg.From, mgvalU256, tracing.BalanceDecreaseGasBuy)
return nil return st.msg.GasLimit, nil
} }
func (st *stateTransition) preCheck() error { func (st *stateTransition) preCheck() (uint64, error) {
// Only check transactions that are not fake // Only check transactions that are not fake
msg := st.msg msg := st.msg
if !msg.SkipNonceChecks { if !msg.SkipNonceChecks {
// Make sure this transaction's nonce is correct. // Make sure this transaction's nonce is correct.
stNonce := st.state.GetNonce(msg.From) stNonce := st.state.GetNonce(msg.From)
if msgNonce := msg.Nonce; stNonce < msgNonce { if msgNonce := msg.Nonce; stNonce < msgNonce {
return fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooHigh, return 0, fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooHigh,
msg.From.Hex(), msgNonce, stNonce) msg.From.Hex(), msgNonce, stNonce)
} else if stNonce > msgNonce { } else if stNonce > msgNonce {
return fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooLow, return 0, fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooLow,
msg.From.Hex(), msgNonce, stNonce) msg.From.Hex(), msgNonce, stNonce)
} else if stNonce+1 < stNonce { } else if stNonce+1 < stNonce {
return fmt.Errorf("%w: address %v, nonce: %d", ErrNonceMax, return 0, fmt.Errorf("%w: address %v, nonce: %d", ErrNonceMax,
msg.From.Hex(), stNonce) msg.From.Hex(), stNonce)
} }
} }
@ -393,13 +390,13 @@ func (st *stateTransition) preCheck() error {
if !msg.SkipTransactionChecks { if !msg.SkipTransactionChecks {
// Verify tx gas limit does not exceed EIP-7825 cap. // Verify tx gas limit does not exceed EIP-7825 cap.
if !isAmsterdam && isOsaka && msg.GasLimit > params.MaxTxGas { if !isAmsterdam && isOsaka && msg.GasLimit > params.MaxTxGas {
return fmt.Errorf("%w (cap: %d, tx: %d)", ErrGasLimitTooHigh, params.MaxTxGas, msg.GasLimit) return 0, fmt.Errorf("%w (cap: %d, tx: %d)", ErrGasLimitTooHigh, params.MaxTxGas, msg.GasLimit)
} }
// Make sure the sender is an EOA // Make sure the sender is an EOA
code := st.state.GetCode(msg.From) code := st.state.GetCode(msg.From)
_, delegated := types.ParseDelegation(code) _, delegated := types.ParseDelegation(code)
if len(code) > 0 && !delegated { if len(code) > 0 && !delegated {
return fmt.Errorf("%w: address %v, len(code): %d", ErrSenderNoEOA, msg.From.Hex(), len(code)) return 0, fmt.Errorf("%w: address %v, len(code): %d", ErrSenderNoEOA, msg.From.Hex(), len(code))
} }
} }
// Make sure that transaction gasFeeCap is greater than the baseFee (post london) // Make sure that transaction gasFeeCap is greater than the baseFee (post london)
@ -408,21 +405,21 @@ func (st *stateTransition) preCheck() error {
skipCheck := st.evm.Config.NoBaseFee && msg.GasFeeCap.BitLen() == 0 && msg.GasTipCap.BitLen() == 0 skipCheck := st.evm.Config.NoBaseFee && msg.GasFeeCap.BitLen() == 0 && msg.GasTipCap.BitLen() == 0
if !skipCheck { if !skipCheck {
if l := msg.GasFeeCap.BitLen(); l > 256 { if l := msg.GasFeeCap.BitLen(); l > 256 {
return fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", ErrFeeCapVeryHigh, return 0, fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", ErrFeeCapVeryHigh,
msg.From.Hex(), l) msg.From.Hex(), l)
} }
if l := msg.GasTipCap.BitLen(); l > 256 { if l := msg.GasTipCap.BitLen(); l > 256 {
return fmt.Errorf("%w: address %v, maxPriorityFeePerGas bit length: %d", ErrTipVeryHigh, return 0, fmt.Errorf("%w: address %v, maxPriorityFeePerGas bit length: %d", ErrTipVeryHigh,
msg.From.Hex(), l) msg.From.Hex(), l)
} }
if msg.GasFeeCap.Cmp(msg.GasTipCap) < 0 { if msg.GasFeeCap.Cmp(msg.GasTipCap) < 0 {
return fmt.Errorf("%w: address %v, maxPriorityFeePerGas: %s, maxFeePerGas: %s", ErrTipAboveFeeCap, return 0, fmt.Errorf("%w: address %v, maxPriorityFeePerGas: %s, maxFeePerGas: %s", ErrTipAboveFeeCap,
msg.From.Hex(), msg.GasTipCap, msg.GasFeeCap) msg.From.Hex(), msg.GasTipCap, msg.GasFeeCap)
} }
// This will panic if baseFee is nil, but basefee presence is verified // This will panic if baseFee is nil, but basefee presence is verified
// as part of header validation. // as part of header validation.
if msg.GasFeeCap.Cmp(st.evm.Context.BaseFee) < 0 { if msg.GasFeeCap.Cmp(st.evm.Context.BaseFee) < 0 {
return fmt.Errorf("%w: address %v, maxFeePerGas: %s, baseFee: %s", ErrFeeCapTooLow, return 0, fmt.Errorf("%w: address %v, maxFeePerGas: %s, baseFee: %s", ErrFeeCapTooLow,
msg.From.Hex(), msg.GasFeeCap, st.evm.Context.BaseFee) msg.From.Hex(), msg.GasFeeCap, st.evm.Context.BaseFee)
} }
} }
@ -433,17 +430,17 @@ func (st *stateTransition) preCheck() error {
// has it as a non-nillable value, so any msg derived from blob transaction has it non-nil. // has it as a non-nillable value, so any msg derived from blob transaction has it non-nil.
// However, messages created through RPC (eth_call) don't have this restriction. // However, messages created through RPC (eth_call) don't have this restriction.
if msg.To == nil { if msg.To == nil {
return ErrBlobTxCreate return 0, ErrBlobTxCreate
} }
if len(msg.BlobHashes) == 0 { if len(msg.BlobHashes) == 0 {
return ErrMissingBlobHashes return 0, ErrMissingBlobHashes
} }
if isOsaka && len(msg.BlobHashes) > params.BlobTxMaxBlobs { if isOsaka && len(msg.BlobHashes) > params.BlobTxMaxBlobs {
return ErrTooManyBlobs return 0, ErrTooManyBlobs
} }
for i, hash := range msg.BlobHashes { for i, hash := range msg.BlobHashes {
if !kzg4844.IsValidVersionedHash(hash[:]) { if !kzg4844.IsValidVersionedHash(hash[:]) {
return fmt.Errorf("blob %d has invalid hash version", i) return 0, fmt.Errorf("blob %d has invalid hash version", i)
} }
} }
} }
@ -456,7 +453,7 @@ func (st *stateTransition) preCheck() error {
// This will panic if blobBaseFee is nil, but blobBaseFee presence // This will panic if blobBaseFee is nil, but blobBaseFee presence
// is verified as part of header validation. // is verified as part of header validation.
if msg.BlobGasFeeCap.Cmp(st.evm.Context.BlobBaseFee) < 0 { if msg.BlobGasFeeCap.Cmp(st.evm.Context.BlobBaseFee) < 0 {
return fmt.Errorf("%w: address %v blobGasFeeCap: %v, blobBaseFee: %v", ErrBlobFeeCapTooLow, return 0, fmt.Errorf("%w: address %v blobGasFeeCap: %v, blobBaseFee: %v", ErrBlobFeeCapTooLow,
msg.From.Hex(), msg.BlobGasFeeCap, st.evm.Context.BlobBaseFee) msg.From.Hex(), msg.BlobGasFeeCap, st.evm.Context.BlobBaseFee)
} }
} }
@ -465,10 +462,10 @@ func (st *stateTransition) preCheck() error {
// Check that EIP-7702 authorization list signatures are well formed. // Check that EIP-7702 authorization list signatures are well formed.
if msg.SetCodeAuthorizations != nil { if msg.SetCodeAuthorizations != nil {
if msg.To == nil { if msg.To == nil {
return fmt.Errorf("%w (sender %v)", ErrSetCodeTxCreate, msg.From) return 0, fmt.Errorf("%w (sender %v)", ErrSetCodeTxCreate, msg.From)
} }
if len(msg.SetCodeAuthorizations) == 0 { if len(msg.SetCodeAuthorizations) == 0 {
return fmt.Errorf("%w (sender %v)", ErrEmptyAuthList, msg.From) return 0, fmt.Errorf("%w (sender %v)", ErrEmptyAuthList, msg.From)
} }
} }
return st.buyGas() return st.buyGas()
@ -496,7 +493,8 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
// 6. caller has enough balance to cover asset transfer for **topmost** call // 6. caller has enough balance to cover asset transfer for **topmost** call
// Check clauses 1-3, buy gas if everything is correct // Check clauses 1-3, buy gas if everything is correct
if err := st.preCheck(); err != nil { gas, err := st.preCheck()
if err != nil {
return nil, err return nil, err
} }
@ -506,12 +504,33 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
contractCreation = msg.To == nil contractCreation = msg.To == nil
floorDataGas uint64 floorDataGas uint64
) )
if !rules.IsAmsterdam {
if err := st.gp.SubGas(gas); err != nil {
return nil, err
}
}
// Check clauses 4-5, subtract intrinsic gas if everything is correct // Check clauses 4-5, subtract intrinsic gas if everything is correct
cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules, st.evm.Context.CostPerGasByte) cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules, st.evm.Context.CostPerGasByte)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Regular gas check for block inclusion post-amsterdam includes state gas.
if rules.IsAmsterdam {
subGasAmount := msg.GasLimit
if subGasAmount > cost.StateGas {
subGasAmount -= cost.StateGas
} else {
subGasAmount = 0
}
subGasAmount = min(subGasAmount, params.MaxTxGas)
if err := st.gp.SubGas(subGasAmount); err != nil {
return nil, err
}
}
// Compute the floor data cost (EIP-7623), needed for both Prague and Amsterdam validation. // Compute the floor data cost (EIP-7623), needed for both Prague and Amsterdam validation.
if rules.IsPrague { if rules.IsPrague {
floorDataGas, err = FloorDataGas(rules, msg.Data, msg.AccessList) floorDataGas, err = FloorDataGas(rules, msg.Data, msg.AccessList)

View file

@ -141,22 +141,21 @@ func (c *Contract) UseGas(cost GasCosts, logger *tracing.Hooks, reason tracing.G
// RefundGas refunds gas to the contract. // RefundGas refunds gas to the contract.
func (c *Contract) RefundGas(err error, initialRegularGasUsed uint64, gas GasBudget, gasUsed GasUsed, logger *tracing.Hooks, reason tracing.GasChangeReason) { func (c *Contract) RefundGas(err error, initialRegularGasUsed uint64, gas GasBudget, gasUsed GasUsed, logger *tracing.Hooks, reason tracing.GasChangeReason) {
// If the preceding call errored, return the child's state-gas reservoir
// and any net state-gas consumption to the parent. A negative
// gasUsed.StateGas (an unabsorbed SSTORE 0→x→0 refund) is an inflation
// to undo: the matching state mutation was reverted with the child, so
// the refund must not leak back to the parent.
if err != nil { if err != nil {
if gasUsed.StateGas >= 0 { if gasUsed.StateGas > 0 {
gas.StateGas += uint64(gasUsed.StateGas) gas.StateGas += uint64(gasUsed.StateGas)
} else if undo := uint64(-gasUsed.StateGas); gas.StateGas >= undo {
gas.StateGas -= undo
} else {
gas.StateGas = 0
} }
gasUsed.StateGas = 0 gasUsed.StateGas = 0
if gas.StateGasRefund > 0 {
if gas.StateGas >= gas.StateGasRefund {
gas.StateGas -= gas.StateGasRefund
} else {
gas.StateGas = 0
}
gas.StateGasRefund = 0
}
} }
if gas.RegularGas == 0 && gas.StateGas == 0 && gasUsed.StateGas == 0 && gasUsed.RegularGas == 0 { if gas.RegularGas == 0 && gas.StateGas == 0 && gasUsed.StateGas == 0 && gasUsed.RegularGas == 0 && gas.StateGasRefund == 0 {
return return
} }
if logger != nil && logger.OnGasChange != nil && reason != tracing.GasChangeIgnored { if logger != nil && logger.OnGasChange != nil && reason != tracing.GasChangeIgnored {
@ -164,6 +163,9 @@ func (c *Contract) RefundGas(err error, initialRegularGasUsed uint64, gas GasBud
} }
c.Gas.RegularGas += gas.RegularGas c.Gas.RegularGas += gas.RegularGas
c.Gas.StateGas = gas.StateGas c.Gas.StateGas = gas.StateGas
// Propagate the child's applied inline refund so a later ancestor
// error can still undo the reservoir inflation.
c.Gas.StateGasRefund += gas.StateGasRefund
c.GasUsed.StateGas += gasUsed.StateGas c.GasUsed.StateGas += gasUsed.StateGas
c.GasUsed.RegularGas = initialRegularGasUsed + gasUsed.RegularGas c.GasUsed.RegularGas = initialRegularGasUsed + gasUsed.RegularGas
} }
@ -172,6 +174,7 @@ func (c *Contract) RefundGas(err error, initialRegularGasUsed uint64, gas GasBud
func (c *Contract) RefundCreateStateGas(refund uint64) { func (c *Contract) RefundCreateStateGas(refund uint64) {
if refund > 0 { if refund > 0 {
c.Gas.StateGas += refund c.Gas.StateGas += refund
c.Gas.StateGasRefund += refund
c.GasUsed.StateGas -= int64(refund) c.GasUsed.StateGas -= int64(refund)
} }
} }

View file

@ -699,9 +699,11 @@ func gasSStore8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo
if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1) if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1)
// EIP-8037 point (2): refund state gas directly to the reservoir // EIP-8037 point (2): refund state gas directly to the reservoir
// at the SSTORE restoration point (0→x→0 in same tx); not to the // at the SSTORE restoration point (0→x→0 in same tx); not to the
// refund counter, which is capped at gas_used/5. // refund counter, which is capped at gas_used/5. StateGasRefund
// tracks the reservoir inflation so a frame error undoes it.
stateRefund := params.StorageCreationSize * evm.Context.CostPerGasByte stateRefund := params.StorageCreationSize * evm.Context.CostPerGasByte
contract.Gas.StateGas += stateRefund contract.Gas.StateGas += stateRefund
contract.Gas.StateGasRefund += stateRefund
contract.GasUsed.StateGas -= int64(stateRefund) contract.GasUsed.StateGas -= int64(stateRefund)
// Regular portion of the refund still goes through the refund counter. // Regular portion of the refund still goes through the refund counter.
evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929 - params.WarmStorageReadCostEIP2929) evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929 - params.WarmStorageReadCostEIP2929)

View file

@ -19,7 +19,7 @@ package vm
import "fmt" import "fmt"
// GasUsed is the per-frame accumulator for gas consumption. // GasUsed is the per-frame accumulator for gas consumption.
// StateGas is signed because of 0 -> X -> 0 SSTORE refunds. // StateGas is signed, because it can be negative in a 0 -> x -> 0 scenario.
type GasUsed struct { type GasUsed struct {
RegularGas uint64 RegularGas uint64
StateGas int64 StateGas int64
@ -55,6 +55,10 @@ func (g GasCosts) String() string {
type GasBudget struct { type GasBudget struct {
RegularGas uint64 // The leftover gas for execution and state gas usage RegularGas uint64 // The leftover gas for execution and state gas usage
StateGas uint64 // The state gas reservoir StateGas uint64 // The state gas reservoir
// Tracks the gas refunds in this call frame. Needed so we can
// revert the refunds if the call frame reverts.
StateGasRefund uint64
} }
// NewGasBudgetReg creates a GasBudget with the given initial regular gas allowance. // NewGasBudgetReg creates a GasBudget with the given initial regular gas allowance.