Merge pull request #271 from dauTT/dauTT/vm-NOP-JMP-JMPIF-JMPIFNOT-opcodes
VM: Implement NOP, JMP, JMPIF, JMPIFNOT opcode, closes #270. Merging as per #283 discussion.
This commit is contained in:
commit
6ffb4b6a5e
4 changed files with 255 additions and 0 deletions
|
@ -121,6 +121,11 @@ func (c *Context) ReadUint16() uint16 {
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReadInt16 reads a int16 from the script
|
||||||
|
func (c *Context) ReadInt16() int16 {
|
||||||
|
return int16(c.ReadUint16())
|
||||||
|
}
|
||||||
|
|
||||||
// ReadByte reads one byte from the script
|
// ReadByte reads one byte from the script
|
||||||
func (c *Context) ReadByte() (byte, error) {
|
func (c *Context) ReadByte() (byte, error) {
|
||||||
byt, err := c.ReadBytes(1)
|
byt, err := c.ReadBytes(1)
|
||||||
|
@ -152,6 +157,16 @@ func (c *Context) readVarBytes() ([]byte, error) {
|
||||||
return c.ReadBytes(int(n))
|
return c.ReadBytes(int(n))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetIP sets the instruction pointer ip to a given integer.
|
||||||
|
// Returns an error if ip is less than -1 or greater than LenInstr.
|
||||||
|
func (c *Context) SetIP(ip int) error {
|
||||||
|
if ok := ip < -1 || ip > c.LenInstr(); ok {
|
||||||
|
return errors.New("invalid instruction pointer")
|
||||||
|
}
|
||||||
|
c.ip = ip
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Hash overrides the default abstract hash method.
|
// Hash overrides the default abstract hash method.
|
||||||
func (c *Context) Hash() (string, error) {
|
func (c *Context) Hash() (string, error) {
|
||||||
data := c.String() + fmt.Sprintf(" %v-%v-%v-%v-%v", c.ip, c.prog, c.breakPoints, c.Estack, c.Astack)
|
data := c.String() + fmt.Sprintf(" %v-%v-%v-%v-%v", c.ip, c.prog, c.breakPoints, c.Estack, c.Astack)
|
||||||
|
|
|
@ -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)
|
type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error)
|
||||||
|
|
||||||
var opFunc = map[stack.Instruction]stackInfo{
|
var opFunc = map[stack.Instruction]stackInfo{
|
||||||
|
stack.JMPIFNOT: JMPIFNOT,
|
||||||
|
stack.JMPIF: JMPIF,
|
||||||
|
stack.JMP: JMP,
|
||||||
|
stack.NOP: NOP,
|
||||||
stack.HASH256: HASH256,
|
stack.HASH256: HASH256,
|
||||||
stack.HASH160: HASH160,
|
stack.HASH160: HASH160,
|
||||||
stack.SHA256: SHA256,
|
stack.SHA256: SHA256,
|
||||||
|
|
|
@ -25,3 +25,71 @@ func RET(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rst
|
||||||
|
|
||||||
return NONE, nil
|
return NONE, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOP Returns NONE VMState.
|
||||||
|
func NOP(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) {
|
||||||
|
return NONE, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// JMP moves the instruction pointer to an offset which is
|
||||||
|
// calculated base on the instructionPointerOffset method.
|
||||||
|
// Returns and error if the offset is out of range.
|
||||||
|
func JMP(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) {
|
||||||
|
offset := instructionPointerOffset(ctx)
|
||||||
|
if err := ctx.SetIP(offset); err != nil {
|
||||||
|
return FAULT, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return NONE, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// JMPIF pops a boolean off of the stack and,
|
||||||
|
// if the the boolean's value is true, it
|
||||||
|
// moves the instruction pointer to an offset which is
|
||||||
|
// calculated base on the instructionPointerOffset method.
|
||||||
|
// Returns and error if the offset is out of range or
|
||||||
|
// the popped item is not a boolean.
|
||||||
|
func JMPIF(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) {
|
||||||
|
b, err := ctx.Estack.PopBoolean()
|
||||||
|
if err != nil {
|
||||||
|
return FAULT, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.Value() {
|
||||||
|
offset := instructionPointerOffset(ctx)
|
||||||
|
if err := ctx.SetIP(offset); err != nil {
|
||||||
|
return FAULT, err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return NONE, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// JMPIFNOT pops a boolean off of the stack and,
|
||||||
|
// if the the boolean's value is false, it
|
||||||
|
// moves the instruction pointer to an offset which is
|
||||||
|
// calculated base on the instructionPointerOffset method.
|
||||||
|
// Returns and error if the offset is out of range or
|
||||||
|
// the popped item is not a boolean.
|
||||||
|
func JMPIFNOT(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) {
|
||||||
|
b, err := ctx.Estack.PopBoolean()
|
||||||
|
if err != nil {
|
||||||
|
return FAULT, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !b.Value() {
|
||||||
|
offset := instructionPointerOffset(ctx)
|
||||||
|
if err := ctx.SetIP(offset); err != nil {
|
||||||
|
return FAULT, err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return NONE, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func instructionPointerOffset(ctx *stack.Context) int {
|
||||||
|
return ctx.IP() + int(ctx.ReadInt16()) - 3
|
||||||
|
}
|
||||||
|
|
168
pkg/vm/vm_ops_flow_test.go
Normal file
168
pkg/vm/vm_ops_flow_test.go
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
package vm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/vm/stack"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNopOp(t *testing.T) {
|
||||||
|
|
||||||
|
v := VM{}
|
||||||
|
|
||||||
|
a, err := stack.NewInt(big.NewInt(10))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
ctx := stack.NewContext([]byte{})
|
||||||
|
ctx.Estack.Push(a)
|
||||||
|
|
||||||
|
v.executeOp(stack.NOP, ctx)
|
||||||
|
|
||||||
|
// Stack should have one item
|
||||||
|
assert.Equal(t, 1, ctx.Estack.Len())
|
||||||
|
|
||||||
|
item, err := ctx.Estack.PopInt()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(10), item.Value().Int64())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJmpOp(t *testing.T) {
|
||||||
|
|
||||||
|
v := VM{}
|
||||||
|
|
||||||
|
a, err := stack.NewInt(big.NewInt(10))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
ctx := stack.NewContext([]byte{5, 0, 2, 3, 4})
|
||||||
|
ctx.Estack.Push(a)
|
||||||
|
|
||||||
|
// ctx.ip = -1
|
||||||
|
// ctx.IP() = ctx.ip + 1
|
||||||
|
assert.Equal(t, 0, ctx.IP())
|
||||||
|
|
||||||
|
// ctx.ip will be set to offset.
|
||||||
|
// offset = ctx.IP() + int(ctx.ReadInt16()) - 3
|
||||||
|
// = 0 + 5 -3 = 2
|
||||||
|
v.executeOp(stack.JMP, ctx)
|
||||||
|
|
||||||
|
// Stack should have one item
|
||||||
|
assert.Equal(t, 1, ctx.Estack.Len())
|
||||||
|
|
||||||
|
// ctx.IP() = ctx.ip + 1
|
||||||
|
assert.Equal(t, 3, ctx.IP())
|
||||||
|
}
|
||||||
|
|
||||||
|
// test JMPIF instruction with true boolean
|
||||||
|
// on top of the stack
|
||||||
|
func TestJmpIfOp1(t *testing.T) {
|
||||||
|
|
||||||
|
v := VM{}
|
||||||
|
|
||||||
|
a := stack.NewBoolean(true)
|
||||||
|
|
||||||
|
ctx := stack.NewContext([]byte{5, 0, 2, 3, 4})
|
||||||
|
ctx.Estack.Push(a)
|
||||||
|
|
||||||
|
// ctx.ip = -1
|
||||||
|
// ctx.IP() = ctx.ip + 1
|
||||||
|
assert.Equal(t, 0, ctx.IP())
|
||||||
|
|
||||||
|
// ctx.ip will be set to offset
|
||||||
|
// because the there is a true boolean
|
||||||
|
// on top of the stack.
|
||||||
|
// offset = ctx.IP() + int(ctx.ReadInt16()) - 3
|
||||||
|
// = 0 + 5 -3 = 2
|
||||||
|
v.executeOp(stack.JMPIF, ctx)
|
||||||
|
|
||||||
|
// Stack should have 0 item
|
||||||
|
assert.Equal(t, 0, ctx.Estack.Len())
|
||||||
|
|
||||||
|
// ctx.IP() = ctx.ip + 1
|
||||||
|
assert.Equal(t, 3, ctx.IP())
|
||||||
|
}
|
||||||
|
|
||||||
|
// test JMPIF instruction with false boolean
|
||||||
|
// on top of the stack
|
||||||
|
func TestJmpIfOp2(t *testing.T) {
|
||||||
|
|
||||||
|
v := VM{}
|
||||||
|
|
||||||
|
a := stack.NewBoolean(false)
|
||||||
|
|
||||||
|
ctx := stack.NewContext([]byte{5, 0, 2, 3, 4})
|
||||||
|
ctx.Estack.Push(a)
|
||||||
|
|
||||||
|
// ctx.ip = -1
|
||||||
|
// ctx.IP() = ctx.ip + 1
|
||||||
|
assert.Equal(t, 0, ctx.IP())
|
||||||
|
|
||||||
|
// nothing will happen because
|
||||||
|
// the value of the boolean on top of the stack
|
||||||
|
// is false
|
||||||
|
v.executeOp(stack.JMPIF, ctx)
|
||||||
|
|
||||||
|
// Stack should have 0 item
|
||||||
|
assert.Equal(t, 0, ctx.Estack.Len())
|
||||||
|
|
||||||
|
// ctx.IP() = ctx.ip + 1
|
||||||
|
assert.Equal(t, 0, ctx.IP())
|
||||||
|
}
|
||||||
|
|
||||||
|
// test JMPIFNOT instruction with true boolean
|
||||||
|
// on top of the stack
|
||||||
|
func TestJmpIfNotOp1(t *testing.T) {
|
||||||
|
|
||||||
|
v := VM{}
|
||||||
|
|
||||||
|
a := stack.NewBoolean(true)
|
||||||
|
|
||||||
|
ctx := stack.NewContext([]byte{5, 0, 2, 3, 4})
|
||||||
|
ctx.Estack.Push(a)
|
||||||
|
|
||||||
|
// ctx.ip = -1
|
||||||
|
// ctx.IP() = ctx.ip + 1
|
||||||
|
assert.Equal(t, 0, ctx.IP())
|
||||||
|
|
||||||
|
// nothing will happen because
|
||||||
|
// the value of the boolean on top of the stack
|
||||||
|
// is true
|
||||||
|
v.executeOp(stack.JMPIFNOT, ctx)
|
||||||
|
|
||||||
|
// Stack should have 0 item
|
||||||
|
assert.Equal(t, 0, ctx.Estack.Len())
|
||||||
|
|
||||||
|
// ctx.IP() = ctx.ip + 1
|
||||||
|
assert.Equal(t, 0, ctx.IP())
|
||||||
|
}
|
||||||
|
|
||||||
|
// test JMPIFNOT instruction with false boolean
|
||||||
|
// on top of the stack
|
||||||
|
func TestJmpIfNotOp2(t *testing.T) {
|
||||||
|
|
||||||
|
v := VM{}
|
||||||
|
|
||||||
|
a := stack.NewBoolean(false)
|
||||||
|
|
||||||
|
ctx := stack.NewContext([]byte{5, 0, 2, 3, 4})
|
||||||
|
ctx.Estack.Push(a)
|
||||||
|
|
||||||
|
// ctx.ip = -1
|
||||||
|
// ctx.IP() = ctx.ip + 1
|
||||||
|
assert.Equal(t, 0, ctx.IP())
|
||||||
|
|
||||||
|
// ctx.ip will be set to offset
|
||||||
|
// because the there is a false boolean
|
||||||
|
// on top of the stack.
|
||||||
|
// offset = ctx.IP() + int(ctx.ReadInt16()) - 3
|
||||||
|
// = 0 + 5 -3 = 2
|
||||||
|
v.executeOp(stack.JMPIFNOT, ctx)
|
||||||
|
|
||||||
|
// Stack should have one item
|
||||||
|
assert.Equal(t, 0, ctx.Estack.Len())
|
||||||
|
|
||||||
|
// ctx.IP() = ctx.ip + 1
|
||||||
|
assert.Equal(t, 3, ctx.IP())
|
||||||
|
}
|
Loading…
Reference in a new issue