diff --git a/pkg/vm/stack/stack.go b/pkg/vm/stack/stack.go index c832a9f79..d42c7b123 100644 --- a/pkg/vm/stack/stack.go +++ b/pkg/vm/stack/stack.go @@ -127,6 +127,21 @@ func (ras *RandomAccess) CopyTo(stack *RandomAccess) error { return nil } +// Set sets the n-item from the stack +// starting from the top of the stack with the new item. +// the n-item to replace is located at the position "len(stack)-index-1". +func (ras *RandomAccess) Set(index uint16, item Item) error { + stackSize := uint16(len(ras.vals)) + if ok := index >= stackSize; ok { + return errors.New("index out of range") + } + + n := stackSize - index - 1 + ras.vals[n] = item + + return 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..e6d61edc9 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.DROP: DROP, + stack.DEPTH: DEPTH, + stack.XTUCK: XTUCK, + stack.XSWAP: XSWAP, 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..81ec1d91c 100644 --- a/pkg/vm/vm_ops_stackmani.go +++ b/pkg/vm/vm_ops_stackmani.go @@ -1,6 +1,8 @@ package vm import ( + "math/big" + "github.com/CityOfZion/neo-go/pkg/vm/stack" ) @@ -17,3 +19,84 @@ func PushNBytes(op stack.Instruction, ctx *stack.Context, istack *stack.Invocati ctx.Estack.Push(ba) return NONE, nil } + +// XSWAP pops an integer n off of the stack and +// swaps the n-item from the stack starting from +// the top of the stack with the top stack item. +func XSWAP(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.Peek(uint16(n.Value().Int64())) + if err != nil { + return FAULT, err + } + + item, err := ctx.Estack.Peek(0) + if err != nil { + return FAULT, err + } + + if err := ctx.Estack.Set(uint16(n.Value().Int64()), item); err != nil { + return FAULT, err + } + + if err := ctx.Estack.Set(0, nItem); err != nil { + return FAULT, err + } + + return NONE, nil +} + +// XTUCK pops an integer n off of the stack and +// inserts the top stack item to the position len(stack)-n in the evaluation stack. +func XTUCK(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + n, err := ctx.Estack.PopInt() + if err != nil || n.Value().Int64() < 0 { + return FAULT, err + } + + item, err := ctx.Estack.Peek(0) + if err != nil { + return FAULT, err + } + + ras, err := ctx.Estack.Insert(uint16(n.Value().Int64()), item) + if err != nil { + return FAULT, err + } + + ctx.Estack = *ras + + return NONE, nil +} + +// DEPTH puts the number of stack items onto the stack. +func DEPTH(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + l := ctx.Estack.Len() + length, err := stack.NewInt(big.NewInt(int64(l))) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(length) + + return NONE, nil +} + +// DROP removes the the top stack item. +// Returns error if the operation Pop cannot +// be performed. +func DROP(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + _, err := ctx.Estack.Pop() + if err != nil { + return FAULT, err + } + + 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..eb771df36 --- /dev/null +++ b/pkg/vm/vm_ops_stackmani_test.go @@ -0,0 +1,162 @@ +package vm + +import ( + "math/big" + "testing" + + "github.com/CityOfZion/neo-go/pkg/vm/stack" + "github.com/stretchr/testify/assert" +) + +func TestXswapOp(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. + // we will swap the n-item which + // is located in position len(stack)-n-1 (= 3-2-1= 0) + // with the top stack item. + // The final stack will be [c,b,a] + v.executeOp(stack.XSWAP, ctx) + + // Stack should have three items + assert.Equal(t, 3, ctx.Estack.Len()) + + itemA, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemB, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemC, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(3), itemA.Value().Int64()) + assert.Equal(t, int64(6), itemB.Value().Int64()) + assert.Equal(t, int64(9), itemC.Value().Int64()) + +} + +func TestXTuckOp(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 insert the top stack item c + // to the position len(stack)-n (= 3-2 = 1) + // of the stack.The final stack will be [a,c,b,c] + v.executeOp(stack.XTUCK, ctx) + + // Stack should have four items + assert.Equal(t, 4, ctx.Estack.Len()) + + // c + item0, err := ctx.Estack.PopInt() + assert.Nil(t, err) + // b + item1, err := ctx.Estack.PopInt() + assert.Nil(t, err) + // c + item2, err := ctx.Estack.PopInt() + assert.Nil(t, err) + // a + item3, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(9), item0.Value().Int64()) + assert.Equal(t, int64(6), item1.Value().Int64()) + assert.Equal(t, int64(9), item2.Value().Int64()) + assert.Equal(t, int64(3), item3.Value().Int64()) + +} + +func TestXDepthOp(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) + + // push integer whose value is len(stack) (2) + // on top of the stack + v.executeOp(stack.DEPTH, ctx) + + // Stack should have three items + assert.Equal(t, 3, ctx.Estack.Len()) + + // len(stack) + item0, err := ctx.Estack.PopInt() + assert.Nil(t, err) + // b + item1, err := ctx.Estack.PopInt() + assert.Nil(t, err) + // a + item2, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(2), item0.Value().Int64()) + assert.Equal(t, int64(6), item1.Value().Int64()) + assert.Equal(t, int64(3), item2.Value().Int64()) +} + +func TestXDropOp(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) + + // Remove the top stack item from the stack. + // The remaining stack is [a] + v.executeOp(stack.DROP, ctx) + + // Stack should have 2 items + assert.Equal(t, 1, ctx.Estack.Len()) + + itemA, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(3), itemA.Value().Int64()) +}