Skip to content

Commit

Permalink
core/vm: implement EIP-3860: Limit and meter initcode (ethereum#23847)
Browse files Browse the repository at this point in the history
  • Loading branch information
gzliudan committed Sep 10, 2024
1 parent 73b7fc4 commit 995b805
Show file tree
Hide file tree
Showing 13 changed files with 178 additions and 27 deletions.
2 changes: 1 addition & 1 deletion core/bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
return func(i int, gen *BlockGen) {
toaddr := common.Address{}
data := make([]byte, nbytes)
gas, _ := IntrinsicGas(data, nil, false, false)
gas, _ := IntrinsicGas(data, nil, false, false, false)
tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(benchRootAddr), toaddr, big.NewInt(1), gas, nil, data), types.HomesteadSigner{}, benchRootKey)
gen.AddTx(tx)
}
Expand Down
4 changes: 4 additions & 0 deletions core/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ var (
// by a transaction is higher than what's left in the block.
ErrGasLimitReached = errors.New("gas limit reached")

// ErrMaxInitCodeSizeExceeded is returned if creation transaction provides the init code bigger
// than init code size limit.
ErrMaxInitCodeSizeExceeded = errors.New("max initcode size exceeded")

// ErrInsufficientFunds is returned if the total cost of executing a transaction
// is higher than the balance of the user's account.
ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value")
Expand Down
47 changes: 37 additions & 10 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,17 @@ type Message interface {
}

// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation, isHomestead bool) (uint64, error) {
func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation, isHomestead bool, isEIP3860 bool) (uint64, error) {
// Set the starting gas for the raw transaction
var gas uint64
if isContractCreation && isHomestead {
gas = params.TxGasContractCreation
} else {
gas = params.TxGas
}
dataLen := uint64(len(data))
// Bump the required gas by the amount of transactional data
if len(data) > 0 {
if dataLen > 0 {
// Zero and non-zero bytes are priced differently
var nz uint64
for _, byt := range data {
Expand All @@ -113,11 +114,19 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation,
}
gas += nz * params.TxDataNonZeroGas

z := uint64(len(data)) - nz
z := dataLen - nz
if (math.MaxUint64-gas)/params.TxDataZeroGas < z {
return 0, ErrGasUintOverflow
}
gas += z * params.TxDataZeroGas

if isContractCreation && isEIP3860 {
lenWords := toWordSize(dataLen)
if (math.MaxUint64-gas)/params.InitCodeWordGas < lenWords {
return 0, ErrGasUintOverflow
}
gas += lenWords * params.InitCodeWordGas
}
}
if accessList != nil {
gas += uint64(len(accessList)) * params.TxAccessListAddressGas
Expand All @@ -126,6 +135,15 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation,
return gas, nil
}

// toWordSize returns the ceiled word size required for init code payment calculation.
func toWordSize(size uint64) uint64 {
if size > math.MaxUint64-31 {
return math.MaxUint64/32 + 1
}

return (size + 31) / 32
}

// NewStateTransition initialises and returns a new state transition object.
func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition {
return &StateTransition{
Expand Down Expand Up @@ -265,14 +283,18 @@ func (st *StateTransition) TransitionDb(owner common.Address) (ret []byte, usedG
if err = st.preCheck(); err != nil {
return nil, 0, false, err, nil
}
msg := st.msg
sender := st.from() // err checked in preCheck
homestead := st.evm.ChainConfig().IsHomestead(st.evm.BlockNumber)
eip3529 := st.evm.ChainConfig().IsEIP1559(st.evm.Context.BlockNumber)
contractCreation := msg.To() == nil

var (
msg = st.msg
sender = st.from() // err checked in preCheck
rules = st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber)
homestead = st.evm.ChainConfig().IsHomestead(st.evm.BlockNumber)
eip3529 = st.evm.ChainConfig().IsEIP1559(st.evm.Context.BlockNumber)
contractCreation = msg.To() == nil
)

// Check clauses 4-5, subtract intrinsic gas if everything is correct
gas, err := IntrinsicGas(st.data, st.msg.AccessList(), contractCreation, homestead)
gas, err := IntrinsicGas(st.data, st.msg.AccessList(), contractCreation, homestead, rules.IsShanghai)
if err != nil {
return nil, 0, false, err, nil
}
Expand All @@ -281,7 +303,12 @@ func (st *StateTransition) TransitionDb(owner common.Address) (ret []byte, usedG
}
st.gas -= gas

if rules := st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber); rules.IsEIP1559 {
// Check whether the init code size has been exceeded.
if rules.IsEIP1559 && contractCreation && len(st.data) > params.MaxInitCodeSize {
return nil, 0, false, fmt.Errorf("%w: code size %v limit %v", ErrMaxInitCodeSizeExceeded, len(st.data), params.MaxInitCodeSize), nil
}

if rules.IsEIP1559 {
st.state.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList())
}

Expand Down
2 changes: 1 addition & 1 deletion core/txpool/txpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -702,7 +702,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {

if tx.To() == nil || (tx.To() != nil && !tx.IsSpecialTransaction()) {
// Ensure the transaction has more gas than the basic tx fee.
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true)
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.eip1559)
if err != nil {
return err
}
Expand Down
8 changes: 8 additions & 0 deletions core/vm/eips.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (

var activators = map[int]func(*JumpTable){
3855: enable3855,
3860: enable3860,
3529: enable3529,
3198: enable3198,
2929: enable2929,
Expand Down Expand Up @@ -179,3 +180,10 @@ func opPush0(pc *uint64, interpreter *EVMInterpreter, callContext *ScopeContext)
callContext.Stack.push(new(uint256.Int))
return nil, nil
}

// ebnable3860 enables "EIP-3860: Limit and meter initcode"
// https://eips.ethereum.org/EIPS/eip-3860
func enable3860(jt *JumpTable) {
jt[CREATE].dynamicGas = gasCreateEip3860
jt[CREATE2].dynamicGas = gasCreate2Eip3860
}
1 change: 1 addition & 0 deletions core/vm/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ var (
ErrInsufficientBalance = errors.New("insufficient balance for transfer")
ErrContractAddressCollision = errors.New("contract address collision")
ErrExecutionReverted = errors.New("execution reverted")
ErrMaxInitCodeSizeExceeded = errors.New("max initcode size exceeded")
ErrMaxCodeSizeExceeded = errors.New("max code size exceeded")
ErrInvalidJump = errors.New("invalid jump destination")
ErrWriteProtection = errors.New("write protection")
Expand Down
52 changes: 43 additions & 9 deletions core/vm/gas_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,19 +163,19 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi
return params.NetSstoreDirtyGas, nil
}

// 0. If *gasleft* is less than or equal to 2300, fail the current call.
// 1. If current value equals new value (this is a no-op), SLOAD_GAS is deducted.
// 2. If current value does not equal new value:
// 2.1. If original value equals current value (this storage slot has not been changed by the current execution context):
// 0. If *gasleft* is less than or equal to 2300, fail the current call.
// 1. If current value equals new value (this is a no-op), SLOAD_GAS is deducted.
// 2. If current value does not equal new value:
// 2.1. If original value equals current value (this storage slot has not been changed by the current execution context):
// 2.1.1. If original value is 0, SSTORE_SET_GAS (20K) gas is deducted.
// 2.1.2. Otherwise, SSTORE_RESET_GAS gas is deducted. If new value is 0, add SSTORE_CLEARS_SCHEDULE to refund counter.
// 2.2. If original value does not equal current value (this storage slot is dirty), SLOAD_GAS gas is deducted. Apply both of the following clauses:
// 2.2. If original value does not equal current value (this storage slot is dirty), SLOAD_GAS gas is deducted. Apply both of the following clauses:
// 2.2.1. If original value is not 0:
// 2.2.1.1. If current value is 0 (also means that new value is not 0), subtract SSTORE_CLEARS_SCHEDULE gas from refund counter.
// 2.2.1.2. If new value is 0 (also means that current value is not 0), add SSTORE_CLEARS_SCHEDULE gas to refund counter.
// 2.2.1.1. If current value is 0 (also means that new value is not 0), subtract SSTORE_CLEARS_SCHEDULE gas from refund counter.
// 2.2.1.2. If new value is 0 (also means that current value is not 0), add SSTORE_CLEARS_SCHEDULE gas to refund counter.
// 2.2.2. If original value equals new value (this storage slot is reset):
// 2.2.2.1. If original value is 0, add SSTORE_SET_GAS - SLOAD_GAS to refund counter.
// 2.2.2.2. Otherwise, add SSTORE_RESET_GAS - SLOAD_GAS gas to refund counter.
// 2.2.2.1. If original value is 0, add SSTORE_SET_GAS - SLOAD_GAS to refund counter.
// 2.2.2.2. Otherwise, add SSTORE_RESET_GAS - SLOAD_GAS gas to refund counter.
func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
// If we fail the minimum gas availability invariant, fail (0)
if contract.Gas <= params.SstoreSentryGasEIP2200 {
Expand Down Expand Up @@ -300,6 +300,40 @@ func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memoryS
return gas, nil
}

func gasCreateEip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
gas, err := memoryGasCost(mem, memorySize)
if err != nil {
return 0, err
}
size, overflow := stack.Back(2).Uint64WithOverflow()
if overflow || size > params.MaxInitCodeSize {
return 0, ErrGasUintOverflow
}
// Since size <= params.MaxInitCodeSize, these multiplication cannot overflow
moreGas := params.InitCodeWordGas * ((size + 31) / 32)
if gas, overflow = math.SafeAdd(gas, moreGas); overflow {
return 0, ErrGasUintOverflow
}
return gas, nil
}

func gasCreate2Eip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
gas, err := memoryGasCost(mem, memorySize)
if err != nil {
return 0, err
}
size, overflow := stack.Back(2).Uint64WithOverflow()
if overflow || size > params.MaxInitCodeSize {
return 0, ErrGasUintOverflow
}
// Since size <= params.MaxInitCodeSize, these multiplication cannot overflow
moreGas := (params.InitCodeWordGas + params.Keccak256WordGas) * ((size + 31) / 32)
if gas, overflow = math.SafeAdd(gas, moreGas); overflow {
return 0, ErrGasUintOverflow
}
return gas, nil
}

func gasExpFrontier(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
expByteLen := uint64((stack.data[stack.len()-2].BitLen() + 7) / 8)

Expand Down
71 changes: 71 additions & 0 deletions core/vm/gas_table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
package vm

import (
"bytes"
"math"
"math/big"
"sort"
"testing"

"github.com/XinFinOrg/XDPoSChain/core/rawdb"
Expand Down Expand Up @@ -106,3 +108,72 @@ func TestEIP2200(t *testing.T) {
}
}
}

var createGasTests = []struct {
code string
eip3860 bool
gasUsed uint64
minimumGas uint64
}{
// legacy create(0, 0, 0xc000) without 3860 used
{"0x61C00060006000f0" + "600052" + "60206000F3", false, 41237, 41237},
// legacy create(0, 0, 0xc000) _with_ 3860
{"0x61C00060006000f0" + "600052" + "60206000F3", true, 44309, 44309},
// create2(0, 0, 0xc001, 0) without 3860
{"0x600061C00160006000f5" + "600052" + "60206000F3", false, 50471, 100_000},
// create2(0, 0, 0xc001, 0) (too large), with 3860
{"0x600061C00160006000f5" + "600052" + "60206000F3", true, 32012, 100_000},
// create2(0, 0, 0xc000, 0)
// This case is trying to deploy code at (within) the limit
{"0x600061C00060006000f5" + "600052" + "60206000F3", true, 53528, 100_000},
// create2(0, 0, 0xc001, 0)
// This case is trying to deploy code exceeding the limit
{"0x600061C00160006000f5" + "600052" + "60206000F3", true, 32024, 100_000}}

func TestCreateGas(t *testing.T) {
for i, tt := range createGasTests {
var gasUsed = uint64(0)
doCheck := func(testGas int) bool {
address := common.BytesToAddress([]byte("contract"))
statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()))
statedb.CreateAccount(address)
statedb.SetCode(address, hexutil.MustDecode(tt.code))
statedb.Finalise(true)
vmctx := Context{
CanTransfer: func(StateDB, common.Address, *big.Int) bool { return true },
Transfer: func(StateDB, common.Address, common.Address, *big.Int) {},
BlockNumber: big.NewInt(0),
}
config := Config{}
if tt.eip3860 {
config.ExtraEips = []int{3860}
}

vmenv := NewEVM(vmctx, statedb, nil, params.AllEthashProtocolChanges, config)
var startGas = uint64(testGas)
ret, gas, err := vmenv.Call(AccountRef(common.Address{}), address, nil, startGas, new(big.Int))
if err != nil {
return false
}
gasUsed = startGas - gas
if len(ret) != 32 {
t.Fatalf("test %d: expected 32 bytes returned, have %d", i, len(ret))
}
if bytes.Equal(ret, make([]byte, 32)) {
// Failure
return false
}
return true
}
minGas := sort.Search(100_000, doCheck)
if uint64(minGas) != tt.minimumGas {
t.Fatalf("test %d: min gas error, want %d, have %d", i, tt.minimumGas, minGas)
}
// If the deployment succeeded, we also check the gas used
if minGas < 100_000 {
if gasUsed != tt.gasUsed {
t.Errorf("test %d: gas used mismatch: have %v, want %v", i, gasUsed, tt.gasUsed)
}
}
}
}
1 change: 0 additions & 1 deletion core/vm/instructions.go
Original file line number Diff line number Diff line change
Expand Up @@ -640,7 +640,6 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
gas = scope.Contract.Gas
)

// Apply EIP150
gas -= gas / 64
scope.Contract.UseGas(gas)
Expand Down
1 change: 1 addition & 0 deletions core/vm/jump_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ func newEip1559InstructionSet() JumpTable {
instructionSet := newShanghaiInstructionSet()
enable2929(&instructionSet) // Gas cost increases for state access opcodes https://eips.ethereum.org/EIPS/eip-2929
enable3529(&instructionSet) // EIP-3529: Reduction in refunds https://eips.ethereum.org/EIPS/eip-3529
enable3860(&instructionSet) // Limit and meter initcode
return instructionSet
}

Expand Down
3 changes: 2 additions & 1 deletion eth/tracers/tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -682,9 +682,10 @@ func (jst *JsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Ad

// Compute intrinsic gas
isHomestead := env.ChainConfig().IsHomestead(env.Context.BlockNumber)
isEIP1559 := env.ChainConfig().IsEIP1559(env.Context.BlockNumber)
// after update core.IntrinsicGas, use isIstanbul in it
// isIstanbul := env.ChainConfig().IsIstanbul(env.Context.BlockNumber)
intrinsicGas, err := core.IntrinsicGas(input, nil, jst.ctx["type"] == "CREATE", isHomestead)
intrinsicGas, err := core.IntrinsicGas(input, nil, jst.ctx["type"] == "CREATE", isHomestead, isEIP1559)
if err != nil {
return
}
Expand Down
4 changes: 3 additions & 1 deletion light/txpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ type TxPool struct {

homestead bool
eip2718 bool // Fork indicator whether we are in the eip2718 stage.
eip1559 bool // Fork indicator whether we are in the eip1559 stage.
}

// TxRelayBackend provides an interface to the mechanism that forwards transacions
Expand Down Expand Up @@ -317,6 +318,7 @@ func (pool *TxPool) setNewHead(head *types.Header) {
next := new(big.Int).Add(head.Number, big.NewInt(1))
pool.homestead = pool.config.IsHomestead(head.Number)
pool.eip2718 = pool.config.IsEIP1559(next)
pool.eip1559 = pool.config.IsEIP1559(next)
}

// Stop stops the light transaction pool
Expand Down Expand Up @@ -408,7 +410,7 @@ func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error
}

// Should supply enough intrinsic gas
gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, pool.homestead)
gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, pool.homestead, pool.eip1559)
if err != nil {
return err
}
Expand Down
9 changes: 6 additions & 3 deletions params/protocol_params.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ const (
LogDataGas uint64 = 8 // Per byte in a LOG* operation's data.
CallStipend uint64 = 2300 // Free gas given at beginning of call.

Keccak256Gas uint64 = 30 // Once per KECCAK256 operation.
Keccak256WordGas uint64 = 6 // Once per word of the KECCAK256 operation's data.
Keccak256Gas uint64 = 30 // Once per KECCAK256 operation.
Keccak256WordGas uint64 = 6 // Once per word of the KECCAK256 operation's data.
InitCodeWordGas uint64 = 2 // Once per word of the init code when creating a contract.

SstoreResetGas uint64 = 5000 // Once per SSTORE operation if the zeroness changes from zero.
SstoreClearGas uint64 = 5000 // Once per SSTORE operation if the zeroness doesn't change.
SstoreRefundGas uint64 = 15000 // Once per SSTORE operation if the zeroness changes to zero.
Expand All @@ -69,7 +71,8 @@ const (

InitialBaseFee = 12500000000 // Initial base fee for EIP-1559 blocks.

MaxCodeSize = 24576 // Maximum bytecode to permit for a contract
MaxCodeSize = 24576 // Maximum bytecode to permit for a contract
MaxInitCodeSize = 2 * MaxCodeSize // Maximum initcode to permit in a creation transaction and create instructions

// Precompiled contract gas prices

Expand Down

0 comments on commit 995b805

Please sign in to comment.