From 5bd50c7bcdd6ce636b2d08dd882dc149484d9c91 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 6 Sep 2019 10:33:43 +0300 Subject: [PATCH 1/2] vm: add some tests for PACK instruction --- pkg/vm/stack.go | 12 ++++++++++++ pkg/vm/vm_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/pkg/vm/stack.go b/pkg/vm/stack.go index a08882d8d..17c6e8143 100644 --- a/pkg/vm/stack.go +++ b/pkg/vm/stack.go @@ -84,6 +84,18 @@ func (e *Element) Bytes() []byte { return e.value.Value().([]byte) } +// Array attempts to get the underlying value of the element as an array of +// other items. Will panic if the item type is different which will be caught +// by the VM. +func (e *Element) Array() []StackItem { + switch t := e.value.(type) { + case *ArrayItem: + return t.value + default: + panic("element is not an array") + } +} + // Stack represents a Stack backed by a double linked list. type Stack struct { top Element diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 8109e8067..ebdb50cab 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -633,6 +633,46 @@ func TestRIGHTBadLen(t *testing.T) { assert.Equal(t, true, vm.state.HasFlag(faultState)) } +func TestPACKBadLen(t *testing.T) { + prog := makeProgram(PACK) + vm := load(prog) + vm.estack.PushVal(1) + vm.Run() + assert.Equal(t, true, vm.state.HasFlag(faultState)) +} + +func TestPACKGoodZeroLen(t *testing.T) { + prog := makeProgram(PACK) + vm := load(prog) + vm.estack.PushVal(0) + vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, []StackItem{}, vm.estack.Peek(0).Array()) +} + +func TestPACKGood(t *testing.T) { + prog := makeProgram(PACK) + elements := []int{55, 34, 42} + vm := load(prog) + // canary + vm.estack.PushVal(1) + for i := len(elements) - 1; i >= 0; i-- { + vm.estack.PushVal(elements[i]) + } + vm.estack.PushVal(len(elements)) + vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) + assert.Equal(t, 2, vm.estack.Len()) + a := vm.estack.Peek(0).Array() + assert.Equal(t, len(elements), len(a)) + for i := 0; i < len(elements); i++ { + e := a[i].Value().(*big.Int) + assert.Equal(t, int64(elements[i]), e.Int64()) + } + assert.Equal(t, int64(1), vm.estack.Peek(1).BigInt().Int64()) +} + func makeProgram(opcodes ...Instruction) []byte { prog := make([]byte, len(opcodes)+1) // RET for i := 0; i < len(opcodes); i++ { From aefe572df3c64abeace35bd9d07fa6477a12ec19 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 6 Sep 2019 11:32:20 +0300 Subject: [PATCH 2/2] vm: implement UNPACK, REVERSE, REMOVE opcodes Also expand makeStackItem() to accept slices of int for testing convenience. Fixes #195. --- pkg/vm/stack_item.go | 6 +++ pkg/vm/vm.go | 25 ++++++++- pkg/vm/vm_test.go | 125 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 154 insertions(+), 2 deletions(-) diff --git a/pkg/vm/stack_item.go b/pkg/vm/stack_item.go index cec438d6b..18ed92f19 100644 --- a/pkg/vm/stack_item.go +++ b/pkg/vm/stack_item.go @@ -37,6 +37,12 @@ func makeStackItem(v interface{}) StackItem { } case StackItem: return val + case []int: + a := []StackItem{} + for _, i := range val { + a = append(a, makeStackItem(i)) + } + return makeStackItem(a) default: panic( fmt.Sprintf( diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 2e19ba363..e8d33afca 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -607,6 +607,14 @@ func (v *VM) execute(ctx *Context, op Instruction) { v.estack.PushVal(items) + case UNPACK: + a := v.estack.Pop().Array() + l := len(a) + for i := l - 1; i >= 0; i-- { + v.estack.PushVal(a[i]) + } + v.estack.PushVal(l) + case PICKITEM: var ( key = v.estack.Pop() @@ -647,6 +655,20 @@ func (v *VM) execute(ctx *Context, op Instruction) { panic(fmt.Sprintf("SETITEM: invalid item type %s", t)) } + case REVERSE: + a := v.estack.Peek(0).Array() + if len(a) > 1 { + for i, j := 0, len(a)-1; i <= j; i, j = i+1, j-1 { + a[i], a[j] = a[j], a[i] + } + } + case REMOVE: + key := int(v.estack.Pop().BigInt().Int64()) + elem := v.estack.Peek(0) + a := elem.Array() + a = append(a[:key], a[key+1:]...) + elem.value = makeStackItem(a) + case ARRAYSIZE: elem := v.estack.Pop() // Cause there is no native (byte) item type here, hence we need to check @@ -729,8 +751,7 @@ func (v *VM) execute(ctx *Context, op Instruction) { v.state = haltState } - case CHECKSIG, CHECKMULTISIG, - UNPACK, REVERSE, REMOVE: + case CHECKSIG, CHECKMULTISIG: panic("unimplemented") // Cryptographic operations. diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index ebdb50cab..61de7a16d 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -673,6 +673,131 @@ func TestPACKGood(t *testing.T) { assert.Equal(t, int64(1), vm.estack.Peek(1).BigInt().Int64()) } +func TestUNPACKBadNotArray(t *testing.T) { + prog := makeProgram(UNPACK) + vm := load(prog) + vm.estack.PushVal(1) + vm.Run() + assert.Equal(t, true, vm.state.HasFlag(faultState)) +} + +func TestUNPACKGood(t *testing.T) { + prog := makeProgram(UNPACK) + elements := []int{55, 34, 42} + vm := load(prog) + // canary + vm.estack.PushVal(1) + vm.estack.PushVal(elements) + vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) + assert.Equal(t, 5, vm.estack.Len()) + assert.Equal(t, int64(len(elements)), vm.estack.Peek(0).BigInt().Int64()) + for k, v := range elements { + assert.Equal(t, int64(v), vm.estack.Peek(k + 1).BigInt().Int64()) + } + assert.Equal(t, int64(1), vm.estack.Peek(len(elements) + 1).BigInt().Int64()) +} + +func TestREVERSEBadNotArray(t *testing.T) { + prog := makeProgram(REVERSE) + vm := load(prog) + vm.estack.PushVal(1) + vm.Run() + assert.Equal(t, true, vm.state.HasFlag(faultState)) +} + +func TestREVERSEGoodOneElem(t *testing.T) { + prog := makeProgram(REVERSE) + elements := []int{22} + vm := load(prog) + vm.estack.PushVal(1) + vm.estack.PushVal(elements) + vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) + assert.Equal(t, 2, vm.estack.Len()) + a := vm.estack.Peek(0).Array() + assert.Equal(t, len(elements), len(a)) + e := a[0].Value().(*big.Int) + assert.Equal(t, int64(elements[0]), e.Int64()) +} + +func TestREVERSEGood(t *testing.T) { + eodd := []int{22, 34, 42, 55, 81} + even := []int{22, 34, 42, 55, 81, 99} + eall := [][]int{eodd, even} + + for _, elements := range eall { + prog := makeProgram(REVERSE) + vm := load(prog) + vm.estack.PushVal(1) + vm.estack.PushVal(elements) + vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) + assert.Equal(t, 2, vm.estack.Len()) + a := vm.estack.Peek(0).Array() + assert.Equal(t, len(elements), len(a)) + for k, v := range elements { + e := a[len(a) - 1 - k].Value().(*big.Int) + assert.Equal(t, int64(v), e.Int64()) + } + assert.Equal(t, int64(1), vm.estack.Peek(1).BigInt().Int64()) + } +} + +func TestREMOVEBadNoArgs(t *testing.T) { + prog := makeProgram(REMOVE) + vm := load(prog) + vm.Run() + assert.Equal(t, true, vm.state.HasFlag(faultState)) +} + +func TestREMOVEBadOneArg(t *testing.T) { + prog := makeProgram(REMOVE) + vm := load(prog) + vm.estack.PushVal(1) + vm.Run() + assert.Equal(t, true, vm.state.HasFlag(faultState)) +} + +func TestREMOVEBadNotArray(t *testing.T) { + prog := makeProgram(REMOVE) + vm := load(prog) + vm.estack.PushVal(1) + vm.estack.PushVal(1) + vm.Run() + assert.Equal(t, true, vm.state.HasFlag(faultState)) +} + +func TestREMOVEBadIndex(t *testing.T) { + prog := makeProgram(REMOVE) + elements := []int{22, 34, 42, 55, 81} + vm := load(prog) + vm.estack.PushVal(elements) + vm.estack.PushVal(10) + vm.Run() + assert.Equal(t, true, vm.state.HasFlag(faultState)) +} + +func TestREMOVEGood(t *testing.T) { + prog := makeProgram(REMOVE) + elements := []int{22, 34, 42, 55, 81} + reselements := []int{22, 34, 55, 81} + vm := load(prog) + vm.estack.PushVal(1) + vm.estack.PushVal(elements) + vm.estack.PushVal(2) + vm.Run() + assert.Equal(t, false, vm.state.HasFlag(faultState)) + assert.Equal(t, 2, vm.estack.Len()) + a := vm.estack.Peek(0).Array() + assert.Equal(t, len(reselements), len(a)) + for k, v := range reselements { + e := a[k].Value().(*big.Int) + assert.Equal(t, int64(v), e.Int64()) + } + assert.Equal(t, int64(1), vm.estack.Peek(1).BigInt().Int64()) +} + func makeProgram(opcodes ...Instruction) []byte { prog := make([]byte, len(opcodes)+1) // RET for i := 0; i < len(opcodes); i++ {