From d36d8b456c1b256faa34534dad4d0de9c15caaec Mon Sep 17 00:00:00 2001 From: DauTT Date: Fri, 12 Apr 2019 22:27:52 +0200 Subject: [PATCH] Implemented following opcodes: 1) ROLL 2) ROT 3) SWAP 4) TUCK --- pkg/vm/stack/stack.go | 16 ++++ pkg/vm/vm_ops.go | 4 + pkg/vm/vm_ops_stackmani.go | 74 ++++++++++++++ pkg/vm/vm_ops_stackmani_test.go | 164 ++++++++++++++++++++++++++++++++ 4 files changed, 258 insertions(+) create mode 100644 pkg/vm/vm_ops_stackmani_test.go diff --git a/pkg/vm/stack/stack.go b/pkg/vm/stack/stack.go index c832a9f79..5e837258c 100644 --- a/pkg/vm/stack/stack.go +++ b/pkg/vm/stack/stack.go @@ -127,6 +127,22 @@ func (ras *RandomAccess) CopyTo(stack *RandomAccess) error { return nil } +// Remove removes the n-item from the stack +// starting from the top of the stack. In other words +// the n-item to remove is located at the index "len(stack)-n-1" +func (ras *RandomAccess) Remove(n uint16) (Item, error) { + if int(n) >= len(ras.vals) { + return nil, errors.New("index out of range") + } + + index := uint16(len(ras.vals)) - n - 1 + item := ras.vals[index] + + ras.vals = append(ras.vals[:index], ras.vals[index+1:]...) + + return item, nil +} + // Convenience Functions // PopInt will remove the last stack item that was added diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 0a9c6f173..83aafda7d 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -5,6 +5,10 @@ import "github.com/CityOfZion/neo-go/pkg/vm/stack" type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) var opFunc = map[stack.Instruction]stackInfo{ + stack.TUCK: TUCK, + stack.SWAP: SWAP, + stack.ROT: ROT, + stack.ROLL: ROLL, stack.NUMEQUAL: NumEqual, stack.NUMNOTEQUAL: NumNotEqual, stack.BOOLAND: BoolAnd, diff --git a/pkg/vm/vm_ops_stackmani.go b/pkg/vm/vm_ops_stackmani.go index f5e2ddc24..eb8fcc8d4 100644 --- a/pkg/vm/vm_ops_stackmani.go +++ b/pkg/vm/vm_ops_stackmani.go @@ -17,3 +17,77 @@ func PushNBytes(op stack.Instruction, ctx *stack.Context, istack *stack.Invocati ctx.Estack.Push(ba) return NONE, nil } + +// ROLL pops an integer n off of the stack and +// moves the n-item starting from +// the top of the stack onto the top stack item. +// Returns an error if the top stack item is not an +// integer or n-item does not exist. +func ROLL(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + n, err := ctx.Estack.PopInt() + if err != nil { + return FAULT, err + } + + nItem, err := ctx.Estack.Remove(uint16(n.Value().Int64())) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(nItem) + + return NONE, nil +} + +// ROT moves the third top stack item +// onto the top stack item. +// Returns an error if the third top stack item +// does not exist. +func ROT(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + item, err := ctx.Estack.Remove(2) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(item) + + return NONE, nil +} + +// SWAP swaps the second top stack item with +// the top stack item. +// Returns an error if the second top stack item +// does not exist. +func SWAP(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + item, err := ctx.Estack.Remove(1) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(item) + + return NONE, nil +} + +// TUCK copies the top stack item and +// inserts it before the second top stack item. +// Returns an error if the stack is empty or +// len(stack) is less or equal 2. +func TUCK(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + item, err := ctx.Estack.Peek(0) + if err != nil { + return FAULT, err + } + + ras, err := ctx.Estack.Insert(2, item) + if err != nil { + return FAULT, err + } + ctx.Estack = *ras + + return NONE, nil +} diff --git a/pkg/vm/vm_ops_stackmani_test.go b/pkg/vm/vm_ops_stackmani_test.go new file mode 100644 index 000000000..ab80b36d2 --- /dev/null +++ b/pkg/vm/vm_ops_stackmani_test.go @@ -0,0 +1,164 @@ +package vm + +import ( + "math/big" + "testing" + + "github.com/CityOfZion/neo-go/pkg/vm/stack" + "github.com/stretchr/testify/assert" +) + +func TestRollOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(6)) + assert.Nil(t, err) + + c, err := stack.NewInt(big.NewInt(9)) + assert.Nil(t, err) + + d, err := stack.NewInt(big.NewInt(2)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b).Push(c).Push(d) + + // pop n (= d = 2) from the stack + // and move the n-item which + // has index len(stack)-n-1 (= 3-2-1= 0) + // onto the top stack item. + // The final stack will be [b,c,a] + v.executeOp(stack.ROLL, ctx) + + // Stack should have three items + assert.Equal(t, 3, ctx.Estack.Len()) + + itemA, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemC, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemB, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(3), itemA.Value().Int64()) + assert.Equal(t, int64(9), itemC.Value().Int64()) + assert.Equal(t, int64(6), itemB.Value().Int64()) +} + +func TestRotOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(6)) + assert.Nil(t, err) + + c, err := stack.NewInt(big.NewInt(9)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b).Push(c) + + // move the third top stack a item onto + // the top stack item c. + // The final stack will be [b,c,a] + v.executeOp(stack.ROT, ctx) + + // Stack should have three items + assert.Equal(t, 3, ctx.Estack.Len()) + + itemA, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemC, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemB, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(3), itemA.Value().Int64()) + assert.Equal(t, int64(9), itemC.Value().Int64()) + assert.Equal(t, int64(6), itemB.Value().Int64()) +} + +func TestSwapOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(6)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + // Swaps the top two stack items. + // The final stack will be [b,a] + v.executeOp(stack.SWAP, ctx) + + // Stack should have two items + assert.Equal(t, 2, ctx.Estack.Len()) + + itemA, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemB, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(3), itemA.Value().Int64()) + assert.Equal(t, int64(6), itemB.Value().Int64()) + +} + +func TestTuckOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(6)) + assert.Nil(t, err) + + c, err := stack.NewInt(big.NewInt(9)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b).Push(c) + + // copy the top stack item c and + // inserts it before the second top stack item. + // The final stack will be [a,c,b,c] + v.executeOp(stack.TUCK, ctx) + + // Stack should have four items + assert.Equal(t, 4, ctx.Estack.Len()) + + itemC, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemB, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemC2, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemA, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(9), itemC.Value().Int64()) + assert.Equal(t, int64(6), itemB.Value().Int64()) + assert.Equal(t, int64(9), itemC2.Value().Int64()) + assert.Equal(t, int64(3), itemA.Value().Int64()) + +}