Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EIP-3860: Limit and meter initcode #5892

Merged
merged 16 commits into from
Oct 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions core/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,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
63 changes: 39 additions & 24 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ package core

import (
"fmt"
"math/bits"

"github.com/holiman/uint256"

"github.com/ledgerwatch/erigon/common"
"github.com/ledgerwatch/erigon/common/math"
cmath "github.com/ledgerwatch/erigon/common/math"
"github.com/ledgerwatch/erigon/common/u256"
"github.com/ledgerwatch/erigon/consensus"
Expand Down Expand Up @@ -129,7 +129,7 @@ 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, isEIP3860 bool) (uint64, error) {
// Set the starting gas for the raw transaction
var gas uint64
if isContractCreation && isHomestead {
Expand All @@ -138,11 +138,9 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation b
gas = params.TxGas
}

// Auxiliary variables for overflow protection
var product, overflow uint64

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 @@ -156,41 +154,53 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation b
nonZeroGas = params.TxDataNonZeroGasEIP2028
}

overflow, product = bits.Mul64(nz, nonZeroGas)
if overflow != 0 {
product, overflow := math.SafeMul(nz, nonZeroGas)
if overflow {
return 0, ErrGasUintOverflow
}
gas, overflow = bits.Add64(gas, product, 0)
if overflow != 0 {
gas, overflow = math.SafeAdd(gas, product)
if overflow {
return 0, ErrGasUintOverflow
}

z := uint64(len(data)) - nz
overflow, product = bits.Mul64(z, params.TxDataZeroGas)
if overflow != 0 {
z := dataLen - nz
product, overflow = math.SafeMul(z, params.TxDataZeroGas)
if overflow {
return 0, ErrGasUintOverflow
}
gas, overflow = bits.Add64(gas, product, 0)
if overflow != 0 {
gas, overflow = math.SafeAdd(gas, product)
if overflow {
return 0, ErrGasUintOverflow
}

if isContractCreation && isEIP3860 {
numWords := vm.ToWordSize(dataLen)
product, overflow = math.SafeMul(numWords, params.InitCodeWordGas)
if overflow {
return 0, ErrGasUintOverflow
}
gas, overflow = math.SafeAdd(gas, product)
if overflow {
return 0, ErrGasUintOverflow
}
}
}
if accessList != nil {
overflow, product = bits.Mul64(uint64(len(accessList)), params.TxAccessListAddressGas)
if overflow != 0 {
product, overflow := math.SafeMul(uint64(len(accessList)), params.TxAccessListAddressGas)
if overflow {
return 0, ErrGasUintOverflow
}
gas, overflow = bits.Add64(gas, product, 0)
if overflow != 0 {
gas, overflow = math.SafeAdd(gas, product)
if overflow {
return 0, ErrGasUintOverflow
}

overflow, product = bits.Mul64(uint64(accessList.StorageKeys()), params.TxAccessListStorageKeyGas)
if overflow != 0 {
product, overflow = math.SafeMul(uint64(accessList.StorageKeys()), params.TxAccessListStorageKeyGas)
if overflow {
return 0, ErrGasUintOverflow
}
gas, overflow = bits.Add64(gas, product, 0)
if overflow != 0 {
gas, overflow = math.SafeAdd(gas, product)
if overflow {
return 0, ErrGasUintOverflow
}
}
Expand Down Expand Up @@ -391,7 +401,7 @@ func (st *StateTransition) TransitionDb(refunds bool, gasBailout bool) (*Executi
}

// 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.IsShanghai)
if err != nil {
return nil, err
}
Expand All @@ -408,6 +418,11 @@ func (st *StateTransition) TransitionDb(refunds bool, gasBailout bool) (*Executi
}
}

// Check whether the init code size has been exceeded.
if rules.IsShanghai && 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.
if rules.IsBerlin {
st.state.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList())
Expand Down
4 changes: 2 additions & 2 deletions core/vm/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ func getDataBig(data []byte, start *uint256.Int, size uint64) []byte {
return getData(data, start64, size)
}

// toWordSize returns the ceiled word size required for memory expansion.
func toWordSize(size uint64) uint64 {
// ToWordSize returns the ceiled word size required for memory expansion.
func ToWordSize(size uint64) uint64 {
if size > math.MaxUint64-31 {
return math.MaxUint64/32 + 1
}
Expand Down
8 changes: 8 additions & 0 deletions core/vm/eips.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (

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

// 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 @@ -32,6 +32,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
4 changes: 4 additions & 0 deletions core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,10 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
err = ErrContractAddressCollision
return nil, common.Address{}, 0, err
}
// Check whether the init code size has been exceeded.
if evm.chainRules.IsShanghai && len(codeAndHash.code) > params.MaxInitCodeSize {
return nil, address, gas, ErrMaxInitCodeSizeExceeded
}
// Create a new account on the state
snapshot := evm.intraBlockState.Snapshot()
evm.intraBlockState.CreateAccount(address, true)
Expand Down
27 changes: 21 additions & 6 deletions core/vm/gas_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, error) {
if newMemSize > 0x1FFFFFFFE0 {
return 0, ErrGasUintOverflow
}
newMemSizeWords := toWordSize(newMemSize)
newMemSizeWords := ToWordSize(newMemSize)
newMemSize = newMemSizeWords * 32

if newMemSize > uint64(mem.Len()) {
Expand Down Expand Up @@ -78,7 +78,7 @@ func memoryCopierGas(stackpos int) gasFunc {
return 0, ErrGasUintOverflow
}

if words, overflow = math.SafeMul(toWordSize(words), params.CopyGas); overflow {
if words, overflow = math.SafeMul(ToWordSize(words), params.CopyGas); overflow {
return 0, ErrGasUintOverflow
}

Expand Down Expand Up @@ -261,7 +261,7 @@ func gasKeccak256(evm *EVM, contract *Contract, stack *stack.Stack, mem *Memory,
if overflow {
return 0, ErrGasUintOverflow
}
if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Keccak256WordGas); overflow {
if wordGas, overflow = math.SafeMul(ToWordSize(wordGas), params.Keccak256WordGas); overflow {
return 0, ErrGasUintOverflow
}
if gas, overflow = math.SafeAdd(gas, wordGas); overflow {
Expand All @@ -287,18 +287,33 @@ var (
)

func gasCreate2(evm *EVM, contract *Contract, stack *stack.Stack, mem *Memory, memorySize uint64) (uint64, error) {
return gasCreateImplementation(stack, mem, memorySize, params.Keccak256WordGas)
}

func gasCreateEip3860(evm *EVM, contract *Contract, stack *stack.Stack, mem *Memory, memorySize uint64) (uint64, error) {
return gasCreateImplementation(stack, mem, memorySize, params.InitCodeWordGas)
}

func gasCreate2Eip3860(evm *EVM, contract *Contract, stack *stack.Stack, mem *Memory, memorySize uint64) (uint64, error) {
return gasCreateImplementation(stack, mem, memorySize, params.Keccak256WordGas+params.InitCodeWordGas)
}

func gasCreateImplementation(stack *stack.Stack, mem *Memory, memorySize uint64, wordCost uint64) (uint64, error) {
gas, err := memoryGasCost(mem, memorySize)
if err != nil {
return 0, err
}
wordGas, overflow := stack.Back(2).Uint64WithOverflow()
len, overflow := stack.Back(2).Uint64WithOverflow()
if overflow {
return 0, ErrGasUintOverflow
}
if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Keccak256WordGas); overflow {
numWords := ToWordSize(len)
wordGas, overflow := math.SafeMul(numWords, wordCost)
if overflow {
return 0, ErrGasUintOverflow
}
if gas, overflow = math.SafeAdd(gas, wordGas); overflow {
gas, overflow = math.SafeAdd(gas, wordGas)
if overflow {
return 0, ErrGasUintOverflow
}
return gas, nil
Expand Down
47 changes: 47 additions & 0 deletions core/vm/gas_table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,50 @@ func TestEIP2200(t *testing.T) {
})
}
}

var createGasTests = []struct {
code string
eip3860 bool
gasUsed uint64
}{
// create(0, 0, 0xc000)
{"0x61C00060006000f0", false, 41225},
// create(0, 0, 0xc000)
{"0x61C00060006000f0", true, 44297},
// create2(0, 0, 0xc000, 0)
{"0x600061C00060006000f5", false, 50444},
// create2(0, 0, 0xc000, 0)
{"0x600061C00060006000f5", true, 53516},
}

func TestCreateGas(t *testing.T) {
for i, tt := range createGasTests {
address := common.BytesToAddress([]byte("contract"))
_, tx := memdb.NewTestTx(t)

s := state.New(state.NewPlainStateReader(tx))
s.CreateAccount(address, true)
s.SetCode(address, hexutil.MustDecode(tt.code))
_ = s.CommitBlock(params.AllEthashProtocolChanges.Rules(0), state.NewPlainStateWriter(tx, tx, 0))

vmctx := BlockContext{
CanTransfer: func(IntraBlockState, common.Address, *uint256.Int) bool { return true },
Transfer: func(IntraBlockState, common.Address, common.Address, *uint256.Int, bool) {},
}
config := Config{}
if tt.eip3860 {
config.ExtraEips = []int{3860}
}

vmenv := NewEVM(vmctx, TxContext{}, s, params.AllEthashProtocolChanges, config)

var startGas uint64 = math.MaxUint64
_, gas, err := vmenv.Call(AccountRef(common.Address{}), address, nil, startGas, new(uint256.Int), false /* bailout */)
if err != nil {
t.Errorf("test %d execution failed: %v", i, err)
}
if gasUsed := startGas - gas; gasUsed != tt.gasUsed {
t.Errorf("test %d: gas used mismatch: have %v, want %v", i, gasUsed, tt.gasUsed)
}
}
}
14 changes: 13 additions & 1 deletion core/vm/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,17 @@ type VM struct {
returnData []byte // Last CALL's return data for subsequent reuse
}

func copyJumpTable(jt *JumpTable) *JumpTable {
var copy JumpTable
for i, op := range jt {
if op != nil {
opCopy := *op
copy[i] = &opCopy
}
}
return &copy
}

// NewEVMInterpreter returns a new instance of the Interpreter.
func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
var jt *JumpTable
Expand Down Expand Up @@ -115,6 +126,7 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
jt = &frontierInstructionSet
}
if len(cfg.ExtraEips) > 0 {
jt = copyJumpTable(jt)
for i, eip := range cfg.ExtraEips {
if err := EnableEIP(eip, jt); err != nil {
// Disable it, so caller can check if it's activated or not
Expand Down Expand Up @@ -299,7 +311,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
}
// memory is expanded in words of 32 bytes. Gas
// is also calculated in words.
if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
if memorySize, overflow = math.SafeMul(ToWordSize(memSize), 32); overflow {
return nil, ErrGasUintOverflow
}
}
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 @@ -85,6 +85,7 @@ func newCancunInstructionSet() JumpTable {
func newShanghaiInstructionSet() JumpTable {
instructionSet := newLondonInstructionSet()
enable3855(&instructionSet) // PUSH0 instruction https://eips.ethereum.org/EIPS/eip-3855
enable3860(&instructionSet) // Limit and meter initcode https://eips.ethereum.org/EIPS/eip-3860
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 @@ -600,7 +600,8 @@ func (jst *Tracer) CaptureStart(env *vm.EVM, depth int, from common.Address, to
// Compute intrinsic gas
isHomestead := env.ChainConfig().IsHomestead(env.Context().BlockNumber)
isIstanbul := env.ChainConfig().IsIstanbul(env.Context().BlockNumber)
intrinsicGas, err := core.IntrinsicGas(input, nil, jst.ctx["type"] == "CREATE", isHomestead, isIstanbul)
isShanghai := env.ChainConfig().IsShanghai(env.Context().BlockNumber)
intrinsicGas, err := core.IntrinsicGas(input, nil, jst.ctx["type"] == "CREATE", isHomestead, isIstanbul, isShanghai)
if err != nil {
return
}
Expand Down
4 changes: 3 additions & 1 deletion params/protocol_params.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const (

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.

SstoreSetGas uint64 = 20000 // Once per SLOAD operation.
SstoreResetGas uint64 = 5000 // Once per SSTORE operation if the zeroness changes from zero.
Expand Down Expand Up @@ -125,7 +126,8 @@ const (
ElasticityMultiplier = 2 // Bounds the maximum gas limit an EIP-1559 block may have.
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

Expand Down
2 changes: 1 addition & 1 deletion tests/transaction_test_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func (tt *TransactionTest) Run(chainID *big.Int) error {
sender := msg.From()

// Intrinsic gas
requiredGas, err := core.IntrinsicGas(msg.Data(), msg.AccessList(), msg.To() == nil, rules.IsHomestead, rules.IsIstanbul)
requiredGas, err := core.IntrinsicGas(msg.Data(), msg.AccessList(), msg.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
if err != nil {
return nil, nil, 0, err
}
Expand Down