diff --git a/pkg/vm/stack/stack.go b/pkg/vm/stack/stack.go index c832a9f79..91a888a7d 100644 --- a/pkg/vm/stack/stack.go +++ b/pkg/vm/stack/stack.go @@ -127,6 +127,37 @@ 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 +} + +// 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..f02d09be8 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.PICK: PICK, + stack.OVER: OVER, + stack.NIP: NIP, + stack.DUP: DUP, 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..5eecad252 100644 --- a/pkg/vm/vm_ops_stackmani.go +++ b/pkg/vm/vm_ops_stackmani.go @@ -17,3 +17,67 @@ func PushNBytes(op stack.Instruction, ctx *stack.Context, istack *stack.Invocati ctx.Estack.Push(ba) return NONE, nil } + +// DUP duplicates the top stack item. +// Returns an error if stack is empty. +func DUP(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 + } + + ctx.Estack.Push(item) + + return NONE, nil +} + +// NIP removes the second top stack item. +// Returns error if the stack item contains +// only one element. +func NIP(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + _, err := ctx.Estack.Remove(1) + if err != nil { + return FAULT, err + } + + return NONE, nil +} + +// OVER copies the second-to-top stack item onto the top. +// Returns an error if the stack item contains +// only one element. +func OVER(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + item, err := ctx.Estack.Peek(1) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(item) + + return NONE, nil +} + +// PICK pops an integer n off of the stack and +// copies 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 PICK(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 + } + + ctx.Estack.Push(nItem) + + 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..45e84ddb7 --- /dev/null +++ b/pkg/vm/vm_ops_stackmani_test.go @@ -0,0 +1,197 @@ +package vm + +import ( + "math/big" + "testing" + + "github.com/CityOfZion/neo-go/pkg/vm/stack" + "github.com/stretchr/testify/assert" +) + +func TestDupOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + + v.executeOp(stack.DUP, ctx) + + // Stack should have two items + assert.Equal(t, 2, ctx.Estack.Len()) + + item1, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + item2, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(3), item1.Value().Int64()) + assert.Equal(t, int64(3), item2.Value().Int64()) + +} + +func TestNipOp(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) + + v.executeOp(stack.NIP, ctx) + + // Stack should have two items + assert.Equal(t, 2, ctx.Estack.Len()) + + itemC, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemA, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(3), itemA.Value().Int64()) + assert.Equal(t, int64(9), itemC.Value().Int64()) + +} + +func TestOverOp(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) + + // OVER copies the second top stack item a + // onto the top stack item b. + // the new stack will be [a,b,a]. + v.executeOp(stack.OVER, 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) + + itemA2, 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(3), itemA2.Value().Int64()) + +} + +func TestPickOp(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 copy the n-item which + // has index len(stack)-n-1 (= 3-2-1= 0) + // onto the top stack item. + // The final stack will be [a,b,c,a] + v.executeOp(stack.PICK, ctx) + + // Stack should have four items + assert.Equal(t, 4, 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) + + itemA2, 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()) + assert.Equal(t, int64(3), itemA2.Value().Int64()) + +} + +/* +func TestXswapOp(t *testing.T) { + + v := VM{} + + sert.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 wa, 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)) + asill 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()) + +} +*/