From 3e993ff64a9c2e9651fae11aaee55032cd6b0c3e Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 8 Aug 2019 11:07:23 +0200 Subject: [PATCH] Eip 1884 v3 (#19743) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * core/vm, tests: implement EIP 1884, add support for feature-tests * core/vm: 1884-changes to extcodehash, move selfbalance opcode * tests: fix statetests * core/vm: move constants, address review concerns * core/vm: word formatting Co-Authored-By: Péter Szilágyi --- core/vm/eips.go | 63 +++++++++++++++++++++++++++++++++++++++ core/vm/interpreter.go | 24 +++++++++++---- core/vm/jump_table.go | 19 +++++++----- core/vm/opcodes.go | 15 ++++++---- params/protocol_params.go | 23 +++++++------- tests/state_test_util.go | 29 ++++++++++++++++-- 6 files changed, 141 insertions(+), 32 deletions(-) create mode 100644 core/vm/eips.go diff --git a/core/vm/eips.go b/core/vm/eips.go new file mode 100644 index 0000000000..89e76c0271 --- /dev/null +++ b/core/vm/eips.go @@ -0,0 +1,63 @@ +// Copyright 2019 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 . + +package vm + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/params" +) + +// EnableEIP enables the given EIP on the config. +// This operation writes in-place, and callers need to ensure that the globally +// defined jump tables are not polluted. +func EnableEIP(eipNum int, jt *JumpTable) error { + switch eipNum { + case 1884: + enable1884(jt) + default: + return fmt.Errorf("undefined eip %d", eipNum) + } + return nil +} + +// enable1884 applies EIP-1884 to the given jump table: +// - Increase cost of BALANCE to 700 +// - Increase cost of EXTCODEHASH to 700 +// - Increase cost of SLOAD to 800 +// - Define SELFBALANCE, with cost GasFastStep (5) +func enable1884(jt *JumpTable) { + // Gas cost changes + jt[BALANCE].constantGas = params.BalanceGasEIP1884 + jt[EXTCODEHASH].constantGas = params.ExtcodeHashGasEIP1884 + jt[SLOAD].constantGas = params.SloadGasEIP1884 + + // New opcode + jt[SELFBALANCE] = operation{ + execute: opSelfBalance, + constantGas: GasFastStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + valid: true, + } +} + +func opSelfBalance(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { + balance := interpreter.intPool.get().Set(interpreter.evm.StateDB.GetBalance(contract.Address())) + stack.push(balance) + return nil, nil +} diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 18081ec4c9..be6e00a6e6 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -23,6 +23,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/log" ) // Config are the configuration options for the Interpreter @@ -36,6 +37,8 @@ type Config struct { EWASMInterpreter string // External EWASM interpreter options EVMInterpreter string // External EVM interpreter options + + ExtraEips []int // Additional EIPS that are to be enabled } // Interpreter is used to run Ethereum based contracts and will utilise the @@ -88,20 +91,29 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter { // the jump table was initialised. If it was not // we'll set the default jump table. if !cfg.JumpTable[STOP].valid { + var jt JumpTable switch { case evm.chainRules.IsConstantinople: - cfg.JumpTable = constantinopleInstructionSet + jt = constantinopleInstructionSet case evm.chainRules.IsByzantium: - cfg.JumpTable = byzantiumInstructionSet + jt = byzantiumInstructionSet case evm.chainRules.IsEIP158: - cfg.JumpTable = spuriousDragonInstructionSet + jt = spuriousDragonInstructionSet case evm.chainRules.IsEIP150: - cfg.JumpTable = tangerineWhistleInstructionSet + jt = tangerineWhistleInstructionSet case evm.chainRules.IsHomestead: - cfg.JumpTable = homesteadInstructionSet + jt = homesteadInstructionSet default: - cfg.JumpTable = frontierInstructionSet + jt = frontierInstructionSet } + 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 + cfg.ExtraEips = append(cfg.ExtraEips[:i], cfg.ExtraEips[i+1:]...) + log.Error("EIP activation failed", "eip", eip, "error", err) + } + } + cfg.JumpTable = jt } return &EVMInterpreter{ diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index a14f3fd980..da532541c6 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -62,9 +62,12 @@ var ( constantinopleInstructionSet = newConstantinopleInstructionSet() ) +// JumpTable contains the EVM opcodes supported at a given fork. +type JumpTable [256]operation + // NewConstantinopleInstructionSet returns the frontier, homestead // byzantium and contantinople instructions. -func newConstantinopleInstructionSet() [256]operation { +func newConstantinopleInstructionSet() JumpTable { // instructions that can be executed during the byzantium phase. instructionSet := newByzantiumInstructionSet() instructionSet[SHL] = operation{ @@ -90,7 +93,7 @@ func newConstantinopleInstructionSet() [256]operation { } instructionSet[EXTCODEHASH] = operation{ execute: opExtCodeHash, - constantGas: params.ExtcodeHashGas, + constantGas: params.ExtcodeHashGasConstantinople, minStack: minStack(1, 1), maxStack: maxStack(1, 1), valid: true, @@ -111,7 +114,7 @@ func newConstantinopleInstructionSet() [256]operation { // NewByzantiumInstructionSet returns the frontier, homestead and // byzantium instructions. -func newByzantiumInstructionSet() [256]operation { +func newByzantiumInstructionSet() JumpTable { // instructions that can be executed during the homestead phase. instructionSet := newSpuriousDragonInstructionSet() instructionSet[STATICCALL] = operation{ @@ -154,7 +157,7 @@ func newByzantiumInstructionSet() [256]operation { } // EIP 158 a.k.a Spurious Dragon -func newSpuriousDragonInstructionSet() [256]operation { +func newSpuriousDragonInstructionSet() JumpTable { instructionSet := newTangerineWhistleInstructionSet() instructionSet[EXP].dynamicGas = gasExpEIP158 return instructionSet @@ -162,7 +165,7 @@ func newSpuriousDragonInstructionSet() [256]operation { } // EIP 150 a.k.a Tangerine Whistle -func newTangerineWhistleInstructionSet() [256]operation { +func newTangerineWhistleInstructionSet() JumpTable { instructionSet := newHomesteadInstructionSet() instructionSet[BALANCE].constantGas = params.BalanceGasEIP150 instructionSet[EXTCODESIZE].constantGas = params.ExtcodeSizeGasEIP150 @@ -176,7 +179,7 @@ func newTangerineWhistleInstructionSet() [256]operation { // NewHomesteadInstructionSet returns the frontier and homestead // instructions that can be executed during the homestead phase. -func newHomesteadInstructionSet() [256]operation { +func newHomesteadInstructionSet() JumpTable { instructionSet := newFrontierInstructionSet() instructionSet[DELEGATECALL] = operation{ execute: opDelegateCall, @@ -193,8 +196,8 @@ func newHomesteadInstructionSet() [256]operation { // NewFrontierInstructionSet returns the frontier instructions // that can be executed during the frontier phase. -func newFrontierInstructionSet() [256]operation { - return [256]operation{ +func newFrontierInstructionSet() JumpTable { + return JumpTable{ STOP: { execute: opStop, constantGas: 0, diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index 4349ffd295..b385fd14ad 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -101,6 +101,7 @@ const ( NUMBER DIFFICULTY GASLIMIT + SELFBALANCE = 0x47 ) // 0x50 range - 'storage' and execution. @@ -271,12 +272,13 @@ var opCodeToString = map[OpCode]string{ EXTCODEHASH: "EXTCODEHASH", // 0x40 range - block operations. - BLOCKHASH: "BLOCKHASH", - COINBASE: "COINBASE", - TIMESTAMP: "TIMESTAMP", - NUMBER: "NUMBER", - DIFFICULTY: "DIFFICULTY", - GASLIMIT: "GASLIMIT", + BLOCKHASH: "BLOCKHASH", + COINBASE: "COINBASE", + TIMESTAMP: "TIMESTAMP", + NUMBER: "NUMBER", + DIFFICULTY: "DIFFICULTY", + GASLIMIT: "GASLIMIT", + SELFBALANCE: "SELFBALANCE", // 0x50 range - 'storage' and execution. POP: "POP", @@ -444,6 +446,7 @@ var stringToOp = map[string]OpCode{ "NUMBER": NUMBER, "DIFFICULTY": DIFFICULTY, "GASLIMIT": GASLIMIT, + "SELFBALANCE": SELFBALANCE, "POP": POP, "MLOAD": MLOAD, "MSTORE": MSTORE, diff --git a/params/protocol_params.go b/params/protocol_params.go index 110fc16ecb..943788be8c 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -70,16 +70,19 @@ const ( TxDataNonZeroGas uint64 = 68 // Per byte of data attached to a transaction that is not equal to zero. NOTE: Not payable on data of calls between transactions. // These have been changed during the course of the chain - CallGasFrontier uint64 = 40 // Once per CALL operation & message call transaction. - CallGasEIP150 uint64 = 700 // Static portion of gas for CALL-derivates after EIP 150 (Tangerine) - BalanceGasFrontier uint64 = 20 // The cost of a BALANCE operation - BalanceGasEIP150 uint64 = 400 // The cost of a BALANCE operation after Tangerine - ExtcodeSizeGasFrontier uint64 = 20 // Cost of EXTCODESIZE before EIP 150 (Tangerine) - ExtcodeSizeGasEIP150 uint64 = 700 // Cost of EXTCODESIZE after EIP 150 (Tangerine) - SloadGasFrontier uint64 = 50 - SloadGasEIP150 uint64 = 200 - ExtcodeHashGas uint64 = 400 // Cost of EXTCODEHASH (introduced in Constantinople) - SelfdestructGasEIP150 uint64 = 5000 // Cost of SELFDESTRUCT post EIP 150 (Tangerine) + CallGasFrontier uint64 = 40 // Once per CALL operation & message call transaction. + CallGasEIP150 uint64 = 700 // Static portion of gas for CALL-derivates after EIP 150 (Tangerine) + BalanceGasFrontier uint64 = 20 // The cost of a BALANCE operation + BalanceGasEIP150 uint64 = 400 // The cost of a BALANCE operation after Tangerine + BalanceGasEIP1884 uint64 = 700 // The cost of a BALANCE operation after EIP 1884 (part of Istanbul) + ExtcodeSizeGasFrontier uint64 = 20 // Cost of EXTCODESIZE before EIP 150 (Tangerine) + ExtcodeSizeGasEIP150 uint64 = 700 // Cost of EXTCODESIZE after EIP 150 (Tangerine) + SloadGasFrontier uint64 = 50 + SloadGasEIP150 uint64 = 200 + SloadGasEIP1884 uint64 = 800 // Cost of SLOAD after EIP 1884 (part of Istanbul) + ExtcodeHashGasConstantinople uint64 = 400 // Cost of EXTCODEHASH (introduced in Constantinople) + ExtcodeHashGasEIP1884 uint64 = 700 // Cost of EXTCODEHASH after EIP 1884 (part in Istanbul) + SelfdestructGasEIP150 uint64 = 5000 // Cost of SELFDESTRUCT post EIP 150 (Tangerine) // EXP has a dynamic portion depending on the size of the exponent ExpByteFrontier uint64 = 10 // was set to 10 in Frontier diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 0b78f26ed1..c6341e5248 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -21,6 +21,7 @@ import ( "encoding/json" "fmt" "math/big" + "strconv" "strings" "github.com/ethereum/go-ethereum/common" @@ -109,6 +110,29 @@ type stTransactionMarshaling struct { PrivateKey hexutil.Bytes } +// getVMConfig takes a fork definition and returns a chain config. +// The fork definition can be +// - a plain forkname, e.g. `Byzantium`, +// - a fork basename, and a list of EIPs to enable; e.g. `Byzantium+1884+1283`. +func getVMConfig(forkString string) (baseConfig *params.ChainConfig, eips []int, err error) { + var ( + splitForks = strings.Split(forkString, "+") + ok bool + baseName, eipsStrings = splitForks[0], splitForks[1:] + ) + if baseConfig, ok = Forks[baseName]; !ok { + return nil, nil, UnsupportedForkError{baseName} + } + for _, eip := range eipsStrings { + if eipNum, err := strconv.Atoi(eip); err != nil { + return nil, nil, fmt.Errorf("syntax error, invalid eip number %v", eipNum) + } else { + eips = append(eips, eipNum) + } + } + return baseConfig, eips, nil +} + // Subtests returns all valid subtests of the test. func (t *StateTest) Subtests() []StateSubtest { var sub []StateSubtest @@ -122,10 +146,11 @@ func (t *StateTest) Subtests() []StateSubtest { // Run executes a specific subtest. func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config) (*state.StateDB, error) { - config, ok := Forks[subtest.Fork] - if !ok { + config, eips, err := getVMConfig(subtest.Fork) + if err != nil { return nil, UnsupportedForkError{subtest.Fork} } + vmconfig.ExtraEips = eips block := t.genesis(config).ToBlock(nil) statedb := MakePreState(rawdb.NewMemoryDatabase(), t.json.Pre)