From df2ec6d63cb5b07d2b285e9924e449109b7baceb Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 20 Jun 2019 13:41:29 +0200 Subject: [PATCH] core/vm, tests: implement EIP 1884, add support for feature-tests --- core/vm/eips.go | 58 ++++++++++++++++++++++++++++++++++++++++ core/vm/interpreter.go | 24 ++++++++++++----- core/vm/jump_table.go | 16 ++++++----- core/vm/opcodes.go | 15 ++++++----- tests/state_test_util.go | 26 +++++++++++++++++- 5 files changed, 119 insertions(+), 20 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..f1f73a1313 --- /dev/null +++ b/core/vm/eips.go @@ -0,0 +1,58 @@ +// 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" + + + +// EnableEIP enables the given EIP on the config. +// This operation write in-place, and callers need to ensure that the globally +// defined jumptables 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 jumptable +// - Increase cost of BALANCE to 700 +// - Increase cost of SLOAD to 800 +// - Define SELFBALANCE, with cost GasFastStep +func enable1884(jt *JumpTable) { + // Gas cost changes + jt[BALANCE].constantGas = 700 + jt[SLOAD].constantGas = 800 + // 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..ae4a99539a 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 %d failed activation: %v", err) + } + } + cfg.JumpTable = jt } return &EVMInterpreter{ diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index a14f3fd980..bde9b76078 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -62,9 +62,11 @@ var ( constantinopleInstructionSet = newConstantinopleInstructionSet() ) +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{ @@ -111,7 +113,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 +156,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 +164,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 +178,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 +195,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..681440bc9f 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -101,6 +101,7 @@ const ( NUMBER DIFFICULTY GASLIMIT + SELFBALANCE ) // 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/tests/state_test_util.go b/tests/state_test_util.go index 0b78f26ed1..881dd4020a 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -109,6 +109,30 @@ 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, "+") + eipStrings []string + ok bool + ) + baseName, eipsStrings = splitForks[0], splitForks[1:] + if baseConfig, ok = Forks[baseName]; !ok { + return nil, UnsupportedForkError{subtest.Fork} + } + for _, eip := range eips { + if eipNum, err := strconv.Atoi(eip); err != nil { + return nil, fmt.Errorf("syntax error, invalid eip number %v", eipNum) + } else { + eips = append(eips, eipNum) + } + } + return baseConfig, eips +} + // Subtests returns all valid subtests of the test. func (t *StateTest) Subtests() []StateSubtest { var sub []StateSubtest @@ -122,7 +146,7 @@ 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] + config, eips, ok := getVMConfig(subtest.Fork) if !ok { return nil, UnsupportedForkError{subtest.Fork} }