diff --git a/pkg/vm/stack/stack.go b/pkg/vm/stack/stack.go index 996027138..2a1b01693 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 b082c7380..7450c294a 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.XDROP: XDROP, stack.FROMALTSTACK: FROMALTSTACK, stack.TOALTSTACK: TOALTSTACK, diff --git a/pkg/vm/vm_ops_stackmani.go b/pkg/vm/vm_ops_stackmani.go index ce3af78d6..cc32e2812 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" ) @@ -18,6 +20,35 @@ func PushNBytes(op stack.Instruction, ctx *stack.Context, istack *stack.Invocati 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 +} + // DUPFROMALTSTACK duplicates the item on top of alternative stack and // puts it on top of evaluation stack. // Returns an error if the alt stack is empty. @@ -33,6 +64,29 @@ func DUPFROMALTSTACK(op stack.Instruction, ctx *stack.Context, istack *stack.Inv 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 +} + // TOALTSTACK pops an item off of the evaluation stack and // pushes it on top of the alternative stack. // Returns an error if the alternative stack is empty. @@ -48,6 +102,20 @@ func TOALTSTACK(op stack.Instruction, ctx *stack.Context, istack *stack.Invocati 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 +} + // FROMALTSTACK pops an item off of the alternative stack and // pushes it on top of the evaluation stack. // Returns an error if the evaluation stack is empty. @@ -63,6 +131,19 @@ func FROMALTSTACK(op stack.Instruction, ctx *stack.Context, istack *stack.Invoca 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 +} + // XDROP pops an integer n off of the stack and // removes the n-item from the stack starting from // the top of the stack. diff --git a/pkg/vm/vm_ops_stackmani_test.go b/pkg/vm/vm_ops_stackmani_test.go index 6e257ad99..ae6eb685f 100644 --- a/pkg/vm/vm_ops_stackmani_test.go +++ b/pkg/vm/vm_ops_stackmani_test.go @@ -8,6 +8,133 @@ import ( "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 TestDupFromAltStackOp(t *testing.T) { v := VM{}