diff --git a/pkg/vm/stack/stack.go b/pkg/vm/stack/stack.go index c832a9f79..996027138 100644 --- a/pkg/vm/stack/stack.go +++ b/pkg/vm/stack/stack.go @@ -158,3 +158,19 @@ func (ras *RandomAccess) PopBoolean() (*Boolean, error) { } return item.Boolean() } + +// 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 +} diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 0a9c6f173..57accf126 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -5,34 +5,38 @@ 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.NUMEQUAL: NumEqual, - stack.NUMNOTEQUAL: NumNotEqual, - stack.BOOLAND: BoolAnd, - stack.BOOLOR: BoolOr, - stack.LT: Lt, - stack.LTE: Lte, - stack.GT: Gt, - stack.GTE: Gte, - stack.SHR: Shr, - stack.SHL: Shl, - stack.INC: Inc, - stack.DEC: Dec, - stack.DIV: Div, - stack.MOD: Mod, - stack.NZ: Nz, - stack.MUL: Mul, - stack.ABS: Abs, - stack.NOT: Not, - stack.SIGN: Sign, - stack.NEGATE: Negate, - stack.ADD: Add, - stack.SUB: Sub, - stack.PUSHBYTES1: PushNBytes, - stack.PUSHBYTES75: PushNBytes, - stack.RET: RET, - stack.EQUAL: EQUAL, - stack.THROWIFNOT: THROWIFNOT, - stack.THROW: THROW, + stack.XDROP: XDROP, + stack.FROMALTSTACK: FROMALTSTACK, + stack.TOALTSTACK: TOALTSTACK, + stack.DUPFROMALTSTACK: DUPFROMALTSTACK, + stack.NUMEQUAL: NumEqual, + stack.NUMNOTEQUAL: NumNotEqual, + stack.BOOLAND: BoolAnd, + stack.BOOLOR: BoolOr, + stack.LT: Lt, + stack.LTE: Lte, + stack.GT: Gt, + stack.GTE: Gte, + stack.SHR: Shr, + stack.SHL: Shl, + stack.INC: Inc, + stack.DEC: Dec, + stack.DIV: Div, + stack.MOD: Mod, + stack.NZ: Nz, + stack.MUL: Mul, + stack.ABS: Abs, + stack.NOT: Not, + stack.SIGN: Sign, + stack.NEGATE: Negate, + stack.ADD: Add, + stack.SUB: Sub, + stack.PUSHBYTES1: PushNBytes, + stack.PUSHBYTES75: PushNBytes, + stack.RET: RET, + stack.EQUAL: EQUAL, + stack.THROWIFNOT: THROWIFNOT, + stack.THROW: THROW, } func init() { diff --git a/pkg/vm/vm_ops_stackmani.go b/pkg/vm/vm_ops_stackmani.go index f5e2ddc24..ce3af78d6 100644 --- a/pkg/vm/vm_ops_stackmani.go +++ b/pkg/vm/vm_ops_stackmani.go @@ -17,3 +17,66 @@ func PushNBytes(op stack.Instruction, ctx *stack.Context, istack *stack.Invocati ctx.Estack.Push(ba) 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. +func DUPFROMALTSTACK(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + item, err := ctx.Astack.Peek(0) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(item) + + 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. +func TOALTSTACK(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + item, err := ctx.Estack.Pop() + if err != nil { + return FAULT, err + } + + ctx.Astack.Push(item) + + 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. +func FROMALTSTACK(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + item, err := ctx.Astack.Pop() + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(item) + + 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. +func XDROP(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 + } + + ctx.Estack.Remove(uint16(n.Value().Uint64())) + 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..6e257ad99 --- /dev/null +++ b/pkg/vm/vm_ops_stackmani_test.go @@ -0,0 +1,131 @@ +package vm + +import ( + "math/big" + "testing" + + "github.com/CityOfZion/neo-go/pkg/vm/stack" + "github.com/stretchr/testify/assert" +) + +func TestDupFromAltStackOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(10)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(2)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + ctx.Astack.Push(b) + + v.executeOp(stack.DUPFROMALTSTACK, ctx) + + assert.Equal(t, 1, ctx.Astack.Len()) + assert.Equal(t, 2, ctx.Estack.Len()) + + itemE, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemA, err := ctx.Astack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(2), itemE.Value().Int64()) + assert.Equal(t, int64(2), itemA.Value().Int64()) +} + +func TestToAltStackOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(10)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(2)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + ctx.Astack.Push(b) + + v.executeOp(stack.TOALTSTACK, ctx) + + assert.Equal(t, 2, ctx.Astack.Len()) + assert.Equal(t, 0, ctx.Estack.Len()) + + item, err := ctx.Astack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(10), item.Value().Int64()) +} + +func TestFromAltStackOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(10)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(2)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + ctx.Astack.Push(b) + + v.executeOp(stack.FROMALTSTACK, ctx) + + assert.Equal(t, 0, ctx.Astack.Len()) + assert.Equal(t, 2, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(2), item.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) + + 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) + ctx.Estack.Push(b) + ctx.Estack.Push(c) + ctx.Estack.Push(d) + + // pop n (= d = 2) from the stack. + // we will remove the n-item which + // is located at position + // len(stack)-n-1 = 3-2-1 = 0. + // Therefore a is removed from the stack. + // Only b, c remain on the stack. + v.executeOp(stack.XDROP, ctx) + + assert.Equal(t, 2, ctx.Estack.Len()) + + itemC, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemB, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(6), itemB.Value().Int64()) + assert.Equal(t, int64(9), itemC.Value().Int64()) + +}