EIP-3860: Limit and meter initcode (#5892)

[EIP-3860](https://eips.ethereum.org/EIPS/eip-3860) is
[included](https://github.com/ethereum/execution-specs/pull/633) into
[Shanghai](https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/shanghai.md).

TODO: similar changes to `txpool`.

Co-authored-by: Andrei Maiboroda <andrei@ethereum.org>
This commit is contained in:
Andrew Ashikhmin 2022-10-31 13:40:41 +01:00 committed by GitHub
parent 3163f40e58
commit 9ffc457cbb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 146 additions and 36 deletions

View File

@ -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")

View File

@ -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"
@ -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 {
@ -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 {
@ -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
}
}
@ -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
}
@ -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())

View File

@ -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
}

View File

@ -27,6 +27,7 @@ import (
var activators = map[int]func(*JumpTable){
3855: enable3855,
3860: enable3860,
3529: enable3529,
3198: enable3198,
2929: enable2929,
@ -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
}

View File

@ -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")

View File

@ -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)

View File

@ -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()) {
@ -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
}
@ -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 {
@ -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

View File

@ -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)
}
}
}

View File

@ -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
@ -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
@ -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
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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.
@ -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

View File

@ -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
}