Skip to content

Commit

Permalink
Implement calldata cost increase per EIP-7623 (#13080) (#13407)
Browse files Browse the repository at this point in the history
  • Loading branch information
somnathb1 authored Jan 14, 2025
1 parent 24b8e92 commit 8288f26
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 26 deletions.
19 changes: 12 additions & 7 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ type Message interface {

// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
// TODO: convert the input to a struct
func IntrinsicGas(data []byte, accessList types2.AccessList, isContractCreation bool, isHomestead, isEIP2028, isEIP3860 bool, authorizationsLen uint64) (uint64, error) {
func IntrinsicGas(data []byte, accessList types2.AccessList, isContractCreation bool, isHomestead, isEIP2028, isEIP3860, isPrague bool, authorizationsLen uint64) (uint64, uint64, error) {
// Zero and non-zero bytes are priced differently
dataLen := uint64(len(data))
dataNonZeroLen := uint64(0)
Expand All @@ -114,11 +114,11 @@ func IntrinsicGas(data []byte, accessList types2.AccessList, isContractCreation
}
}

gas, status := txpoolcfg.CalcIntrinsicGas(dataLen, dataNonZeroLen, authorizationsLen, accessList, isContractCreation, isHomestead, isEIP2028, isEIP3860)
gas, floorGas7623, status := txpoolcfg.CalcIntrinsicGas(dataLen, dataNonZeroLen, authorizationsLen, accessList, isContractCreation, isHomestead, isEIP2028, isEIP3860, isPrague)
if status != txpoolcfg.Success {
return 0, ErrGasUintOverflow
return 0, 0, ErrGasUintOverflow
}
return gas, nil
return gas, floorGas7623, nil
}

// NewStateTransition initialises and returns a new state transition object.
Expand Down Expand Up @@ -417,12 +417,12 @@ func (st *StateTransition) TransitionDb(refunds bool, gasBailout bool) (*evmtype
}

// Check clauses 4-5, subtract intrinsic gas if everything is correct
gas, err := IntrinsicGas(st.data, accessTuples, contractCreation, rules.IsHomestead, rules.IsIstanbul, isEIP3860, uint64(len(auths)))
gas, floorGas7623, err := IntrinsicGas(st.data, accessTuples, contractCreation, rules.IsHomestead, rules.IsIstanbul, isEIP3860, rules.IsPrague, uint64(len(auths)))
if err != nil {
return nil, err
}
if st.gasRemaining < gas {
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining, gas)
if st.gasRemaining < gas || st.gasRemaining < floorGas7623 {
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining, max(gas, floorGas7623))
}
st.gasRemaining -= gas

Expand Down Expand Up @@ -457,6 +457,11 @@ func (st *StateTransition) TransitionDb(refunds bool, gasBailout bool) (*evmtype
} else {
ret, st.gasRemaining, vmerr = st.evm.Call(sender, st.to(), st.data, st.gasRemaining, st.value, bailout)
}
gasUsed := st.gasUsed()
if gasUsed < floorGas7623 && rules.IsPrague {
gasUsed = floorGas7623
st.gasRemaining = st.initialGas - gasUsed
}
if refunds && !gasBailout {
if rules.IsLondon {
// After EIP-3529: refunds are capped to gasUsed / 5
Expand Down
1 change: 1 addition & 0 deletions erigon-lib/common/fixedgas/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const (
TxDataNonZeroGasEIP2028 uint64 = 16 // Per byte of non zero data attached to a transaction after EIP 2028 (part in Istanbul)
TxAccessListAddressGas uint64 = 2400 // Per address specified in EIP 2930 access list
TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in EIP 2930 access list
TxTotalCostFloorPerToken uint64 = 10 // Per token of calldata in a transaction, as a minimum the txn must pay (EIP-7623)

MaxCodeSize = 24576 // Maximum bytecode to permit for a contract

Expand Down
12 changes: 10 additions & 2 deletions erigon-lib/txpool/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,7 @@ func (p *TxPool) best(n uint16, txs *types.TxsRlp, tx kv.Tx, onTopOf, availableG
best := p.pending.best

isShanghai := p.isShanghai() || p.isAgra()
isPrague := p.isPrague()

txs.Resize(uint(cmp.Min(int(n), len(best.ms))))
var toRemove []*metaTx
Expand Down Expand Up @@ -774,7 +775,10 @@ func (p *TxPool) best(n uint16, txs *types.TxsRlp, tx kv.Tx, onTopOf, availableG
// not an exact science using intrinsic gas but as close as we could hope for at
// this stage
authorizationLen := uint64(len(mt.Tx.Authorizations))
intrinsicGas, _ := txpoolcfg.CalcIntrinsicGas(uint64(mt.Tx.DataLen), uint64(mt.Tx.DataNonZeroLen), authorizationLen, nil, mt.Tx.Creation, true, true, isShanghai)
intrinsicGas, floorGas, _ := txpoolcfg.CalcIntrinsicGas(uint64(mt.Tx.DataLen), uint64(mt.Tx.DataNonZeroLen), authorizationLen, nil, mt.Tx.Creation, true, true, isShanghai, isPrague)
if isPrague && floorGas > intrinsicGas {
intrinsicGas = floorGas
}
if intrinsicGas > availableGas {
// we might find another TX with a low enough intrinsic gas to include so carry on
continue
Expand Down Expand Up @@ -918,7 +922,11 @@ func (p *TxPool) validateTx(txn *types.TxSlot, isLocal bool, stateCache kvcache.
}
return txpoolcfg.UnderPriced
}
gas, reason := txpoolcfg.CalcIntrinsicGas(uint64(txn.DataLen), uint64(txn.DataNonZeroLen), uint64(authorizationLen), nil, txn.Creation, true, true, isShanghai)
gas, floorGas, reason := txpoolcfg.CalcIntrinsicGas(uint64(txn.DataLen), uint64(txn.DataNonZeroLen), uint64(authorizationLen), nil, txn.Creation, true, true, isShanghai, p.isPrague())
if p.isPrague() && floorGas > gas {
gas = floorGas
}

if txn.Traced {
p.logger.Info(fmt.Sprintf("TX TRACING: validateTx intrinsic gas idHash=%x gas=%d", txn.IDHash, gas))
}
Expand Down
3 changes: 2 additions & 1 deletion erigon-lib/txpool/pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,8 @@ func TestShanghaiIntrinsicGas(t *testing.T) {

for name, c := range cases {
t.Run(name, func(t *testing.T) {
gas, reason := txpoolcfg.CalcIntrinsicGas(c.dataLen, c.dataNonZeroLen, c.authorizationsLen, nil, c.creation, true, true, c.isShanghai)
// Todo (@somnathb1) - Factor in EIP-7623
gas, _, reason := txpoolcfg.CalcIntrinsicGas(c.dataLen, c.dataNonZeroLen, c.authorizationsLen, nil, c.creation, true, true, c.isShanghai, false)
if reason != txpoolcfg.Success {
t.Errorf("expected success but got reason %v", reason)
}
Expand Down
43 changes: 28 additions & 15 deletions erigon-lib/txpool/txpoolcfg/txpoolcfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,13 @@ func (r DiscardReason) String() string {

// CalcIntrinsicGas computes the 'intrinsic gas' for a message with the given data.
// TODO: move input data to a struct
func CalcIntrinsicGas(dataLen, dataNonZeroLen, authorizationsLen uint64, accessList types.AccessList, isContractCreation, isHomestead, isEIP2028, isShanghai bool) (uint64, DiscardReason) {
func CalcIntrinsicGas(dataLen, dataNonZeroLen, authorizationsLen uint64, accessList types.AccessList, isContractCreation, isHomestead, isEIP2028, isShanghai, isPrague bool) (gas uint64, floorGas7623 uint64, d DiscardReason) {
// Set the starting gas for the raw transaction
var gas uint64
if isContractCreation && isHomestead {
gas = fixedgas.TxGasContractCreation
} else {
gas = fixedgas.TxGas
floorGas7623 = fixedgas.TxGas
}
// Bump the required gas by the amount of transactional data
if dataLen > 0 {
Expand All @@ -208,68 +208,81 @@ func CalcIntrinsicGas(dataLen, dataNonZeroLen, authorizationsLen uint64, accessL

product, overflow := emath.SafeMul(nz, nonZeroGas)
if overflow {
return 0, GasUintOverflow
return 0, 0, GasUintOverflow
}
gas, overflow = emath.SafeAdd(gas, product)
if overflow {
return 0, GasUintOverflow
return 0, 0, GasUintOverflow
}

z := dataLen - nz

product, overflow = emath.SafeMul(z, fixedgas.TxDataZeroGas)
if overflow {
return 0, GasUintOverflow
return 0, 0, GasUintOverflow
}
gas, overflow = emath.SafeAdd(gas, product)
if overflow {
return 0, GasUintOverflow
return 0, 0, GasUintOverflow
}

if isContractCreation && isShanghai {
numWords := toWordSize(dataLen)
product, overflow = emath.SafeMul(numWords, fixedgas.InitCodeWordGas)
if overflow {
return 0, GasUintOverflow
return 0, 0, GasUintOverflow
}
gas, overflow = emath.SafeAdd(gas, product)
if overflow {
return 0, GasUintOverflow
return 0, 0, GasUintOverflow
}
}

// EIP-7623
if isPrague {
tokenLen := dataLen + 3*nz
dataGas, overflow := emath.SafeMul(tokenLen, fixedgas.TxTotalCostFloorPerToken)
if overflow {
return 0, 0, GasUintOverflow
}
floorGas7623, overflow = emath.SafeAdd(floorGas7623, dataGas)
if overflow {
return 0, 0, GasUintOverflow
}
}
}
if accessList != nil {
product, overflow := emath.SafeMul(uint64(len(accessList)), fixedgas.TxAccessListAddressGas)
if overflow {
return 0, GasUintOverflow
return 0, 0, GasUintOverflow
}
gas, overflow = emath.SafeAdd(gas, product)
if overflow {
return 0, GasUintOverflow
return 0, 0, GasUintOverflow
}

product, overflow = emath.SafeMul(uint64(accessList.StorageKeys()), fixedgas.TxAccessListStorageKeyGas)
if overflow {
return 0, GasUintOverflow
return 0, 0, GasUintOverflow
}
gas, overflow = emath.SafeAdd(gas, product)
if overflow {
return 0, GasUintOverflow
return 0, 0, GasUintOverflow
}
}

// Add the cost of authorizations
product, overflow := emath.SafeMul(authorizationsLen, fixedgas.PerEmptyAccountCost)
if overflow {
return 0, GasUintOverflow
return 0, 0, GasUintOverflow
}

gas, overflow = emath.SafeAdd(gas, product)
if overflow {
return 0, GasUintOverflow
return 0, 0, GasUintOverflow
}

return gas, Success
return gas, floorGas7623, Success
}

// toWordSize returns the ceiled word size required for memory expansion.
Expand Down
32 changes: 32 additions & 0 deletions erigon-lib/txpool/txpoolcfg/txpoolcfg_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright 2025 The Erigon Authors
// This file is part of Erigon.
//
// Erigon is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Erigon is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Erigon. If not, see <http://www.gnu.org/licenses/>.

package txpoolcfg

import (
"testing"

"github.com/erigontech/erigon-lib/common/fixedgas"
"github.com/stretchr/testify/assert"
)

func TestZeroDataIntrinsicGas(t *testing.T) {
assert := assert.New(t)
gas, floorGas7623, discardReason := CalcIntrinsicGas(0, 0, 0, nil, false, true, true, true, true)
assert.Equal(discardReason, Success)
assert.Equal(gas, fixedgas.TxGas)
assert.Equal(floorGas7623, fixedgas.TxGas)
}
5 changes: 4 additions & 1 deletion tests/transaction_test_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,10 @@ func (tt *TransactionTest) Run(chainID *big.Int) error {
if stx, ok := tx.(*types.SetCodeTransaction); ok {
authorizationsLen = uint64(len(stx.GetAuthorizations()))
}
requiredGas, err := core.IntrinsicGas(msg.Data(), msg.AccessList(), msg.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai, authorizationsLen)
requiredGas, floorGas, err := core.IntrinsicGas(msg.Data(), msg.AccessList(), msg.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai, rules.IsPrague, authorizationsLen)
if rules.IsPrague && floorGas > requiredGas {
requiredGas = floorGas
}
if err != nil {
return nil, nil, 0, err
}
Expand Down

0 comments on commit 8288f26

Please sign in to comment.