Implemented following control flow opcodes:

1) NOP
2) JMP
3) JMPIF
4) JMPIFNOT
This commit is contained in:
DauTT 2019-04-09 01:07:15 +02:00
parent 045db09af2
commit 4dc11ee48f
4 changed files with 255 additions and 0 deletions

View file

@ -120,6 +120,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)
@ -150,3 +155,13 @@ 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
}

View file

@ -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.NUMEQUAL: NumEqual, stack.NUMEQUAL: NumEqual,
stack.NUMNOTEQUAL: NumNotEqual, stack.NUMNOTEQUAL: NumNotEqual,
stack.BOOLAND: BoolAnd, stack.BOOLAND: BoolAnd,

View file

@ -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
View 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())
}