mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-20 05:41:35 +00:00
feat(core): implement EIP-7623 increase calldata cost 30946 (#2031)
Link to spec: https://eips.ethereum.org/EIPS/eip-7623 --------- Co-authored-by: Marius van der Wijden <m.vanderwijden@live.de> Co-authored-by: lightclient <14004106+lightclient@users.noreply.github.com> Co-authored-by: lightclient <lightclient@protonmail.com>
This commit is contained in:
parent
99749a2381
commit
9dba15a673
6 changed files with 85 additions and 28 deletions
|
|
@ -76,6 +76,10 @@ var (
|
|||
// than required to start the invocation.
|
||||
ErrIntrinsicGas = errors.New("intrinsic gas too low")
|
||||
|
||||
// ErrFloorDataGas is returned if the transaction is specified to use less gas
|
||||
// than required for the data floor cost.
|
||||
ErrFloorDataGas = errors.New("insufficient gas for floor data gas cost")
|
||||
|
||||
// ErrTxTypeNotSupported is returned if a transaction is not supported in the
|
||||
// current network configuration.
|
||||
ErrTxTypeNotSupported = types.ErrTxTypeNotSupported
|
||||
|
|
|
|||
|
|
@ -231,9 +231,9 @@ func TestStateProcessorErrors(t *testing.T) {
|
|||
},
|
||||
{ // ErrMaxInitCodeSizeExceeded
|
||||
txs: []*types.Transaction{
|
||||
mkDynamicCreationTx(0, 500000, common.Big0, big.NewInt(params.InitialBaseFee), tooBigInitCode[:]),
|
||||
mkDynamicCreationTx(0, 520000, common.Big0, big.NewInt(params.InitialBaseFee), tooBigInitCode[:]),
|
||||
},
|
||||
want: "could not apply tx 0 [0x42c498ac252e8943de9e8ab3267113b0a0cde54543df1ab0711b0d87ccca3e03]: max initcode size exceeded: code size 49153 limit 49152",
|
||||
want: "could not apply tx 0 [0x41d48b664cf891e625a16696a90e892ba3857c0b5ea759c3f2bdb4158338cb85]: max initcode size exceeded: code size 49153 limit 49152",
|
||||
},
|
||||
{ // ErrIntrinsicGas: Not enough gas to cover init code
|
||||
txs: []*types.Transaction{
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
|
|
@ -71,19 +72,15 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
|
|||
// Bump the required gas by the amount of transactional data
|
||||
if dataLen > 0 {
|
||||
// Zero and non-zero bytes are priced differently
|
||||
var nz uint64
|
||||
for _, byt := range data {
|
||||
if byt != 0 {
|
||||
nz++
|
||||
}
|
||||
}
|
||||
z := uint64(bytes.Count(data, []byte{0}))
|
||||
nz := dataLen - z
|
||||
|
||||
// Make sure we don't exceed uint64 for all data combinations
|
||||
if (math.MaxUint64-gas)/params.TxDataNonZeroGas < nz {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
gas += nz * params.TxDataNonZeroGas
|
||||
|
||||
z := dataLen - nz
|
||||
if (math.MaxUint64-gas)/params.TxDataZeroGas < z {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
|
|
@ -107,6 +104,21 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
|
|||
return gas, nil
|
||||
}
|
||||
|
||||
// FloorDataGas computes the minimum gas required for a transaction based on its data tokens (EIP-7623).
|
||||
func FloorDataGas(data []byte) (uint64, error) {
|
||||
var (
|
||||
z = uint64(bytes.Count(data, []byte{0}))
|
||||
nz = uint64(len(data)) - z
|
||||
tokens = nz*params.TxTokenPerNonZeroByte + z
|
||||
)
|
||||
// Check for overflow
|
||||
if (math.MaxUint64-params.TxGas)/params.TxCostFloorPerToken < tokens {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
// Minimum gas required for a transaction based on its data tokens (EIP-7623).
|
||||
return params.TxGas + tokens*params.TxCostFloorPerToken, nil
|
||||
}
|
||||
|
||||
// toWordSize returns the ceiled word size required for init code payment calculation.
|
||||
func toWordSize(size uint64) uint64 {
|
||||
if size > math.MaxUint64-31 {
|
||||
|
|
@ -384,6 +396,7 @@ func (st *StateTransition) TransitionDb(owner common.Address) (*ExecutionResult,
|
|||
sender = vm.AccountRef(st.from())
|
||||
rules = st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber)
|
||||
contractCreation = msg.To == nil
|
||||
floorDataGas uint64
|
||||
)
|
||||
|
||||
// Check clauses 4-5, subtract intrinsic gas if everything is correct
|
||||
|
|
@ -394,6 +407,16 @@ func (st *StateTransition) TransitionDb(owner common.Address) (*ExecutionResult,
|
|||
if st.gasRemaining < gas {
|
||||
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining, gas)
|
||||
}
|
||||
// Gas limit suffices for the floor data cost (EIP-7623)
|
||||
if rules.IsPrague {
|
||||
floorDataGas, err = FloorDataGas(msg.Data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if msg.GasLimit < floorDataGas {
|
||||
return nil, fmt.Errorf("%w: have %d, want %d", ErrFloorDataGas, msg.GasLimit, floorDataGas)
|
||||
}
|
||||
}
|
||||
if t := st.evm.Config.Tracer; t != nil && t.OnGasChange != nil {
|
||||
t.OnGasChange(st.gasRemaining, st.gasRemaining-gas, tracing.GasChangeTxIntrinsicGas)
|
||||
}
|
||||
|
|
@ -449,13 +472,20 @@ func (st *StateTransition) TransitionDb(owner common.Address) (*ExecutionResult,
|
|||
ret, st.gasRemaining, vmerr = st.evm.Call(sender, st.to(), msg.Data, st.gasRemaining, value)
|
||||
}
|
||||
|
||||
if !rules.IsEIP1559 {
|
||||
// Before EIP-3529: refunds were capped to gasUsed / 2
|
||||
st.refundGas(params.RefundQuotient)
|
||||
} else {
|
||||
// After EIP-3529: refunds are capped to gasUsed / 5
|
||||
st.refundGas(params.RefundQuotientEIP3529)
|
||||
// Compute refund counter, capped to a refund quotient.
|
||||
gasRefund := st.calcRefund()
|
||||
st.gasRemaining += gasRefund
|
||||
if rules.IsPrague {
|
||||
// After EIP-7623: Data-heavy transactions pay the floor gas.
|
||||
if st.gasUsed() < floorDataGas {
|
||||
prev := st.gasRemaining
|
||||
st.gasRemaining = st.initialGas - floorDataGas
|
||||
if t := st.evm.Config.Tracer; t != nil && t.OnGasChange != nil {
|
||||
t.OnGasChange(prev, st.gasRemaining, tracing.GasChangeTxDataFloor)
|
||||
}
|
||||
}
|
||||
}
|
||||
st.returnGas()
|
||||
|
||||
// GasPrice of special tx is always 0, so we can skip AddBalance
|
||||
if !types.IsSpecialTx(msg.To) {
|
||||
|
|
@ -540,22 +570,31 @@ func (st *StateTransition) applyAuthorization(msg *Message, auth *types.SetCodeA
|
|||
return nil
|
||||
}
|
||||
|
||||
func (st *StateTransition) refundGas(refundQuotient uint64) {
|
||||
// Apply refund counter, capped to a refund quotient
|
||||
refund := st.gasUsed() / refundQuotient
|
||||
// calcRefund computes refund counter, capped to a refund quotient.
|
||||
func (st *StateTransition) calcRefund() uint64 {
|
||||
var refund uint64
|
||||
if !st.evm.ChainConfig().IsEIP1559(st.evm.Context.BlockNumber) {
|
||||
// Before EIP-3529: refunds were capped to gasUsed / 2
|
||||
refund = st.gasUsed() / params.RefundQuotient
|
||||
} else {
|
||||
// After EIP-3529: refunds are capped to gasUsed / 5
|
||||
refund = st.gasUsed() / params.RefundQuotientEIP3529
|
||||
}
|
||||
if refund > st.state.GetRefund() {
|
||||
refund = st.state.GetRefund()
|
||||
}
|
||||
|
||||
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && refund > 0 {
|
||||
st.evm.Config.Tracer.OnGasChange(st.gasRemaining, st.gasRemaining+refund, tracing.GasChangeTxRefunds)
|
||||
}
|
||||
return refund
|
||||
}
|
||||
|
||||
st.gasRemaining += refund
|
||||
|
||||
// returnGas returns ETH for remaining gas,
|
||||
// exchanged at the original rate.
|
||||
func (st *StateTransition) returnGas() {
|
||||
if st.msg.BalanceTokenFee == nil {
|
||||
// Return ETH for remaining gas, exchanged at the original rate.
|
||||
remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gasRemaining), st.msg.GasPrice)
|
||||
remaining := new(big.Int).SetUint64(st.gasRemaining)
|
||||
remaining.Mul(remaining, st.msg.GasPrice)
|
||||
st.state.AddBalance(st.from(), remaining, tracing.BalanceIncreaseGasReturn)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -299,6 +299,9 @@ const (
|
|||
GasChangeCallStorageColdAccess GasChangeReason = 13
|
||||
// GasChangeCallFailedExecution is the burning of the remaining gas when the execution failed without a revert.
|
||||
GasChangeCallFailedExecution GasChangeReason = 14
|
||||
// GasChangeTxDataFloor is the amount of extra gas the transaction has to pay to reach the minimum gas requirement for the
|
||||
// transaction data. This change will always be a negative change.
|
||||
GasChangeTxDataFloor GasChangeReason = 19
|
||||
|
||||
// GasChangeIgnored is a special value that can be used to indicate that the gas change should be ignored as
|
||||
// it will be "manually" tracked by a direct emit of the gas change event.
|
||||
|
|
|
|||
|
|
@ -108,11 +108,6 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
|||
if tx.GasPrice().Sign() == 0 {
|
||||
return ErrZeroGasPrice
|
||||
}
|
||||
// Ensure the gas price is high enough to cover the requirement of the calling
|
||||
// pool and/or block producer
|
||||
if tx.GasTipCapIntCmp(opts.MinTip) < 0 {
|
||||
return fmt.Errorf("%w: tip needed %v, tip permitted %v", ErrUnderpriced, opts.MinTip, tx.GasTipCap())
|
||||
}
|
||||
// Ensure the transaction has more gas than the bare minimum needed to
|
||||
// cover the transaction metadata
|
||||
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, true, rules.IsEIP1559)
|
||||
|
|
@ -122,6 +117,20 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
|||
if tx.Gas() < intrGas {
|
||||
return fmt.Errorf("%w: needed %v, allowed %v", core.ErrIntrinsicGas, intrGas, tx.Gas())
|
||||
}
|
||||
// Ensure the transaction can cover floor data gas.
|
||||
if rules.IsPrague {
|
||||
floorDataGas, err := core.FloorDataGas(tx.Data())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tx.Gas() < floorDataGas {
|
||||
return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrFloorDataGas, tx.Gas(), floorDataGas)
|
||||
}
|
||||
}
|
||||
// Ensure the gas price is high enough to cover the requirement of the calling pool
|
||||
if tx.GasTipCapIntCmp(opts.MinTip) < 0 {
|
||||
return fmt.Errorf("%w: tip needed %v, tip permitted %v", ErrUnderpriced, opts.MinTip, tx.GasTipCap())
|
||||
}
|
||||
if tx.Type() == types.SetCodeTxType {
|
||||
if len(tx.SetCodeAuthorizations()) == 0 {
|
||||
return fmt.Errorf("set code tx must have at least one authorization tuple")
|
||||
|
|
|
|||
|
|
@ -65,6 +65,8 @@ const (
|
|||
SuicideRefundGas uint64 = 24000 // Refunded following a suicide operation.
|
||||
MemoryGas uint64 = 3 // Times the address of the (highest referenced byte in memory + 1). NOTE: referencing happens on read, write and in instructions such as RETURN and CALL.
|
||||
|
||||
TxTokenPerNonZeroByte uint64 = 4 // Token cost per non-zero byte as specified by EIP-7623.
|
||||
TxCostFloorPerToken uint64 = 10 // Cost floor per byte of data as specified by EIP-7623.
|
||||
TxAccessListAddressGas uint64 = 2400 // Per address specified in EIP 2930 access list
|
||||
TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in EIP 2930 access list
|
||||
TxAuthTupleGas uint64 = 12500 // Per auth tuple code specified in EIP-7702
|
||||
|
|
|
|||
Loading…
Reference in a new issue