Skip to content

Commit

Permalink
Add EIP-3860 (#1209)
Browse files Browse the repository at this point in the history
* Add bn256ScalarMul test case

* Add params for max init code size exceeded

* Update tx pool to limit max init code size in cortina

* Add eip3860

* Implement eip3860

* goimports

* fixes tests (#1210)

* Move copyJumpTable

* Update core/tx_pool.go

Co-authored-by: Darioush Jalali <[email protected]>

* Address comment

---------

Co-authored-by: Darioush Jalali <[email protected]>
  • Loading branch information
aaronbuchwald and darioush authored Mar 1, 2023
1 parent 3f736e7 commit 71d7ca3
Show file tree
Hide file tree
Showing 14 changed files with 300 additions and 32 deletions.
2 changes: 1 addition & 1 deletion core/bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,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, false)
gas, _ := IntrinsicGas(data, nil, false, false, false, false)
tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(benchRootAddr), toaddr, big.NewInt(1), gas, big.NewInt(225000000000), data), types.HomesteadSigner{}, benchRootKey)
gen.AddTx(tx)
}
Expand Down
85 changes: 85 additions & 0 deletions core/state_processor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,18 @@ func mkDynamicTx(nonce uint64, to common.Address, gasLimit uint64, gasTipCap, ga
return tx
}

func mkDynamicCreationTx(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, testKey)
return tx
}

// TestStateProcessorErrors tests the output from the 'core' errors
// as defined in core/error.go. These errors are generated when the
// blockchain imports bad blocks, meaning blocks which have valid headers but
Expand Down Expand Up @@ -295,6 +307,79 @@ func TestStateProcessorErrors(t *testing.T) {
}
}
}

// ErrMaxInitCodeSizeExceeded, for this we need extra Shanghai (Cortina/EIP-3860) enabled.
{
var (
db = rawdb.NewMemoryDatabase()
gspec = &Genesis{
Config: &params.ChainConfig{
ChainID: big.NewInt(1),
HomesteadBlock: big.NewInt(0),
DAOForkBlock: big.NewInt(0),
DAOForkSupport: true,
EIP150Block: big.NewInt(0),
EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"),
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),
ApricotPhase1BlockTimestamp: big.NewInt(0),
ApricotPhase2BlockTimestamp: big.NewInt(0),
ApricotPhase3BlockTimestamp: big.NewInt(0),
ApricotPhase4BlockTimestamp: big.NewInt(0),
ApricotPhase5BlockTimestamp: big.NewInt(0),
ApricotPhasePre6BlockTimestamp: big.NewInt(0),
ApricotPhase6BlockTimestamp: big.NewInt(0),
ApricotPhasePost6BlockTimestamp: big.NewInt(0),
BanffBlockTimestamp: big.NewInt(0),
CortinaBlockTimestamp: big.NewInt(0),
},
Alloc: GenesisAlloc{
common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"): GenesisAccount{
Balance: big.NewInt(1000000000000000000), // 1 ether
Nonce: 0,
},
},
GasLimit: params.CortinaGasLimit,
}
genesis = gspec.MustCommit(db)
blockchain, _ = NewBlockChain(db, DefaultCacheConfig, gspec.Config, dummy.NewFaker(), vm.Config{}, common.Hash{})
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, big.NewInt(params.ApricotPhase3InitialBaseFee), tooBigInitCode[:]),
},
want: "could not apply tx 0 [0x18a05f40f29ff16d5287f6f88b21c9f3c7fbc268f707251144996294552c4cd6]: 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, big.NewInt(params.ApricotPhase3InitialBaseFee), smallInitCode[:]),
},
want: "could not apply tx 0 [0x849278f616d51ab56bba399551317213ce7a10e4d9cbc3d14bb663e50cb7ab99]: intrinsic gas too low: have 54299, want 54300",
},
} {
block := GenerateBadBlock(genesis, dummy.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
Expand Down
31 changes: 27 additions & 4 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,16 +128,17 @@ func (result *ExecutionResult) Revert() []byte {
}

// 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
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 @@ -155,11 +156,19 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation b
}
gas += nz * nonZeroGas

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 @@ -168,6 +177,15 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation b
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 @@ -320,7 +338,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
)

// 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.IsCortina)
if err != nil {
return nil, err
}
Expand All @@ -334,6 +352,11 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From().Hex())
}

// Check whether the init code size has been exceeded.
if rules.IsCortina && contractCreation && len(st.data) > params.MaxInitCodeSize {
return nil, fmt.Errorf("%w: code size %v limit %v", vmerrs.ErrMaxInitCodeSizeExceeded, len(st.data), params.MaxInitCodeSize)
}

// Set up the initial access list.
if rules.IsApricotPhase2 {
st.state.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList())
Expand Down
9 changes: 8 additions & 1 deletion core/tx_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import (
"github.com/ava-labs/coreth/core/types"
"github.com/ava-labs/coreth/metrics"
"github.com/ava-labs/coreth/params"
"github.com/ava-labs/coreth/vmerrs"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/prque"
"github.com/ethereum/go-ethereum/event"
Expand Down Expand Up @@ -260,6 +261,7 @@ type TxPool struct {
istanbul bool // Fork indicator whether we are in the istanbul stage.
eip2718 bool // Fork indicator whether we are using EIP-2718 type transactions.
eip1559 bool // Fork indicator whether we are using EIP-1559 type transactions.
cortina bool // Fork indicator whether cortina is activated. (equivalent to Shanghai in go-ethereum)

currentHead *types.Header
// [currentState] is the state of the blockchain head. It is reset whenever
Expand Down Expand Up @@ -675,6 +677,10 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
if txSize := uint64(tx.Size()); txSize > txMaxSize {
return fmt.Errorf("%w tx size %d > max size %d", ErrOversizedData, txSize, txMaxSize)
}
// Check whether the init code size has been exceeded.
if pool.cortina && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize {
return fmt.Errorf("%w: code size %v limit %v", vmerrs.ErrMaxInitCodeSizeExceeded, len(tx.Data()), params.MaxInitCodeSize)
}
// Transactions can't be negative. This may never happen using RLP decoded
// transactions but may occur if you create a transaction using the RPC.
if tx.Value().Sign() < 0 {
Expand Down Expand Up @@ -716,7 +722,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
// Transactor should have enough funds to cover the costs

// 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.cortina)
if err != nil {
return err
}
Expand Down Expand Up @@ -1410,6 +1416,7 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {
timestamp := new(big.Int).SetUint64(newHead.Time)
pool.eip2718 = pool.chainconfig.IsApricotPhase2(timestamp)
pool.eip1559 = pool.chainconfig.IsApricotPhase3(timestamp)
pool.cortina = pool.chainconfig.IsCortina(timestamp)
}

// promoteExecutables moves transactions that have become processable from the
Expand Down
8 changes: 8 additions & 0 deletions core/vm/eips.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (

var activators = map[int]func(*JumpTable){
3855: enable3855,
3860: enable3860,
3198: enable3198,
2929: enable2929,
2200: enable2200,
Expand Down Expand Up @@ -206,3 +207,10 @@ func opPush0(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by
scope.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
}
33 changes: 33 additions & 0 deletions core/vm/gas_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,39 @@ 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, vmerrs.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, vmerrs.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, vmerrs.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, vmerrs.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
72 changes: 72 additions & 0 deletions core/vm/gas_table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@
package vm

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

"github.com/ava-labs/coreth/core/rawdb"
Expand Down Expand Up @@ -116,3 +118,73 @@ 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}
}

vmenv := NewEVM(vmctx, TxContext{}, statedb, params.TestChainConfig, 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 @@ -658,7 +658,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
Loading

0 comments on commit 71d7ca3

Please sign in to comment.