core/vm: more extensive testcase generation and execution of opcodes

This commit is contained in:
Martin Holst Swende 2018-03-16 16:25:08 +01:00
parent 92faf1bf7a
commit 17bc30bda9
No known key found for this signature in database
GPG key ID: 683B438C05A5DDF0
22 changed files with 158 additions and 86 deletions

View file

@ -18,6 +18,9 @@ package vm
import ( import (
"bytes" "bytes"
"encoding/json"
"fmt"
"io/ioutil"
"math/big" "math/big"
"testing" "testing"
@ -26,32 +29,91 @@ import (
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
type twoOperandTest struct { type TwoOperandTestcase struct {
x string X string
y string Y string
expected string Expected string
} }
func testTwoOperandOp(t *testing.T, tests []twoOperandTest, opFn func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error)) { type twoOperandParams struct {
x string
y string
}
var commonParams []*twoOperandParams
var twoOpMethods map[string]executionFunc
func init() {
// Params is a list of common edgecases that should be used for some common tests
params := []string{
"0000000000000000000000000000000000000000000000000000000000000000", // 0
"0000000000000000000000000000000000000000000000000000000000000001", // +1
"0000000000000000000000000000000000000000000000000000000000000005", // +5
"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe", // + max -1
"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // + max
"8000000000000000000000000000000000000000000000000000000000000000", // - max
"8000000000000000000000000000000000000000000000000000000000000001", // - max+1
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb", // - 5
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // - 1
}
// Params are combined so each param is used on each 'side'
commonParams = make([]*twoOperandParams, len(params)*len(params))
for i, x := range params {
for j, y := range params {
commonParams[i*len(params)+j] = &twoOperandParams{x, y}
}
}
twoOpMethods = map[string]executionFunc{
"add": opAdd,
"sub": opSub,
"mul": opMul,
"div": opDiv,
"sdiv": opSdiv,
"mod": opMod,
"smod": opSmod,
"exp": opExp,
"signext": opSignExtend,
"lt": opLt,
"gt": opGt,
"slt": opSlt,
"sgt": opSgt,
"eq": opEq,
"and": opAnd,
"or": opOr,
"xor": opXor,
"byte": opByte,
"shl": opSHL,
"shr": opSHR,
"sar": opSAR,
}
}
func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFunc, name string) {
var ( var (
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) env = NewEVM(Context{}, nil, params.TestChainConfig, Config{})
stack = newstack() stack = newstack()
pc = uint64(0) pc = uint64(0)
evmInterpreter = NewEVMInterpreter(env, env.vmConfig) evmInterpreter = env.interpreter.(*EVMInterpreter)
) )
// Stuff a couple of nonzero bigints into pool, to ensure that ops do not rely on pooled integers to be zero
env.interpreter = evmInterpreter
evmInterpreter.intPool = poolOfIntPools.get() evmInterpreter.intPool = poolOfIntPools.get()
evmInterpreter.intPool.put(big.NewInt(-1337))
evmInterpreter.intPool.put(big.NewInt(-1337))
evmInterpreter.intPool.put(big.NewInt(-1337))
for i, test := range tests { for i, test := range tests {
x := new(big.Int).SetBytes(common.Hex2Bytes(test.x)) x := new(big.Int).SetBytes(common.Hex2Bytes(test.X))
shift := new(big.Int).SetBytes(common.Hex2Bytes(test.y)) y := new(big.Int).SetBytes(common.Hex2Bytes(test.Y))
expected := new(big.Int).SetBytes(common.Hex2Bytes(test.expected)) expected := new(big.Int).SetBytes(common.Hex2Bytes(test.Expected))
stack.push(x) stack.push(x)
stack.push(shift) stack.push(y)
opFn(&pc, evmInterpreter, nil, nil, stack) opFn(&pc, evmInterpreter, nil, nil, stack)
actual := stack.pop() actual := stack.pop()
if actual.Cmp(expected) != 0 { if actual.Cmp(expected) != 0 {
t.Errorf("Testcase %d, expected %v, got %v", i, expected, actual) t.Errorf("Testcase %v %d, %v(%x, %x): expected %x, got %x", name, i, name, x, y, expected, actual)
} }
// Check pool usage // Check pool usage
// 1.pool is not allowed to contain anything on the stack // 1.pool is not allowed to contain anything on the stack
@ -64,7 +126,7 @@ func testTwoOperandOp(t *testing.T, tests []twoOperandTest, opFn func(pc *uint64
for evmInterpreter.intPool.pool.len() > 0 { for evmInterpreter.intPool.pool.len() > 0 {
key := evmInterpreter.intPool.get() key := evmInterpreter.intPool.get()
if _, exist := poolvals[key]; exist { if _, exist := poolvals[key]; exist {
t.Errorf("Testcase %d, pool contains double-entry", i) t.Errorf("Testcase %v %d, pool contains double-entry", name, i)
} }
poolvals[key] = struct{}{} poolvals[key] = struct{}{}
} }
@ -74,47 +136,22 @@ func testTwoOperandOp(t *testing.T, tests []twoOperandTest, opFn func(pc *uint64
} }
func TestByteOp(t *testing.T) { func TestByteOp(t *testing.T) {
var ( tests := []TwoOperandTestcase{
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) {"ABCDEF0908070605040302010000000000000000000000000000000000000000", "00", "AB"},
stack = newstack() {"ABCDEF0908070605040302010000000000000000000000000000000000000000", "01", "CD"},
evmInterpreter = NewEVMInterpreter(env, env.vmConfig) {"00CDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff", "00", "00"},
) {"00CDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff", "01", "CD"},
{"0000000000000000000000000000000000000000000000000000000000102030", "1F", "30"},
env.interpreter = evmInterpreter {"0000000000000000000000000000000000000000000000000000000000102030", "1E", "20"},
evmInterpreter.intPool = poolOfIntPools.get() {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "20", "00"},
tests := []struct { {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "FFFFFFFFFFFFFFFF", "00"},
v string
th uint64
expected *big.Int
}{
{"ABCDEF0908070605040302010000000000000000000000000000000000000000", 0, big.NewInt(0xAB)},
{"ABCDEF0908070605040302010000000000000000000000000000000000000000", 1, big.NewInt(0xCD)},
{"00CDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff", 0, big.NewInt(0x00)},
{"00CDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff", 1, big.NewInt(0xCD)},
{"0000000000000000000000000000000000000000000000000000000000102030", 31, big.NewInt(0x30)},
{"0000000000000000000000000000000000000000000000000000000000102030", 30, big.NewInt(0x20)},
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 32, big.NewInt(0x0)},
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 0xFFFFFFFFFFFFFFFF, big.NewInt(0x0)},
} }
pc := uint64(0) testTwoOperandOp(t, tests, opByte, "byte")
for _, test := range tests {
val := new(big.Int).SetBytes(common.Hex2Bytes(test.v))
th := new(big.Int).SetUint64(test.th)
stack.push(val)
stack.push(th)
opByte(&pc, evmInterpreter, nil, nil, stack)
actual := stack.pop()
if actual.Cmp(test.expected) != 0 {
t.Fatalf("Expected [%v] %v:th byte to be %v, was %v.", test.v, test.th, test.expected, actual)
}
}
poolOfIntPools.put(evmInterpreter.intPool)
} }
func TestSHL(t *testing.T) { func TestSHL(t *testing.T) {
// Testcases from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-145.md#shl-shift-left // Testcases from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-145.md#shl-shift-left
tests := []twoOperandTest{ tests := []TwoOperandTestcase{
{"0000000000000000000000000000000000000000000000000000000000000001", "00", "0000000000000000000000000000000000000000000000000000000000000001"},
{"0000000000000000000000000000000000000000000000000000000000000001", "01", "0000000000000000000000000000000000000000000000000000000000000002"}, {"0000000000000000000000000000000000000000000000000000000000000001", "01", "0000000000000000000000000000000000000000000000000000000000000002"},
{"0000000000000000000000000000000000000000000000000000000000000001", "ff", "8000000000000000000000000000000000000000000000000000000000000000"}, {"0000000000000000000000000000000000000000000000000000000000000001", "ff", "8000000000000000000000000000000000000000000000000000000000000000"},
{"0000000000000000000000000000000000000000000000000000000000000001", "0100", "0000000000000000000000000000000000000000000000000000000000000000"}, {"0000000000000000000000000000000000000000000000000000000000000001", "0100", "0000000000000000000000000000000000000000000000000000000000000000"},
@ -126,12 +163,12 @@ func TestSHL(t *testing.T) {
{"0000000000000000000000000000000000000000000000000000000000000000", "01", "0000000000000000000000000000000000000000000000000000000000000000"}, {"0000000000000000000000000000000000000000000000000000000000000000", "01", "0000000000000000000000000000000000000000000000000000000000000000"},
{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "01", "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"}, {"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "01", "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},
} }
testTwoOperandOp(t, tests, opSHL) testTwoOperandOp(t, tests, opSHL, "shl")
} }
func TestSHR(t *testing.T) { func TestSHR(t *testing.T) {
// Testcases from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-145.md#shr-logical-shift-right // Testcases from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-145.md#shr-logical-shift-right
tests := []twoOperandTest{ tests := []TwoOperandTestcase{
{"0000000000000000000000000000000000000000000000000000000000000001", "00", "0000000000000000000000000000000000000000000000000000000000000001"}, {"0000000000000000000000000000000000000000000000000000000000000001", "00", "0000000000000000000000000000000000000000000000000000000000000001"},
{"0000000000000000000000000000000000000000000000000000000000000001", "01", "0000000000000000000000000000000000000000000000000000000000000000"}, {"0000000000000000000000000000000000000000000000000000000000000001", "01", "0000000000000000000000000000000000000000000000000000000000000000"},
{"8000000000000000000000000000000000000000000000000000000000000000", "01", "4000000000000000000000000000000000000000000000000000000000000000"}, {"8000000000000000000000000000000000000000000000000000000000000000", "01", "4000000000000000000000000000000000000000000000000000000000000000"},
@ -144,12 +181,12 @@ func TestSHR(t *testing.T) {
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0100", "0000000000000000000000000000000000000000000000000000000000000000"}, {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0100", "0000000000000000000000000000000000000000000000000000000000000000"},
{"0000000000000000000000000000000000000000000000000000000000000000", "01", "0000000000000000000000000000000000000000000000000000000000000000"}, {"0000000000000000000000000000000000000000000000000000000000000000", "01", "0000000000000000000000000000000000000000000000000000000000000000"},
} }
testTwoOperandOp(t, tests, opSHR) testTwoOperandOp(t, tests, opSHR, "shr")
} }
func TestSAR(t *testing.T) { func TestSAR(t *testing.T) {
// Testcases from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-145.md#sar-arithmetic-shift-right // Testcases from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-145.md#sar-arithmetic-shift-right
tests := []twoOperandTest{ tests := []TwoOperandTestcase{
{"0000000000000000000000000000000000000000000000000000000000000001", "00", "0000000000000000000000000000000000000000000000000000000000000001"}, {"0000000000000000000000000000000000000000000000000000000000000001", "00", "0000000000000000000000000000000000000000000000000000000000000001"},
{"0000000000000000000000000000000000000000000000000000000000000001", "01", "0000000000000000000000000000000000000000000000000000000000000000"}, {"0000000000000000000000000000000000000000000000000000000000000001", "01", "0000000000000000000000000000000000000000000000000000000000000000"},
{"8000000000000000000000000000000000000000000000000000000000000000", "01", "c000000000000000000000000000000000000000000000000000000000000000"}, {"8000000000000000000000000000000000000000000000000000000000000000", "01", "c000000000000000000000000000000000000000000000000000000000000000"},
@ -168,44 +205,58 @@ func TestSAR(t *testing.T) {
{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0100", "0000000000000000000000000000000000000000000000000000000000000000"}, {"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0100", "0000000000000000000000000000000000000000000000000000000000000000"},
} }
testTwoOperandOp(t, tests, opSAR) testTwoOperandOp(t, tests, opSAR, "sar")
} }
func TestSGT(t *testing.T) { // getResult is a convenience function to generate the expected values
tests := []twoOperandTest{ func getResult(args []*twoOperandParams, opFn executionFunc) []TwoOperandTestcase {
var (
{"0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000"}, env = NewEVM(Context{}, nil, params.TestChainConfig, Config{})
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000000"}, stack = newstack()
{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000000"}, pc = uint64(0)
{"0000000000000000000000000000000000000000000000000000000000000001", "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000001"}, interpreter = env.interpreter.(*EVMInterpreter)
{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000"}, )
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000001"}, interpreter.intPool = poolOfIntPools.get()
{"0000000000000000000000000000000000000000000000000000000000000001", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000000"}, result := make([]TwoOperandTestcase, len(args))
{"8000000000000000000000000000000000000000000000000000000000000001", "8000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000"}, for i, param := range args {
{"8000000000000000000000000000000000000000000000000000000000000001", "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000001"}, x := new(big.Int).SetBytes(common.Hex2Bytes(param.x))
{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "8000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000"}, y := new(big.Int).SetBytes(common.Hex2Bytes(param.y))
{"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb", "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd", "0000000000000000000000000000000000000000000000000000000000000001"}, stack.push(x)
{"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd", "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb", "0000000000000000000000000000000000000000000000000000000000000000"}, stack.push(y)
opFn(&pc, interpreter, nil, nil, stack)
actual := stack.pop()
result[i] = TwoOperandTestcase{param.x, param.y, fmt.Sprintf("%064x", actual)}
} }
testTwoOperandOp(t, tests, opSgt) return result
} }
func TestSLT(t *testing.T) { // utility function to fill the json-file with testcases
tests := []twoOperandTest{ // Enable this test to generate the 'testcases_xx.json' files
{"0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000"}, func xTestWriteExpectedValues(t *testing.T) {
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000000"}, for name, method := range twoOpMethods {
{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000000"}, data, err := json.Marshal(getResult(commonParams, method))
{"0000000000000000000000000000000000000000000000000000000000000001", "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000000"}, if err != nil {
{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000001"}, t.Fatal(err)
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000"}, }
{"0000000000000000000000000000000000000000000000000000000000000001", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000001"}, _ = ioutil.WriteFile(fmt.Sprintf("testdata/testcases_%v.json", name), data, 0644)
{"8000000000000000000000000000000000000000000000000000000000000001", "8000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000"}, if err != nil {
{"8000000000000000000000000000000000000000000000000000000000000001", "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000000"}, t.Fatal(err)
{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "8000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000001"}, }
{"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb", "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd", "0000000000000000000000000000000000000000000000000000000000000000"}, }
{"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd", "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb", "0000000000000000000000000000000000000000000000000000000000000001"}, t.Fatal("This test should not be activated")
}
// TestJsonTestcases runs through all the testcases defined as json-files
func TestJsonTestcases(t *testing.T) {
for name, _ := range twoOpMethods {
data, err := ioutil.ReadFile(fmt.Sprintf("testdata/testcases_%v.json", name))
if err != nil {
t.Fatal("Failed to read file", err)
}
var testcases []TwoOperandTestcase
json.Unmarshal(data, &testcases)
testTwoOperandOp(t, testcases, twoOpMethods[name], name)
} }
testTwoOperandOp(t, tests, opSlt)
} }
func opBenchmark(bench *testing.B, op func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error), args ...string) { func opBenchmark(bench *testing.B, op func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error), args ...string) {

1
core/vm/testdata/testcases_add.json vendored Normal file

File diff suppressed because one or more lines are too long

1
core/vm/testdata/testcases_and.json vendored Normal file

File diff suppressed because one or more lines are too long

1
core/vm/testdata/testcases_byte.json vendored Normal file

File diff suppressed because one or more lines are too long

1
core/vm/testdata/testcases_div.json vendored Normal file

File diff suppressed because one or more lines are too long

1
core/vm/testdata/testcases_eq.json vendored Normal file

File diff suppressed because one or more lines are too long

1
core/vm/testdata/testcases_exp.json vendored Normal file

File diff suppressed because one or more lines are too long

1
core/vm/testdata/testcases_gt.json vendored Normal file

File diff suppressed because one or more lines are too long

1
core/vm/testdata/testcases_lt.json vendored Normal file

File diff suppressed because one or more lines are too long

1
core/vm/testdata/testcases_mod.json vendored Normal file

File diff suppressed because one or more lines are too long

1
core/vm/testdata/testcases_mul.json vendored Normal file

File diff suppressed because one or more lines are too long

1
core/vm/testdata/testcases_or.json vendored Normal file

File diff suppressed because one or more lines are too long

1
core/vm/testdata/testcases_sar.json vendored Normal file

File diff suppressed because one or more lines are too long

1
core/vm/testdata/testcases_sdiv.json vendored Normal file

File diff suppressed because one or more lines are too long

1
core/vm/testdata/testcases_sgt.json vendored Normal file

File diff suppressed because one or more lines are too long

1
core/vm/testdata/testcases_shl.json vendored Normal file

File diff suppressed because one or more lines are too long

1
core/vm/testdata/testcases_shr.json vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
core/vm/testdata/testcases_slt.json vendored Normal file

File diff suppressed because one or more lines are too long

1
core/vm/testdata/testcases_smod.json vendored Normal file

File diff suppressed because one or more lines are too long

1
core/vm/testdata/testcases_sub.json vendored Normal file

File diff suppressed because one or more lines are too long

1
core/vm/testdata/testcases_xor.json vendored Normal file

File diff suppressed because one or more lines are too long