Implement 2 EIPS: limit and meter initcode and PUSH0 instructions (#1443)

* core/vm: deepcopy jumptable when enabling extra eips

When the interpreter is configured to use extra-eips,
this change makes it so that all the opcodes are deep-copied,
to prevent accidental modification of the 'base' jumptable.

Original-auther: yihuang <huang@crypto.com>
Co-authored-by: Martin Holst Swende <martin@swende.se>

* core/vm: implement EIP-3860: Limit and meter initcode

Implementation of https://eips.ethereum.org/EIPS/eip-3860, limit and meter initcode.
Most of this change takes the go-ethereum implementation as reference.

Original-author: Andrei Maiboroda <andrei@ethereum.org>
Co-authored-by: lightclient@protonmail.com <lightclient@protonmail.com>
Co-authored-by: Martin Holst Swende <martin@swende.se>
Co-authored-by: Marius van der Wijden <m.vanderwijden@live.de>

* core/vm: implement EIP-3855: PUSH0 instruction

* core/vm: Implement PUSH0

* Move PUSH0 to enable3855

* Add method doc

Original-author: Alex Beregszaszi <alex@rtfs.hu>

* core/vm: enable EIP-3855 (PUSH0) in Boneh

Original-author: Andrew Ashikhmin <34320705+yperbasis@users.noreply.github.com>

* core/vm: Fix issue of incorrect instructionSet used for jump_table

Also update the related test case for gas change.

* core/vm: fix test cases issues that not suitable for boneh

* core/vm: reuse ErrMaxInitCodeSizeExceeded as error message

---------

Co-authored-by: Martin Holst Swende <martin@swende.se>
Co-authored-by: lightclient@protonmail.com <lightclient@protonmail.com>
Co-authored-by: Marius van der Wijden <m.vanderwijden@live.de>
This commit is contained in:
sunny2022da 2023-04-13 17:26:21 +08:00 committed by GitHub
parent f7d15e34df
commit b4f1cdffd5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 332 additions and 18 deletions

@ -140,7 +140,7 @@ func Transaction(ctx *cli.Context) error {
} }
// Check intrinsic gas // Check intrinsic gas
if gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, if gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil,
chainConfig.IsHomestead(new(big.Int)), chainConfig.IsIstanbul(new(big.Int))); err != nil { chainConfig.IsHomestead(new(big.Int)), chainConfig.IsIstanbul(new(big.Int)), chainConfig.IsBoneh(new(big.Int))); err != nil {
r.Error = err r.Error = err
results = append(results, r) results = append(results, r)
continue continue
@ -171,6 +171,10 @@ func Transaction(ctx *cli.Context) error {
case new(big.Int).Mul(tx.GasFeeCap(), new(big.Int).SetUint64(tx.Gas())).BitLen() > 256: case new(big.Int).Mul(tx.GasFeeCap(), new(big.Int).SetUint64(tx.Gas())).BitLen() > 256:
r.Error = errors.New("gas * maxFeePerGas exceeds 256 bits") r.Error = errors.New("gas * maxFeePerGas exceeds 256 bits")
} }
// Check whether the init code size has been exceeded.
if chainConfig.IsBoneh(new(big.Int)) && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize {
r.Error = core.ErrMaxInitCodeSizeExceeded
}
results = append(results, r) results = append(results, r)
} }
out, err := json.MarshalIndent(results, "", " ") out, err := json.MarshalIndent(results, "", " ")

@ -85,7 +85,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
return func(i int, gen *BlockGen) { return func(i int, gen *BlockGen) {
toaddr := common.Address{} toaddr := common.Address{}
data := make([]byte, nbytes) data := make([]byte, nbytes)
gas, _ := IntrinsicGas(data, nil, false, false, false) gas, _ := IntrinsicGas(data, nil, false, false, false, false)
signer := types.MakeSigner(gen.config, big.NewInt(int64(i))) signer := types.MakeSigner(gen.config, big.NewInt(int64(i)))
gasPrice := big.NewInt(0) gasPrice := big.NewInt(0)
if gen.header.BaseFee != nil { if gen.header.BaseFee != nil {

@ -3587,6 +3587,9 @@ func TestInitThenFailCreateContract(t *testing.T) {
// checking that the gas usage of a hot SLOAD and a cold SLOAD are calculated // checking that the gas usage of a hot SLOAD and a cold SLOAD are calculated
// correctly. // correctly.
func TestEIP2718Transition(t *testing.T) { func TestEIP2718Transition(t *testing.T) {
customConfig := params.AllEthashProtocolChanges
customConfig.BonehBlock = big.NewInt(100)
customConfig.LynnBlock = big.NewInt(200)
var ( var (
aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa")
@ -3599,7 +3602,7 @@ func TestEIP2718Transition(t *testing.T) {
address = crypto.PubkeyToAddress(key.PublicKey) address = crypto.PubkeyToAddress(key.PublicKey)
funds = big.NewInt(1000000000000000) funds = big.NewInt(1000000000000000)
gspec = &Genesis{ gspec = &Genesis{
Config: params.TestChainConfig, Config: customConfig,
Alloc: GenesisAlloc{ Alloc: GenesisAlloc{
address: {Balance: funds}, address: {Balance: funds},
// The address 0xAAAA sloads 0x00 and 0x01 // The address 0xAAAA sloads 0x00 and 0x01
@ -3654,9 +3657,9 @@ func TestEIP2718Transition(t *testing.T) {
// Expected gas is intrinsic + 2 * pc + hot load + cold load, since only one load is in the access list // Expected gas is intrinsic + 2 * pc + hot load + cold load, since only one load is in the access list
expected := params.TxGas + params.TxAccessListAddressGas + params.TxAccessListStorageKeyGas + expected := params.TxGas + params.TxAccessListAddressGas + params.TxAccessListStorageKeyGas +
vm.GasQuickStep*2 + params.WarmStorageReadCostEIP2929 + params.ColdSloadCostEIP2929 vm.GasQuickStep*2 + params.WarmStorageReadCostEIP2929 + params.ColdSloadCostEIP2929
if block.GasUsed() != expected { if block.GasUsed() != expected {
t.Fatalf("incorrect amount of gas spent: expected %d, got %d", expected, block.GasUsed()) t.Fatalf("incorrect amount of gas spent: expected %d, got %d", expected, block.GasUsed())
} }
} }
@ -3670,6 +3673,9 @@ func TestEIP2718Transition(t *testing.T) {
// gasFeeCap - gasTipCap < baseFee. // gasFeeCap - gasTipCap < baseFee.
// 6. Legacy transaction behave as expected (e.g. gasPrice = gasFeeCap = gasTipCap). // 6. Legacy transaction behave as expected (e.g. gasPrice = gasFeeCap = gasTipCap).
func TestEIP1559Transition(t *testing.T) { func TestEIP1559Transition(t *testing.T) {
customConfig := params.AllEthashProtocolChanges
customConfig.BonehBlock = big.NewInt(100)
customConfig.LynnBlock = big.NewInt(200)
var ( var (
aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa")
@ -3684,7 +3690,7 @@ func TestEIP1559Transition(t *testing.T) {
addr2 = crypto.PubkeyToAddress(key2.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey)
funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether)) funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether))
gspec = &Genesis{ gspec = &Genesis{
Config: params.AllEthashProtocolChanges, Config: customConfig,
Alloc: GenesisAlloc{ Alloc: GenesisAlloc{
addr1: {Balance: funds}, addr1: {Balance: funds},
addr2: {Balance: funds}, addr2: {Balance: funds},

@ -75,6 +75,10 @@ var (
// have enough funds for transfer(topmost call only). // have enough funds for transfer(topmost call only).
ErrInsufficientFundsForTransfer = errors.New("insufficient funds for transfer") ErrInsufficientFundsForTransfer = errors.New("insufficient funds for transfer")
// 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 // ErrInsufficientFunds is returned if the total cost of executing a transaction
// is higher than the balance of the user's account. // is higher than the balance of the user's account.
ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value") ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value")

@ -80,6 +80,17 @@ func TestStateProcessorErrors(t *testing.T) {
}), signer, key1) }), signer, key1)
return tx return tx
} }
var mkDynamicCreationTx = func(nonce uint64, gasLimit uint64, gasTipCap, gasFeeCap *big.Int, data []byte) *types.Transaction {
tx, _ := types.SignTx(types.NewTx(&types.DynamicFeeTx{
Nonce: nonce,
GasTipCap: gasTipCap,
GasFeeCap: gasFeeCap,
Gas: gasLimit,
Value: big.NewInt(0),
Data: data,
}), signer, key1)
return tx
}
{ // Tests against a 'recent' chain definition { // Tests against a 'recent' chain definition
var ( var (
db = rawdb.NewMemoryDatabase() db = rawdb.NewMemoryDatabase()
@ -302,6 +313,71 @@ func TestStateProcessorErrors(t *testing.T) {
} }
} }
} }
// ErrMaxInitCodeSizeExceeded, for this we need extra Boneh (EIP-3860) enabled.
{
var (
db = rawdb.NewMemoryDatabase()
gspec = &Genesis{
Config: &params.ChainConfig{
ChainID: big.NewInt(1),
HomesteadBlock: big.NewInt(0),
EIP150Block: big.NewInt(0),
EIP155Block: big.NewInt(0),
EIP158Block: big.NewInt(0),
ByzantiumBlock: big.NewInt(0),
ConstantinopleBlock: big.NewInt(0),
PetersburgBlock: big.NewInt(0),
IstanbulBlock: big.NewInt(0),
MuirGlacierBlock: big.NewInt(0),
MirrorSyncBlock: big.NewInt(0),
BrunoBlock: big.NewInt(0),
EulerBlock: big.NewInt(0),
BerlinBlock: big.NewInt(0),
LondonBlock: big.NewInt(0),
GibbsBlock: big.NewInt(0),
BonehBlock: big.NewInt(0),
},
Alloc: GenesisAlloc{
common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"): GenesisAccount{
Balance: big.NewInt(1000000000000000000), // 1 ether
Nonce: 0,
},
},
}
genesis = gspec.MustCommit(db)
blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
tooBigInitCode = [params.MaxInitCodeSize + 1]byte{}
smallInitCode = [320]byte{}
)
defer blockchain.Stop()
for i, tt := range []struct {
txs []*types.Transaction
want string
}{
{ // ErrMaxInitCodeSizeExceeded
txs: []*types.Transaction{
mkDynamicCreationTx(0, 500000, common.Big0, misc.CalcBaseFee(config, genesis.Header()), tooBigInitCode[:]),
},
want: "could not apply tx 0 [0x832b54a6c3359474a9f504b1003b2cc1b6fcaa18e4ef369eb45b5d40dad6378f]: max initcode size exceeded: code size 49153 limit 49152",
},
{ // ErrIntrinsicGas: Not enough gas to cover init code
txs: []*types.Transaction{
mkDynamicCreationTx(0, 54299, common.Big0, misc.CalcBaseFee(config, genesis.Header()), smallInitCode[:]),
},
want: "could not apply tx 0 [0x39b7436cb432d3662a25626474282c5c4c1a213326fd87e4e18a91477bae98b2]: intrinsic gas too low: have 54299, want 54300",
},
} {
block := GenerateBadBlock(genesis, ethash.NewFaker(), tt.txs, gspec.Config)
_, err := blockchain.InsertChain(types.Blocks{block})
if err == nil {
t.Fatal("block imported without errors")
}
if have, want := err.Error(), tt.want; have != want {
t.Errorf("test %d:\nhave \"%v\"\nwant \"%v\"\n", i, have, want)
}
}
}
} }
// GenerateBadBlock constructs a "block" which contains the transactions. The transactions are not expected to be // GenerateBadBlock constructs a "block" which contains the transactions. The transactions are not expected to be

@ -118,7 +118,7 @@ func (result *ExecutionResult) Revert() []byte {
} }
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data. // IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation bool, isHomestead, isEIP2028 bool) (uint64, error) { func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation bool, isHomestead, isEIP2028 bool, isEIP3860 bool) (uint64, error) {
// Set the starting gas for the raw transaction // Set the starting gas for the raw transaction
var gas uint64 var gas uint64
if isContractCreation && isHomestead { if isContractCreation && isHomestead {
@ -126,8 +126,9 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation b
} else { } else {
gas = params.TxGas gas = params.TxGas
} }
dataLen := uint64(len(data))
// Bump the required gas by the amount of transactional 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 // Zero and non-zero bytes are priced differently
var nz uint64 var nz uint64
for _, byt := range data { for _, byt := range data {
@ -145,11 +146,19 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation b
} }
gas += nz * nonZeroGas gas += nz * nonZeroGas
z := uint64(len(data)) - nz z := dataLen - nz
if (math.MaxUint64-gas)/params.TxDataZeroGas < z { if (math.MaxUint64-gas)/params.TxDataZeroGas < z {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }
gas += z * params.TxDataZeroGas 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 { if accessList != nil {
gas += uint64(len(accessList)) * params.TxAccessListAddressGas gas += uint64(len(accessList)) * params.TxAccessListAddressGas
@ -158,6 +167,15 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation b
return gas, nil 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. // NewStateTransition initialises and returns a new state transition object.
func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition { func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition {
return &StateTransition{ return &StateTransition{
@ -315,7 +333,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
} }
} }
// Check clauses 4-5, subtract intrinsic gas if everything is correct // Check clauses 4-5, subtract intrinsic gas if everything is correct
gas, err := IntrinsicGas(st.data, st.msg.AccessList(), contractCreation, rules.IsHomestead, rules.IsIstanbul) gas, err := IntrinsicGas(st.data, st.msg.AccessList(), contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsBoneh)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -329,6 +347,11 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From().Hex()) return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From().Hex())
} }
// Check whether the init code size has been exceeded.
if rules.IsBoneh && contractCreation && len(st.data) > params.MaxInitCodeSize {
return nil, fmt.Errorf("%w: code size %v limit %v", ErrMaxInitCodeSizeExceeded, len(st.data), params.MaxInitCodeSize)
}
// Set up the initial access list. // Set up the initial access list.
if rules.IsBerlin { if rules.IsBerlin {
st.state.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList()) st.state.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList())

@ -263,6 +263,7 @@ type TxPool struct {
istanbul bool // Fork indicator whether we are in the istanbul stage. istanbul bool // Fork indicator whether we are in the istanbul stage.
eip2718 bool // Fork indicator whether we are using EIP-2718 type transactions. eip2718 bool // Fork indicator whether we are using EIP-2718 type transactions.
eip1559 bool // Fork indicator whether we are using EIP-1559 type transactions. eip1559 bool // Fork indicator whether we are using EIP-1559 type transactions.
boneh bool // Fork indicator whether we are in the Boneh stage.
currentState *state.StateDB // Current state in the blockchain head currentState *state.StateDB // Current state in the blockchain head
pendingNonces *txNoncer // Pending state tracking virtual nonces pendingNonces *txNoncer // Pending state tracking virtual nonces
@ -706,7 +707,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
} }
// Ensure the transaction has more gas than the basic tx fee. // Ensure the transaction has more gas than the basic tx fee.
intrGas, err := IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.istanbul) intrGas, err := IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.istanbul, pool.boneh)
if err != nil { if err != nil {
return err return err
} }
@ -1433,6 +1434,7 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {
pool.istanbul = pool.chainconfig.IsIstanbul(next) pool.istanbul = pool.chainconfig.IsIstanbul(next)
pool.eip2718 = pool.chainconfig.IsBerlin(next) pool.eip2718 = pool.chainconfig.IsBerlin(next)
pool.eip1559 = pool.chainconfig.IsLondon(next) pool.eip1559 = pool.chainconfig.IsLondon(next)
pool.boneh = pool.chainconfig.IsBoneh(next)
} }
// promoteExecutables moves transactions that have become processable from the // promoteExecutables moves transactions that have become processable from the

@ -25,6 +25,8 @@ import (
) )
var activators = map[int]func(*JumpTable){ var activators = map[int]func(*JumpTable){
3860: enable3860,
3855: enable3855,
3529: enable3529, 3529: enable3529,
3198: enable3198, 3198: enable3198,
2929: enable2929, 2929: enable2929,
@ -174,3 +176,27 @@ func opBaseFee(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
scope.Stack.push(baseFee) scope.Stack.push(baseFee)
return nil, nil 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
}
// enable3855 applies EIP-3855 (PUSH0 opcode)
func enable3855(jt *JumpTable) {
// New opcode
jt[PUSH0] = &operation{
execute: opPush0,
constantGas: GasQuickStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
}
}
// opPush0 implements the PUSH0 opcode
func opPush0(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.push(new(uint256.Int))
return nil, nil
}

@ -29,6 +29,7 @@ var (
ErrInsufficientBalance = errors.New("insufficient balance for transfer") ErrInsufficientBalance = errors.New("insufficient balance for transfer")
ErrContractAddressCollision = errors.New("contract address collision") ErrContractAddressCollision = errors.New("contract address collision")
ErrExecutionReverted = errors.New("execution reverted") ErrExecutionReverted = errors.New("execution reverted")
ErrMaxInitCodeSizeExceeded = errors.New("max initcode size exceeded")
ErrMaxCodeSizeExceeded = errors.New("max code size exceeded") ErrMaxCodeSizeExceeded = errors.New("max code size exceeded")
ErrInvalidJump = errors.New("invalid jump destination") ErrInvalidJump = errors.New("invalid jump destination")
ErrWriteProtection = errors.New("write protection") ErrWriteProtection = errors.New("write protection")

@ -299,6 +299,39 @@ func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memoryS
return gas, nil 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) { func gasExpFrontier(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
expByteLen := uint64((stack.data[stack.len()-2].BitLen() + 7) / 8) expByteLen := uint64((stack.data[stack.len()-2].BitLen() + 7) / 8)

@ -17,8 +17,10 @@
package vm package vm
import ( import (
"bytes"
"math" "math"
"math/big" "math/big"
"sort"
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -105,3 +107,76 @@ 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, 50471},
// 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, 53528},
// create2(0, 0, 0xc001, 0)
// This case is trying to deploy code exceeding the limit
{"0x600061C00160006000f5" + "600052" + "60206000F3", true, 32024, 100000},
}
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()), nil)
statedb.CreateAccount(address)
statedb.SetCode(address, hexutil.MustDecode(tt.code))
statedb.Finalise(true)
vmctx := BlockContext{
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}
}
chainConfig := params.AllEthashProtocolChanges
// Use fake BonehBlock Number
chainConfig.BonehBlock = big.NewInt(30000000)
vmenv := NewEVM(vmctx, TxContext{}, statedb, chainConfig, 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)
}
}
}
}

@ -641,7 +641,6 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
gas = scope.Contract.Gas gas = scope.Contract.Gas
) )
// Apply EIP150 // Apply EIP150
gas -= gas / 64 gas -= gas / 64
scope.Contract.UseGas(gas) scope.Contract.UseGas(gas)

@ -77,6 +77,8 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
// If jump table was not initialised we set the default one. // If jump table was not initialised we set the default one.
if cfg.JumpTable == nil { if cfg.JumpTable == nil {
switch { switch {
case evm.chainRules.IsBoneh:
cfg.JumpTable = &bonehInstructionSet
case evm.chainRules.IsMerge: case evm.chainRules.IsMerge:
cfg.JumpTable = &mergeInstructionSet cfg.JumpTable = &mergeInstructionSet
case evm.chainRules.IsLondon: case evm.chainRules.IsLondon:
@ -99,15 +101,17 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
cfg.JumpTable = &frontierInstructionSet cfg.JumpTable = &frontierInstructionSet
} }
var extraEips []int var extraEips []int
if len(cfg.ExtraEips) > 0 {
// Deep-copy jumptable to prevent modification of opcodes in other tables
cfg.JumpTable = copyJumpTable(cfg.JumpTable)
}
for _, eip := range cfg.ExtraEips { for _, eip := range cfg.ExtraEips {
copy := *cfg.JumpTable if err := EnableEIP(eip, cfg.JumpTable); err != nil {
if err := EnableEIP(eip, &copy); err != nil {
// Disable it, so caller can check if it's activated or not // Disable it, so caller can check if it's activated or not
log.Error("EIP activation failed", "eip", eip, "error", err) log.Error("EIP activation failed", "eip", eip, "error", err)
} else { } else {
extraEips = append(extraEips, eip) extraEips = append(extraEips, eip)
} }
cfg.JumpTable = &copy
} }
cfg.ExtraEips = extraEips cfg.ExtraEips = extraEips

@ -55,6 +55,7 @@ var (
berlinInstructionSet = newBerlinInstructionSet() berlinInstructionSet = newBerlinInstructionSet()
londonInstructionSet = newLondonInstructionSet() londonInstructionSet = newLondonInstructionSet()
mergeInstructionSet = newMergeInstructionSet() mergeInstructionSet = newMergeInstructionSet()
bonehInstructionSet = newBonehInstructionSet()
) )
// JumpTable contains the EVM opcodes supported at a given fork. // JumpTable contains the EVM opcodes supported at a given fork.
@ -78,6 +79,13 @@ func validate(jt JumpTable) JumpTable {
return jt return jt
} }
func newBonehInstructionSet() JumpTable {
instructionSet := newIstanbulInstructionSet()
enable3855(&instructionSet) // PUSH0 instruction
enable3860(&instructionSet) // Limit and meter initcode
return validate(instructionSet)
}
func newMergeInstructionSet() JumpTable { func newMergeInstructionSet() JumpTable {
instructionSet := newLondonInstructionSet() instructionSet := newLondonInstructionSet()
instructionSet[RANDOM] = &operation{ instructionSet[RANDOM] = &operation{
@ -1044,3 +1052,14 @@ func newFrontierInstructionSet() JumpTable {
return validate(tbl) return validate(tbl)
} }
func copyJumpTable(source *JumpTable) *JumpTable {
dest := *source
for i, op := range source {
if op != nil {
opCopy := *op
dest[i] = &opCopy
}
}
return &dest
}

@ -0,0 +1,35 @@
// Copyright 2022 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library 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.
//
// The go-ethereum library 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 the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package vm
import (
"testing"
"github.com/stretchr/testify/require"
)
// TestJumpTableCopy tests that deep copy is necessery to prevent modify shared jump table
func TestJumpTableCopy(t *testing.T) {
tbl := newIstanbulInstructionSet()
require.Equal(t, uint64(800), tbl[SLOAD].constantGas)
// a deep copy won't modify the shared jump table
deepCopy := copyJumpTable(&tbl)
deepCopy[SLOAD].constantGas = 100
require.Equal(t, uint64(100), deepCopy[SLOAD].constantGas)
require.Equal(t, uint64(800), tbl[SLOAD].constantGas)
}

@ -116,6 +116,7 @@ const (
MSIZE OpCode = 0x59 MSIZE OpCode = 0x59
GAS OpCode = 0x5a GAS OpCode = 0x5a
JUMPDEST OpCode = 0x5b JUMPDEST OpCode = 0x5b
PUSH0 OpCode = 0x5f
) )
// 0x60 range - pushes. // 0x60 range - pushes.
@ -297,6 +298,7 @@ var opCodeToString = map[OpCode]string{
MSIZE: "MSIZE", MSIZE: "MSIZE",
GAS: "GAS", GAS: "GAS",
JUMPDEST: "JUMPDEST", JUMPDEST: "JUMPDEST",
PUSH0: "PUSH0",
// 0x60 range - push. // 0x60 range - push.
PUSH1: "PUSH1", PUSH1: "PUSH1",
@ -460,6 +462,7 @@ var stringToOp = map[string]OpCode{
"MSIZE": MSIZE, "MSIZE": MSIZE,
"GAS": GAS, "GAS": GAS,
"JUMPDEST": JUMPDEST, "JUMPDEST": JUMPDEST,
"PUSH0": PUSH0,
"PUSH1": PUSH1, "PUSH1": PUSH1,
"PUSH2": PUSH2, "PUSH2": PUSH2,
"PUSH3": PUSH3, "PUSH3": PUSH3,

@ -514,7 +514,7 @@ func TestTracingWithOverrides(t *testing.T) {
}, },
}, },
}, },
want: `{"gas":23347,"failed":false,"returnValue":"000000000000000000000000000000000000000000000000000000000000007b"}`, want: `{"gas":22047,"failed":false,"returnValue":"000000000000000000000000000000000000000000000000000000000000007b"}`,
}, },
} }
for i, tc := range testSuite { for i, tc := range testSuite {

@ -69,6 +69,7 @@ type TxPool struct {
istanbul bool // Fork indicator whether we are in the istanbul stage. istanbul bool // Fork indicator whether we are in the istanbul stage.
eip2718 bool // Fork indicator whether we are in the eip2718 stage. eip2718 bool // Fork indicator whether we are in the eip2718 stage.
boneh bool // Fork indicator whether we are in the boneh stage.
} }
// TxRelayBackend provides an interface to the mechanism that forwards transacions // TxRelayBackend provides an interface to the mechanism that forwards transacions
@ -318,6 +319,7 @@ func (pool *TxPool) setNewHead(head *types.Header) {
next := new(big.Int).Add(head.Number, big.NewInt(1)) next := new(big.Int).Add(head.Number, big.NewInt(1))
pool.istanbul = pool.config.IsIstanbul(next) pool.istanbul = pool.config.IsIstanbul(next)
pool.eip2718 = pool.config.IsBerlin(next) pool.eip2718 = pool.config.IsBerlin(next)
pool.boneh = pool.config.IsBoneh(next)
} }
// Stop stops the light transaction pool // Stop stops the light transaction pool
@ -385,7 +387,7 @@ func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error
} }
// Should supply enough intrinsic gas // Should supply enough intrinsic gas
gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.istanbul) gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.istanbul, pool.boneh)
if err != nil { if err != nil {
return err return err
} }

@ -40,6 +40,7 @@ const (
Keccak256Gas uint64 = 30 // Once per KECCAK256 operation. Keccak256Gas uint64 = 30 // Once per KECCAK256 operation.
Keccak256WordGas uint64 = 6 // Once per word of the KECCAK256 operation's data. 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.
SstoreSetGas uint64 = 20000 // Once per SSTORE operation. SstoreSetGas uint64 = 20000 // Once per SSTORE operation.
SstoreResetGas uint64 = 5000 // Once per SSTORE operation if the zeroness changes from zero. SstoreResetGas uint64 = 5000 // Once per SSTORE operation if the zeroness changes from zero.
@ -126,6 +127,7 @@ const (
InitialBaseFee = 1000000000 // Initial base fee for EIP-1559 blocks. InitialBaseFee = 1000000000 // 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 // Precompiled contract gas prices

@ -55,7 +55,7 @@ func (tt *TransactionTest) Run(config *params.ChainConfig) error {
return nil, nil, err return nil, nil, err
} }
// Intrinsic gas // Intrinsic gas
requiredGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, isHomestead, isIstanbul) requiredGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, isHomestead, isIstanbul, false)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }