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/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 8109e8067..61de7a16d 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -633,6 +633,171 @@ 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 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++ {