From aefe572df3c64abeace35bd9d07fa6477a12ec19 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 6 Sep 2019 11:32:20 +0300 Subject: [PATCH] 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++ {