VM: Add basic vm (#166)

* VM:Add abstract stack item

* VM: Add stackItems; Array, Boolean, Int and ByteArray

* VM: Add tests for stack item

* VM: first pass at Random Access Stack object

* VM: Add Sub, Mul, Mod LSH, RSH

* VM: moved test helper functions into separate file

* VM: removed helper functions from stack_test.go

* Add conversions for bytearray and Int stack items

* Add instructions file for vm

* - Add guide to stack readme
- Add testReadInt64

* Add Builder

* Refactor Int, Boolean, ByteArray conversion

* Add Context stack Item

* Add Invocation stack - convenience RAS

* rename testhelper to test_helper

* Move opcode file

* - Add `Add` OpCode
- Add Opcode Function map

* - Add test for math `Add` opcode
- basic opcode execution

* Add popTwoIntegers convenience func

* Add `SUB` Opcode

* Export Context Read methods
- Return errors where failable

* - Add `Op` to handleOP func signature
- Add PushNBytes OPcode

* remove error on NewBoolean
- Expose underlying with Getter on Boolean StackItem
- Add Equals method for ByteArray

* Make Next() method on Context failable, refactor peekContext and Peek

* Add ExecuteOp, Step and Run methods on the VM

* Add Equal Opcode

* Add THROWIFNOT Opcode

* Add RET Opcode

* Refactor PushNBytes Opcode

* refactor Add, Sub to return VMSTATE
add popTwoByteArrays helper function

* Add basic tests for vm

* clarify vm states

* Add astack

* [VM]

Pass ResultStack to the opcode handlers

* [VM]

refactor handlers to have rstack as argument

* [Stack]

- Change RemoveCurrentContext for PopCurrentContext
- Add CopTo method to stack

* [VM]

Add Result stack Len check in simple run test

* [VM] fix typo

* [Peer/Stall]

Change seconds to milliseconds in test
This commit is contained in:
decentralisedkev 2019-03-18 21:40:21 +00:00 committed by GitHub
parent de3137197a
commit c1b6738bdb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 1770 additions and 8 deletions

View file

@ -12,8 +12,8 @@ import (
func TestAddRemoveMessage(t *testing.T) {
responseTime := 2 * time.Second
tickerInterval := 1 * time.Second
responseTime := 2 * time.Millisecond
tickerInterval := 1 * time.Millisecond
d := NewDetector(responseTime, tickerInterval)
d.AddMessage(command.GetAddr)
@ -51,15 +51,15 @@ loop:
}
func TestDeadlineWorks(t *testing.T) {
responseTime := 2 * time.Second
tickerInterval := 1 * time.Second
responseTime := 2 * time.Millisecond
tickerInterval := 1 * time.Millisecond
d := NewDetector(responseTime, tickerInterval)
mp := mockPeer{online: true, detector: d, lock: new(sync.RWMutex)}
go mp.loop()
d.AddMessage(command.GetAddr)
time.Sleep(responseTime + 1*time.Second)
time.Sleep(responseTime + 1*time.Millisecond)
k := make(map[command.Type]time.Time)
d.lock.RLock()
@ -70,12 +70,12 @@ func TestDeadlineWorks(t *testing.T) {
mp.lock.RUnlock()
}
func TestDeadlineShouldNotBeEmpty(t *testing.T) {
responseTime := 10 * time.Second
tickerInterval := 1 * time.Second
responseTime := 10 * time.Millisecond
tickerInterval := 1 * time.Millisecond
d := NewDetector(responseTime, tickerInterval)
d.AddMessage(command.GetAddr)
time.Sleep(1 * time.Second)
time.Sleep(1 * time.Millisecond)
k := make(map[command.Type]time.Time)
d.lock.RLock()

94
pkg/vm/stack/Int.go Normal file
View file

@ -0,0 +1,94 @@
package stack
import "math/big"
// Int represents an integer on the stack
type Int struct {
*abstractItem
val *big.Int
}
// NewInt will convert a big integer into
// a StackInteger
func NewInt(val *big.Int) (*Int, error) {
return &Int{
abstractItem: &abstractItem{},
val: val,
}, nil
}
// Equal will check if two integers hold equal value
func (i *Int) Equal(s *Int) bool {
if i.val.Cmp(s.val) != 0 {
return false
}
return true
}
// Add will add two stackIntegers together
func (i *Int) Add(s *Int) (*Int, error) {
return &Int{
val: new(big.Int).Add(i.val, s.val),
}, nil
}
// Sub will subtract two stackIntegers together
func (i *Int) Sub(s *Int) (*Int, error) {
return &Int{
val: new(big.Int).Sub(i.val, s.val),
}, nil
}
// Mul will multiply two stackIntegers together
func (i *Int) Mul(s *Int) (*Int, error) {
return &Int{
val: new(big.Int).Mul(i.val, s.val),
}, nil
}
// Mod will take the mod of two stackIntegers together
func (i *Int) Mod(s *Int) (*Int, error) {
return &Int{
val: new(big.Int).Mod(i.val, s.val),
}, nil
}
// Rsh will shift the integer b to the right by `n` bits
func (i *Int) Rsh(n *Int) (*Int, error) {
return &Int{
val: new(big.Int).Rsh(i.val, uint(n.val.Int64())),
}, nil
}
// Lsh will shift the integer b to the left by `n` bits
func (i *Int) Lsh(n *Int) (*Int, error) {
return &Int{
val: new(big.Int).Lsh(i.val, uint(n.val.Int64())),
}, nil
}
// Integer will overwrite the default implementation
// to allow go to cast this item as an integer.
func (i *Int) Integer() (*Int, error) {
return i, nil
}
// ByteArray override the default ByteArray method
// to convert a Integer into a byte Array
func (i *Int) ByteArray() (*ByteArray, error) {
b := i.val.Bytes()
dest := reverse(b)
return NewByteArray(dest), nil
}
//Boolean override the default Boolean method
// to convert an Integer into a Boolean StackItem
func (i *Int) Boolean() (*Boolean, error) {
boolean := (i.val.Int64() != 0)
return NewBoolean(boolean), nil
}
//Value returns the underlying big.Int
func (i *Int) Value() *big.Int {
return i.val
}

24
pkg/vm/stack/Readme.md Normal file
View file

@ -0,0 +1,24 @@
## VM - Stack
- How do i implement a new StackItem?
Answer: You add it's type to the Item interface, then you implement the default return method on the abstract stack item, this should be the behaviour of the stack item, if it is not the new type. Then you embed the abstract item in the new struct and override the method.
For example, If I wanted to add a new type called `HashMap`
type Item interface{
HashMap()(*HashMap, error)
}
func (a *abstractItem) HashMap() (*HashMap, error) {
return nil, errors.New(This stack item is not a hashmap)
}
type HashMap struct {
*abstractItem
// Variables needed for hashmap
}
func (h *HashMap) HashMap()(*HashMap, error) {
// logic to override default behaviour
}

13
pkg/vm/stack/array.go Normal file
View file

@ -0,0 +1,13 @@
package stack
// Array represents an Array of stackItems on the stack
type Array struct {
*abstractItem
val []Item
}
// Array overrides the default implementation
// by the abstractItem, returning an Array struct
func (a *Array) Array() (*Array, error) {
return a, nil
}

26
pkg/vm/stack/boolean.go Normal file
View file

@ -0,0 +1,26 @@
package stack
// Boolean represents a boolean value on the stack
type Boolean struct {
*abstractItem
val bool
}
//NewBoolean returns a new boolean stack item
func NewBoolean(val bool) *Boolean {
return &Boolean{
&abstractItem{},
val,
}
}
// Boolean overrides the default implementation
// by the abstractItem, returning a Boolean struct
func (b *Boolean) Boolean() (*Boolean, error) {
return b, nil
}
// Value returns the underlying boolean value
func (b *Boolean) Value() bool {
return b.val
}

177
pkg/vm/stack/builder.go Normal file
View file

@ -0,0 +1,177 @@
package stack
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"math/big"
"github.com/CityOfZion/neo-go/pkg/wire/util"
)
// Builder follows the builder pattern and will be used to build scripts
type Builder struct {
w *bytes.Buffer
err error
}
// NewBuilder returns a new builder object
func NewBuilder() *Builder {
return &Builder{
w: &bytes.Buffer{},
err: nil,
}
}
// Bytes returns the byte representation of the built buffer
func (br *Builder) Bytes() []byte {
return br.w.Bytes()
}
// Emit a VM Opcode with data to the given buffer.
func (br *Builder) Emit(op Instruction, b []byte) *Builder {
if br.err != nil {
return br
}
br.err = br.w.WriteByte(byte(op))
_, br.err = br.w.Write(b)
return br
}
// EmitOpcode emits a single VM Opcode the given buffer.
func (br *Builder) EmitOpcode(op Instruction) *Builder {
if br.err != nil {
return br
}
br.err = br.w.WriteByte(byte(op))
return br
}
// EmitBool emits a bool type the given buffer.
func (br *Builder) EmitBool(ok bool) *Builder {
if br.err != nil {
return br
}
op := PUSHT
if !ok {
op = PUSHF
}
return br.EmitOpcode(op)
}
// EmitInt emits a int type to the given buffer.
func (br *Builder) EmitInt(i int64) *Builder {
if br.err != nil {
return br
}
if i == -1 {
return br.EmitOpcode(PUSHM1)
}
if i == 0 {
return br.EmitOpcode(PUSHF)
}
if i > 0 && i < 16 {
val := Instruction(int(PUSH1) - 1 + int(i))
return br.EmitOpcode(val)
}
bInt := big.NewInt(i)
val := reverse(bInt.Bytes())
return br.EmitBytes(val)
}
// EmitString emits a string to the given buffer.
func (br *Builder) EmitString(s string) *Builder {
if br.err != nil {
return br
}
return br.EmitBytes([]byte(s))
}
// EmitBytes emits a byte array to the given buffer.
func (br *Builder) EmitBytes(b []byte) *Builder {
if br.err != nil {
return br
}
var (
n = len(b)
)
if n <= int(PUSHBYTES75) {
return br.Emit(Instruction(n), b)
} else if n < 0x100 {
br.Emit(PUSHDATA1, []byte{byte(n)})
} else if n < 0x10000 {
buf := make([]byte, 2)
binary.LittleEndian.PutUint16(buf, uint16(n))
br.Emit(PUSHDATA2, buf)
} else {
buf := make([]byte, 4)
binary.LittleEndian.PutUint32(buf, uint32(n))
br.Emit(PUSHDATA4, buf)
}
_, br.err = br.w.Write(b)
return br
}
// EmitSyscall emits the syscall API to the given buffer.
// Syscall API string cannot be 0.
func (br *Builder) EmitSyscall(api string) *Builder {
if br.err != nil {
return br
}
if len(api) == 0 {
br.err = errors.New("syscall api cannot be of length 0")
}
buf := make([]byte, len(api)+1)
buf[0] = byte(len(api))
copy(buf[1:], []byte(api))
return br.Emit(SYSCALL, buf)
}
// EmitCall emits a call Opcode with label to the given buffer.
func (br *Builder) EmitCall(op Instruction, label int16) *Builder {
return br.EmitJmp(op, label)
}
// EmitJmp emits a jump Opcode along with label to the given buffer.
func (br *Builder) EmitJmp(op Instruction, label int16) *Builder {
if !isOpcodeJmp(op) {
br.err = fmt.Errorf("opcode %d is not a jump or call type", op)
}
buf := make([]byte, 2)
binary.LittleEndian.PutUint16(buf, uint16(label))
return br.Emit(op, buf)
}
// EmitAppCall emits an appcall, if tailCall is true, tailCall opcode will be
// emitted instead.
func (br *Builder) EmitAppCall(scriptHash util.Uint160, tailCall bool) *Builder {
op := APPCALL
if tailCall {
op = TAILCALL
}
return br.Emit(op, scriptHash.Bytes())
}
// EmitAppCallWithOperationAndData emits an appcall with the given operation and data.
func (br *Builder) EmitAppCallWithOperationAndData(w *bytes.Buffer, scriptHash util.Uint160, operation string, data []byte) *Builder {
br.EmitBytes(data)
br.EmitString(operation)
return br.EmitAppCall(scriptHash, false)
}
// EmitAppCallWithOperation emits an appcall with the given operation.
func (br *Builder) EmitAppCallWithOperation(scriptHash util.Uint160, operation string) *Builder {
br.EmitBool(false)
br.EmitString(operation)
return br.EmitAppCall(scriptHash, false)
}
func isOpcodeJmp(op Instruction) bool {
if op == JMP || op == JMPIFNOT || op == JMPIF || op == CALL {
return true
}
return false
}

71
pkg/vm/stack/bytearray.go Normal file
View file

@ -0,0 +1,71 @@
package stack
import (
"bytes"
"errors"
"math/big"
"strconv"
)
// ByteArray represents a slice of bytes on the stack
type ByteArray struct {
*abstractItem
val []byte
}
//NewByteArray returns a ByteArray stack item
// given a byte slice
func NewByteArray(val []byte) *ByteArray {
return &ByteArray{
&abstractItem{},
val,
}
}
//ByteArray overrides the default abstractItem Bytes array method
func (ba *ByteArray) ByteArray() (*ByteArray, error) {
return ba, nil
}
//Equals returns true, if two bytearrays are equal
func (ba *ByteArray) Equals(other *ByteArray) *Boolean {
// If either are nil, return false
if ba == nil || other == nil {
return NewBoolean(false)
}
return NewBoolean(bytes.Equal(ba.val, other.val))
}
//Integer overrides the default Integer method to convert an
// ByteArray Into an integer
func (ba *ByteArray) Integer() (*Int, error) {
dest := reverse(ba.val)
integerVal := new(big.Int).SetBytes(dest)
return NewInt(integerVal)
}
// Boolean will convert a byte array into a boolean stack item
func (ba *ByteArray) Boolean() (*Boolean, error) {
boolean, err := strconv.ParseBool(string(ba.val))
if err != nil {
return nil, errors.New("cannot convert byte array to a boolean")
}
return NewBoolean(boolean), nil
}
// XXX: move this into a pkg/util/slice folder
// Go mod not working
func reverse(b []byte) []byte {
if len(b) < 2 {
return b
}
dest := make([]byte, len(b))
for i, j := 0, len(b)-1; i < j+1; i, j = i+1, j-1 {
dest[i], dest[j] = b[j], b[i]
}
return dest
}

152
pkg/vm/stack/context.go Normal file
View file

@ -0,0 +1,152 @@
package stack
import (
"encoding/binary"
"errors"
)
// Context represent the current execution context of the VM.
// context will be treated as stack item and placed onto the invocation stack
type Context struct {
*abstractItem
// Instruction pointer.
ip int
// The raw program script.
prog []byte
// Breakpoints
breakPoints []int
// Evaluation Stack
Estack RandomAccess
// Alternative Stack
Astack RandomAccess
}
// NewContext return a new Context object.
func NewContext(b []byte) *Context {
return &Context{
abstractItem: &abstractItem{},
ip: -1,
prog: b,
breakPoints: []int{},
}
}
// Context overrides the default implementation
// to return a context item
func (c *Context) Context() (*Context, error) {
return c, nil
}
// Next return the next instruction to execute.
func (c *Context) Next() (Instruction, error) {
c.ip++
if c.ip >= len(c.prog) {
return RET, errors.New("program pointer is more than the length of program. Returning RET OPCODE")
}
return Instruction(c.prog[c.ip]), nil
}
// IP returns the absolute instruction without taking 0 into account.
// If that program starts the ip = 0 but IP() will return 1, cause its
// the first instruction.
func (c *Context) IP() int {
return c.ip + 1
}
// LenInstr returns the number of instructions loaded.
func (c *Context) LenInstr() int {
return len(c.prog)
}
// CurrInstr returns the current instruction and opcode.
func (c *Context) CurrInstr() (int, Instruction) {
if c.ip < 0 {
return c.ip, Instruction(0x00)
}
return c.ip, Instruction(c.prog[c.ip])
}
// Copy returns an new exact copy of c.
func (c *Context) Copy() *Context {
return &Context{
ip: c.ip,
prog: c.prog,
breakPoints: c.breakPoints,
}
}
// Program returns the loaded program.
func (c *Context) Program() []byte {
return c.prog
}
func (c *Context) atBreakPoint() bool {
for _, n := range c.breakPoints {
if n == c.ip {
return true
}
}
return false
}
func (c *Context) String() string {
return "execution context"
}
// ReadUint32 reads a uint32 from the script
func (c *Context) ReadUint32() uint32 {
start, end := c.IP(), c.IP()+4
if end > len(c.prog) {
return 0
}
val := binary.LittleEndian.Uint32(c.prog[start:end])
c.ip += 4
return val
}
// ReadUint16 reads a uint16 from the script
func (c *Context) ReadUint16() uint16 {
start, end := c.IP(), c.IP()+2
if end > len(c.prog) {
return 0
}
val := binary.LittleEndian.Uint16(c.prog[start:end])
c.ip += 2
return val
}
// ReadByte reads one byte from the script
func (c *Context) ReadByte() (byte, error) {
byt, err := c.ReadBytes(1)
if err != nil {
return 0, err
}
return byt[0], nil
}
// ReadBytes will read n bytes from the context
func (c *Context) ReadBytes(n int) ([]byte, error) {
start, end := c.IP(), c.IP()+n
if end > len(c.prog) {
return nil, errors.New("Too many bytes to read, pointer goes past end of program")
}
out := make([]byte, n)
copy(out, c.prog[start:end])
c.ip += n
return out, nil
}
func (c *Context) readVarBytes() ([]byte, error) {
n, err := c.ReadByte()
if err != nil {
return nil, err
}
return c.ReadBytes(int(n))
}

133
pkg/vm/stack/instruction.go Normal file
View file

@ -0,0 +1,133 @@
package stack
// Instruction represents a operation code in the neovm
type Instruction byte
// Viable list of supported instruction constants.
const (
// Constants
PUSH0 Instruction = 0x00
PUSHF Instruction = PUSH0
PUSHBYTES1 Instruction = 0x01
PUSHBYTES75 Instruction = 0x4B
PUSHDATA1 Instruction = 0x4C
PUSHDATA2 Instruction = 0x4D
PUSHDATA4 Instruction = 0x4E
PUSHM1 Instruction = 0x4F
PUSH1 Instruction = 0x51
PUSHT Instruction = PUSH1
PUSH2 Instruction = 0x52
PUSH3 Instruction = 0x53
PUSH4 Instruction = 0x54
PUSH5 Instruction = 0x55
PUSH6 Instruction = 0x56
PUSH7 Instruction = 0x57
PUSH8 Instruction = 0x58
PUSH9 Instruction = 0x59
PUSH10 Instruction = 0x5A
PUSH11 Instruction = 0x5B
PUSH12 Instruction = 0x5C
PUSH13 Instruction = 0x5D
PUSH14 Instruction = 0x5E
PUSH15 Instruction = 0x5F
PUSH16 Instruction = 0x60
// Flow control
NOP Instruction = 0x61
JMP Instruction = 0x62
JMPIF Instruction = 0x63
JMPIFNOT Instruction = 0x64
CALL Instruction = 0x65
RET Instruction = 0x66
APPCALL Instruction = 0x67
SYSCALL Instruction = 0x68
TAILCALL Instruction = 0x69
// Stack
DUPFROMALTSTACK Instruction = 0x6A
TOALTSTACK Instruction = 0x6B
FROMALTSTACK Instruction = 0x6C
XDROP Instruction = 0x6D
XSWAP Instruction = 0x72
XTUCK Instruction = 0x73
DEPTH Instruction = 0x74
DROP Instruction = 0x75
DUP Instruction = 0x76
NIP Instruction = 0x77
OVER Instruction = 0x78
PICK Instruction = 0x79
ROLL Instruction = 0x7A
ROT Instruction = 0x7B
SWAP Instruction = 0x7C
TUCK Instruction = 0x7D
// Splice
CAT Instruction = 0x7E
SUBSTR Instruction = 0x7F
LEFT Instruction = 0x80
RIGHT Instruction = 0x81
SIZE Instruction = 0x82
// Bitwise logic
INVERT Instruction = 0x83
AND Instruction = 0x84
OR Instruction = 0x85
XOR Instruction = 0x86
EQUAL Instruction = 0x87
// Arithmetic
INC Instruction = 0x8B
DEC Instruction = 0x8C
SIGN Instruction = 0x8D
NEGATE Instruction = 0x8F
ABS Instruction = 0x90
NOT Instruction = 0x91
NZ Instruction = 0x92
ADD Instruction = 0x93
SUB Instruction = 0x94
MUL Instruction = 0x95
DIV Instruction = 0x96
MOD Instruction = 0x97
SHL Instruction = 0x98
SHR Instruction = 0x99
BOOLAND Instruction = 0x9A
BOOLOR Instruction = 0x9B
NUMEQUAL Instruction = 0x9C
NUMNOTEQUAL Instruction = 0x9E
LT Instruction = 0x9F
GT Instruction = 0xA0
LTE Instruction = 0xA1
GTE Instruction = 0xA2
MIN Instruction = 0xA3
MAX Instruction = 0xA4
WITHIN Instruction = 0xA5
// Crypto
SHA1 Instruction = 0xA7
SHA256 Instruction = 0xA8
HASH160 Instruction = 0xA9
HASH256 Instruction = 0xAA
CHECKSIG Instruction = 0xAC
CHECKMULTISIG Instruction = 0xAE
// Array
ARRAYSIZE Instruction = 0xC0
PACK Instruction = 0xC1
UNPACK Instruction = 0xC2
PICKITEM Instruction = 0xC3
SETITEM Instruction = 0xC4
NEWARRAY Instruction = 0xC5
NEWSTRUCT Instruction = 0xC6
APPEND Instruction = 0xC8
REVERSE Instruction = 0xC9
REMOVE Instruction = 0xCA
// Exceptions
THROW Instruction = 0xF0
THROWIFNOT Instruction = 0xF1
)
// Value returns the byte-value of the opcode.
func (i Instruction) Value() byte {
return byte(i)
}

72
pkg/vm/stack/int_test.go Normal file
View file

@ -0,0 +1,72 @@
package stack
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestAdd(t *testing.T) {
a := testMakeStackInt(t, 10)
b := testMakeStackInt(t, 20)
expected := testMakeStackInt(t, 30)
c, err := a.Add(b)
assert.Nil(t, err)
assert.Equal(t, true, expected.Equal(c))
}
func TestSub(t *testing.T) {
a := testMakeStackInt(t, 30)
b := testMakeStackInt(t, 200)
expected := testMakeStackInt(t, 170)
c, err := b.Sub(a)
assert.Nil(t, err)
assert.Equal(t, true, expected.Equal(c))
}
func TestMul(t *testing.T) {
a := testMakeStackInt(t, 10)
b := testMakeStackInt(t, 20)
expected := testMakeStackInt(t, 200)
c, err := a.Mul(b)
assert.Nil(t, err)
assert.Equal(t, true, expected.Equal(c))
}
func TestMod(t *testing.T) {
a := testMakeStackInt(t, 10)
b := testMakeStackInt(t, 20)
expected := testMakeStackInt(t, 10)
c, err := a.Mod(b)
assert.Nil(t, err)
assert.Equal(t, true, expected.Equal(c))
}
func TestLsh(t *testing.T) {
a := testMakeStackInt(t, 23)
b := testMakeStackInt(t, 8)
expected := testMakeStackInt(t, 5888)
c, err := a.Lsh(b)
assert.Nil(t, err)
assert.Equal(t, true, expected.Equal(c))
}
func TestRsh(t *testing.T) {
a := testMakeStackInt(t, 128)
b := testMakeStackInt(t, 3)
expected := testMakeStackInt(t, 16)
c, err := a.Rsh(b)
assert.Nil(t, err)
assert.Equal(t, true, expected.Equal(c))
}
func TestByteArrConversion(t *testing.T) {
var num int64 = 100000
a := testMakeStackInt(t, num)
ba, err := a.ByteArray()
assert.Nil(t, err)
have, err := ba.Integer()
assert.Nil(t, err)
assert.Equal(t, num, have.val.Int64())
}

View file

@ -0,0 +1,61 @@
package stack
import "errors"
// Invocation embeds a Random Access stack
// Providing helper methods for the context object
type Invocation struct{ RandomAccess }
//NewInvocation will return a new
// Invocation stack
func NewInvocation() *Invocation {
return &Invocation{
RandomAccess{
vals: make([]Item, 0, StackAverageSize),
},
}
}
func (i *Invocation) peekContext(n uint16) (*Context, error) {
item, err := i.Peek(n)
if err != nil {
return nil, err
}
return item.Context()
}
// CurrentContext returns the current context on the invocation stack
func (i *Invocation) CurrentContext() (*Context, error) {
return i.peekContext(0)
}
// PopCurrentContext Pops a context item from the top of the stack
func (i *Invocation) PopCurrentContext() (*Context, error) {
item, err := i.Pop()
if err != nil {
return nil, err
}
ctx, err := item.Context()
if err != nil {
return nil, err
}
return ctx, err
}
// CallingContext will return the cntext item
// that will be called next.
func (i *Invocation) CallingContext() (*Context, error) {
if i.Len() < 1 {
return nil, errors.New("Length of invocation stack is < 1, no calling context")
}
return i.peekContext(1)
}
// EntryContext will return the context item that
// started the program
func (i *Invocation) EntryContext() (*Context, error) {
// firstItemIndex refers to the first item that was popped on the stack
firstItemIndex := uint16(i.Len() - 1) // N.B. if this overflows because len is zero, then an error will be returned
return i.peekContext(firstItemIndex)
}

150
pkg/vm/stack/stack.go Normal file
View file

@ -0,0 +1,150 @@
package stack
import (
"errors"
"fmt"
)
const (
// StackAverageSize is used to set the capacity of the stack
// setting this number too low, will cause extra allocations
StackAverageSize = 20
)
// RandomAccess represents a Random Access Stack
type RandomAccess struct {
vals []Item
}
// New will return a new random access stack
func New() *RandomAccess {
return &RandomAccess{
vals: make([]Item, 0, StackAverageSize),
}
}
// Items will return all items in the stack
func (ras *RandomAccess) items() []Item {
return ras.vals
}
//Len will return the length of the stack
func (ras *RandomAccess) Len() int {
if ras.vals == nil {
return -1
}
return len(ras.vals)
}
// Clear will remove all items in the stack
func (ras *RandomAccess) Clear() {
ras.vals = make([]Item, 0, StackAverageSize)
}
// Pop will remove the last stack item that was added
func (ras *RandomAccess) Pop() (Item, error) {
if len(ras.vals) == 0 {
return nil, errors.New("There are no items on the stack to pop")
}
if ras.vals == nil {
return nil, errors.New("Cannot pop from a nil stack")
}
l := len(ras.vals)
item := ras.vals[l-1]
ras.vals = ras.vals[:l-1]
return item, nil
}
// Push will put a stack item onto the top of the stack
func (ras *RandomAccess) Push(item Item) *RandomAccess {
if ras.vals == nil {
ras.vals = make([]Item, 0, StackAverageSize)
}
ras.vals = append(ras.vals, item)
return ras
}
// Insert will push a stackItem onto the stack at position `n`
// Note; index 0 is the top of the stack, which is the end of slice
func (ras *RandomAccess) Insert(n uint16, item Item) (*RandomAccess, error) {
if n == 0 {
return ras.Push(item), nil
}
if ras.vals == nil {
ras.vals = make([]Item, 0, StackAverageSize)
}
// Check that we are not inserting out of the bounds
stackSize := uint16(len(ras.vals))
if n > stackSize-1 {
return nil, fmt.Errorf("Tried to insert at index %d when length of stack is %d", n, len(ras.vals))
}
index := stackSize - n
ras.vals = append(ras.vals, item)
copy(ras.vals[index:], ras.vals[index-1:])
ras.vals[index] = item
return ras, nil
}
// Peek will check an element at a given index
// Note: 0 is the top of the stack, which is the end of the slice
func (ras *RandomAccess) Peek(n uint16) (Item, error) {
stackSize := uint16(len(ras.vals))
if n == 0 {
index := stackSize - 1
return ras.vals[index], nil
}
if ras.Len() < 1 {
return nil, fmt.Errorf("cannot peak at a stack with no item, length of stack is %d", ras.Len())
}
// Check that we are not peeking out of the bounds
if n > stackSize-1 {
return nil, fmt.Errorf("Tried to peek at index %d when length of stack is %d", n, len(ras.vals))
}
index := stackSize - n - 1
return ras.vals[index], nil
}
// CopyTo will copy all of the stack items from `ras` into the stack that is passed as an argument
// XXX: once maxstacksize is implemented, we will return error if size goes over
// There will also be additional checks needed once stack isolation is added
func (ras *RandomAccess) CopyTo(stack *RandomAccess) error {
stack.vals = append(stack.vals, ras.vals...)
return nil
}
// Convenience Functions
// PopInt will remove the last stack item that was added
// And cast it to an integer
func (ras *RandomAccess) PopInt() (*Int, error) {
item, err := ras.Pop()
if err != nil {
return nil, err
}
return item.Integer()
}
// PopByteArray will remove the last stack item that was added
// And cast it to an ByteArray
func (ras *RandomAccess) PopByteArray() (*ByteArray, error) {
item, err := ras.Pop()
if err != nil {
return nil, err
}
return item.ByteArray()
}

161
pkg/vm/stack/stack_test.go Normal file
View file

@ -0,0 +1,161 @@
package stack
import (
"fmt"
"math/big"
"testing"
"github.com/stretchr/testify/assert"
)
func TestStackPushPop(t *testing.T) {
// Create two stack Integers
a, err := NewInt(big.NewInt(10))
if err != nil {
t.Fail()
}
b, err := NewInt(big.NewInt(20))
if err != nil {
t.Fail()
}
// Create a new stack
testStack := New()
// Push to stack
testStack.Push(a).Push(b)
// There should only be two values on the stack
assert.Equal(t, 2, testStack.Len())
// Pop first element and it should be equal to b
stackElement, err := testStack.Pop()
if err != nil {
t.Fail()
}
item, err := stackElement.Integer()
if err != nil {
t.Fail()
}
assert.Equal(t, true, item.Equal(b))
// Pop second element and it should be equal to a
stackElement, err = testStack.Pop()
if err != nil {
t.Fail()
}
item, err = stackElement.Integer()
if err != nil {
t.Fail()
}
assert.Equal(t, true, item.Equal(a))
// We should get an error as there are nomore items left to pop
stackElement, err = testStack.Pop()
assert.NotNil(t, err)
}
// For this test to pass, we should get an error when popping from a nil stack
// and we should initialise and push an element if pushing to an empty stack
func TestPushPopNil(t *testing.T) {
// stack is nil when initialised without New constructor
testStack := RandomAccess{}
// Popping from nil stack
// - should give an error
// - element returned should be nil
stackElement, err := testStack.Pop()
assert.NotNil(t, err)
assert.Nil(t, stackElement)
// stack should still be nil after failing to pop
assert.Nil(t, testStack.vals)
// create a random test stack item
a, err := NewInt(big.NewInt(2))
assert.Nil(t, err)
// push random item to stack
testStack.Push(a)
// push should initialise the stack and put one element on the stack
assert.Equal(t, 1, testStack.Len())
}
// Test passes if we can peek and modify an item
//without modifying the value on the stack
func TestStackPeekMutability(t *testing.T) {
testStack := New()
a, err := NewInt(big.NewInt(2))
assert.Nil(t, err)
b, err := NewInt(big.NewInt(3))
assert.Nil(t, err)
testStack.Push(a).Push(b)
peekedItem := testPeakInteger(t, testStack, 0)
assert.Equal(t, true, peekedItem.Equal(b))
// Check that by modifying the peeked value,
// we did not modify the item on the stack
peekedItem = a
peekedItem.val = big.NewInt(0)
// Pop item from stack and check it is still the same
poppedItem := testPopInteger(t, testStack)
assert.Equal(t, true, poppedItem.Equal(b))
}
func TestStackPeek(t *testing.T) {
testStack := New()
values := []int64{23, 45, 67, 89, 12, 344}
for _, val := range values {
a := testMakeStackInt(t, val)
testStack.Push(a)
}
// i starts at 0, j starts at len(values)-1
for i, j := 0, len(values)-1; j >= 0; i, j = i+1, j-1 {
peekedItem := testPeakInteger(t, testStack, uint16(i))
a := testMakeStackInt(t, values[j])
fmt.Printf("%#v\n", peekedItem.val.Int64())
assert.Equal(t, true, a.Equal(peekedItem))
}
}
func TestStackInsert(t *testing.T) {
testStack := New()
a := testMakeStackInt(t, 2)
b := testMakeStackInt(t, 4)
c := testMakeStackInt(t, 6)
// insert on an empty stack should put element on top
_, err := testStack.Insert(0, a)
assert.Equal(t, err, nil)
_, err = testStack.Insert(0, b)
assert.Equal(t, err, nil)
_, err = testStack.Insert(1, c)
assert.Equal(t, err, nil)
// Order should be [a,c,b]
pop1 := testPopInteger(t, testStack)
pop2 := testPopInteger(t, testStack)
pop3 := testPopInteger(t, testStack)
assert.Equal(t, true, pop1.Equal(b))
assert.Equal(t, true, pop2.Equal(c))
assert.Equal(t, true, pop3.Equal(a))
}

49
pkg/vm/stack/stackitem.go Normal file
View file

@ -0,0 +1,49 @@
package stack
import (
"errors"
)
//Item is an interface which represents object that can be placed on the stack
type Item interface {
Integer() (*Int, error)
Boolean() (*Boolean, error)
ByteArray() (*ByteArray, error)
Array() (*Array, error)
Context() (*Context, error)
}
// Represents an `abstract` stack item
// which will hold default values for stack items
// this is intended to be embedded into types that you will use on the stack
type abstractItem struct{}
// Integer is the default implementation for a stackItem
// Implements Item interface
func (a *abstractItem) Integer() (*Int, error) {
return nil, errors.New("This stack item is not an Integer")
}
// Boolean is the default implementation for a stackItem
// Implements Item interface
func (a *abstractItem) Boolean() (*Boolean, error) {
return nil, errors.New("This stack item is not a Boolean")
}
// ByteArray is the default implementation for a stackItem
// Implements Item interface
func (a *abstractItem) ByteArray() (*ByteArray, error) {
return nil, errors.New("This stack item is not a byte array")
}
// Array is the default implementation for a stackItem
// Implements Item interface
func (a *abstractItem) Array() (*Array, error) {
return nil, errors.New("This stack item is not an array")
}
// Context is the default implementation for a stackItem
// Implements Item interface
func (a *abstractItem) Context() (*Context, error) {
return nil, errors.New("This stack item is not of type context")
}

View file

@ -0,0 +1,68 @@
package stack
import (
"math/big"
"testing"
"github.com/stretchr/testify/assert"
)
// A simple test to ensure that by embedding the abstract interface
// we immediately become a stack item, with the default values set to nil
func TestInterfaceEmbedding(t *testing.T) {
// Create an anonymous struct that embeds the abstractItem
a := struct {
*abstractItem
}{
&abstractItem{},
}
// Since interface checking can be done at compile time.
// If he abstractItem did not implement all methods of our interface `Item`
// Then any struct which embeds it, will also not implement the Item interface.
// This test would then give errors, at compile time.
var Items []Item
Items = append(Items, a)
// Default methods should give errors
// Here we just need to test against one of the methods in the interface
for _, element := range Items {
x, err := element.Integer()
assert.Nil(t, x)
assert.NotNil(t, err, nil)
}
}
// TestIntCasting is a simple test to test that the Integer method is overwritten
// from the abstractItem
func TestIntMethodOverride(t *testing.T) {
testValues := []int64{0, 10, 200, 30, 90}
var Items []Item
// Convert a range of int64s into Stack Integers
// Adding them into an array of StackItems
for _, num := range testValues {
stackInteger, err := NewInt(big.NewInt(num))
if err != nil {
t.Fail()
}
Items = append(Items, stackInteger)
}
// For each item, call the Integer method on the interface
// Which should return an integer and no error
// as the stack integer struct overrides that method
for i, element := range Items {
k, err := element.Integer()
if err != nil {
t.Fail()
}
if k.val.Cmp(big.NewInt(testValues[i])) != 0 {
t.Fail()
}
}
}

View file

@ -0,0 +1,44 @@
package stack
import (
"bytes"
"encoding/binary"
"math/big"
"testing"
"github.com/stretchr/testify/assert"
)
// helper functions
func testPeakInteger(t *testing.T, tStack *RandomAccess, n uint16) *Int {
stackElement, err := tStack.Peek(n)
assert.Nil(t, err)
item, err := stackElement.Integer()
if err != nil {
t.Fail()
}
return item
}
func testPopInteger(t *testing.T, tStack *RandomAccess) *Int {
stackElement, err := tStack.Pop()
assert.Nil(t, err)
item, err := stackElement.Integer()
if err != nil {
t.Fail()
}
return item
}
func testMakeStackInt(t *testing.T, num int64) *Int {
a, err := NewInt(big.NewInt(num))
assert.Nil(t, err)
return a
}
func testReadInt64(data []byte) int64 {
var ret int64
buf := bytes.NewBuffer(data)
binary.Read(buf, binary.LittleEndian, &ret)
return ret
}

20
pkg/vm/state.go Normal file
View file

@ -0,0 +1,20 @@
package vm
//Vmstate represents all possible states that the neo-vm can be in
type Vmstate byte
// List of possible vm states
const (
// NONE is the running state of the vm
// NONE signifies that the vm is ready to process an opcode
NONE = 0
// HALT is a stopped state of the vm
// where the stop was signalled by the program completion
HALT = 1 << 0
// FAULT is a stopped state of the vm
// where the stop was signalled by an error in the program
FAULT = 1 << 1
// BREAK is a suspended state for the VM
// were the break was signalled by a breakpoint
BREAK = 1 << 2
)

72
pkg/vm/vm.go Normal file
View file

@ -0,0 +1,72 @@
package vm
import (
"fmt"
"github.com/CityOfZion/neo-go/pkg/vm/stack"
)
// VM represents an instance of a Neo Virtual Machine
type VM struct {
// ResultStack contains the results of
// the last evaluation stack before the program terminated
ResultStack stack.RandomAccess
// InvocationStack contains all of the contexts
// loaded into the vm
InvocationStack stack.Invocation
state Vmstate
}
// NewVM will:
// Set the state of the VM to NONE
// instantiate a script as a new context
// Push the Context to the Invocation stack
func NewVM(script []byte) *VM {
ctx := stack.NewContext(script)
v := &VM{
state: NONE,
}
v.InvocationStack.Push(ctx)
return v
}
// Run loops over the current context by continuously stepping.
// Run breaks; once step returns an error or any state that is not NONE
func (v *VM) Run() (Vmstate, error) {
for {
state, err := v.step()
if err != nil || state != NONE {
return state, err
}
}
}
// step will read `one` opcode from the script in the current context
// Then excute that opcode
func (v *VM) step() (Vmstate, error) {
// Get Current Context
ctx, err := v.InvocationStack.CurrentContext()
if err != nil {
return FAULT, err
}
// Read Opcode from context
op, _ := ctx.Next() // The only error that can occur from this, is if the pointer goes over the pointer
// In the NEO-VM specs, this is ignored and we return the RET opcode
// Execute OpCode
state, err := v.executeOp(stack.Instruction(op), ctx)
if err != nil {
return FAULT, err
}
return state, nil
}
// ExecuteOp will execute one opcode on a given context.
// If the opcode is not registered, then an unknown opcode error will be returned
func (v *VM) executeOp(op stack.Instruction, ctx *stack.Context) (Vmstate, error) {
//Find function which handles that specific opcode
handleOp, ok := opFunc[op]
if !ok {
return FAULT, fmt.Errorf("unknown opcode entered %v", op)
}
return handleOp(op, ctx, &v.InvocationStack, &v.ResultStack)
}

21
pkg/vm/vm_ops.go Normal file
View file

@ -0,0 +1,21 @@
package vm
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.ADD: Add,
stack.SUB: Sub,
stack.PUSHBYTES1: PushNBytes,
stack.PUSHBYTES75: PushNBytes,
stack.RET: RET,
stack.EQUAL: EQUAL,
stack.THROWIFNOT: THROWIFNOT,
}
func init() {
for i := int(stack.PUSHBYTES1); i <= int(stack.PUSHBYTES75); i++ {
opFunc[stack.Instruction(i)] = PushNBytes
}
}

17
pkg/vm/vm_ops_bitwise.go Normal file
View file

@ -0,0 +1,17 @@
package vm
import "github.com/CityOfZion/neo-go/pkg/vm/stack"
// Bitwise logic
// EQUAL pushes true to the stack
// If the two top items on the stack are equal
func EQUAL(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) {
itemA, itemB, err := popTwoByteArrays(ctx)
if err != nil {
return FAULT, err
}
ctx.Estack.Push(itemA.Equals(itemB))
return NONE, nil
}

View file

@ -0,0 +1,33 @@
package vm
import (
"errors"
"github.com/CityOfZion/neo-go/pkg/vm/stack"
)
// vm exceptions
// THROWIFNOT faults if the item on the top of the stack
// does not evaluate to true
// For specific logic on how a number of bytearray is evaluated can be seen
// from the boolean conversion methods on the stack items
func THROWIFNOT(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) {
// Pop item from top of stack
item, err := ctx.Estack.Pop()
if err != nil {
return FAULT, err
}
// Convert to a boolean
ok, err := item.Boolean()
if err != nil {
return FAULT, err
}
// If false, throw
if !ok.Value() {
return FAULT, errors.New("item on top of stack evaluates to false")
}
return NONE, nil
}

27
pkg/vm/vm_ops_flow.go Normal file
View file

@ -0,0 +1,27 @@
package vm
import (
"github.com/CityOfZion/neo-go/pkg/vm/stack"
)
// Flow control
// RET Returns from the current context
// Returns HALT if there are nomore context's to run
func RET(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) {
// Pop current context from the Inovation stack
ctx, err := istack.PopCurrentContext()
if err != nil {
return FAULT, err
}
// If this was the last context, then we copy over the evaluation stack to the resultstack
// As the program is about to terminate, once we remove the context
if istack.Len() == 0 {
err = ctx.Estack.CopyTo(rstack)
return HALT, err
}
return NONE, nil
}

70
pkg/vm/vm_ops_maths.go Normal file
View file

@ -0,0 +1,70 @@
package vm
import (
"github.com/CityOfZion/neo-go/pkg/vm/stack"
)
// Add adds two stack Items together.
// Returns an error if either items cannot be casted to an integer
// or if integers cannot be added together
func Add(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) {
operandA, operandB, err := popTwoIntegers(ctx)
if err != nil {
return FAULT, err
}
res, err := operandA.Add(operandB)
if err != nil {
return FAULT, err
}
ctx.Estack.Push(res)
return NONE, nil
}
// Sub subtracts two stack Items.
// Returns an error if either items cannot be casted to an integer
// or if integers cannot be subtracted together
func Sub(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) {
operandA, operandB, err := popTwoIntegers(ctx)
if err != nil {
return FAULT, err
}
res, err := operandB.Sub(operandA)
if err != nil {
return HALT, err
}
ctx.Estack.Push(res)
return NONE, nil
}
func popTwoIntegers(ctx *stack.Context) (*stack.Int, *stack.Int, error) {
operandA, err := ctx.Estack.PopInt()
if err != nil {
return nil, nil, err
}
operandB, err := ctx.Estack.PopInt()
if err != nil {
return nil, nil, err
}
return operandA, operandB, nil
}
func popTwoByteArrays(ctx *stack.Context) (*stack.ByteArray, *stack.ByteArray, error) {
// Pop first stack item and cast as byte array
ba1, err := ctx.Estack.PopByteArray()
if err != nil {
return nil, nil, err
}
// Pop second stack item and cast as byte array
ba2, err := ctx.Estack.PopByteArray()
if err != nil {
return nil, nil, err
}
return ba1, ba2, nil
}

View file

@ -0,0 +1,69 @@
package vm
import (
"math/big"
"testing"
"github.com/CityOfZion/neo-go/pkg/vm/stack"
"github.com/stretchr/testify/assert"
)
func TestAddOp(t *testing.T) {
v := VM{}
a, err := stack.NewInt(big.NewInt(20))
if err != nil {
t.Fail()
}
b, err := stack.NewInt(big.NewInt(23))
if err != nil {
t.Fail()
}
ctx := stack.NewContext([]byte{})
ctx.Estack.Push(a).Push(b)
v.executeOp(stack.ADD, ctx)
// Stack should have one item
assert.Equal(t, 1, ctx.Estack.Len())
item, err := ctx.Estack.PopInt()
if err != nil {
t.Fail()
}
assert.Equal(t, int64(43), item.Value().Int64())
}
func TestSubOp(t *testing.T) {
v := VM{}
a, err := stack.NewInt(big.NewInt(30))
if err != nil {
t.Fail()
}
b, err := stack.NewInt(big.NewInt(40))
if err != nil {
t.Fail()
}
ctx := stack.NewContext([]byte{})
ctx.Estack.Push(a).Push(b)
v.executeOp(stack.SUB, ctx)
// Stack should have one item
assert.Equal(t, 1, ctx.Estack.Len())
item, err := ctx.Estack.PopInt()
if err != nil {
t.Fail()
}
assert.Equal(t, int64(-10), item.Value().Int64())
}

View file

@ -0,0 +1,19 @@
package vm
import (
"github.com/CityOfZion/neo-go/pkg/vm/stack"
)
// Stack Manipulation Opcodes
// PushNBytes will Read N Bytes from the script and push it onto the stack
func PushNBytes(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) {
val, err := ctx.ReadBytes(int(op))
if err != nil {
return FAULT, err
}
ba := stack.NewByteArray(val)
ctx.Estack.Push(ba)
return NONE, nil
}

119
pkg/vm/vm_test.go Normal file
View file

@ -0,0 +1,119 @@
package vm
import (
"fmt"
"testing"
"github.com/CityOfZion/neo-go/pkg/vm/stack"
"github.com/stretchr/testify/assert"
)
func TestPushAdd(t *testing.T) {
builder := stack.NewBuilder()
// PUSH TWO NUMBER
// ADD THEM TOGETHER
builder.EmitInt(20).EmitInt(34).EmitOpcode(stack.ADD)
// Pass program to VM
vm := NewVM(builder.Bytes())
// Execute first OPCODE
// Should be PUSH(20)
state, err := vm.step()
assert.Equal(t, NONE, int(state))
assert.Nil(t, err)
// We should have the number 20 on stack
ok := peekTopEStackIsValue(t, vm, 20)
assert.True(t, ok)
// Excute second OPCODE
// Should be PUSH(34)
state, err = vm.step()
assert.Equal(t, NONE, int(state))
assert.Nil(t, err)
// We should have the number 34 at the top of the stack
ok = peekTopEStackIsValue(t, vm, 34)
assert.True(t, ok)
// Excute third OPCODE
// Should Add both values on the stack
state, err = vm.step()
assert.Equal(t, NONE, int(state))
assert.Nil(t, err)
// We should now have one value on the stack
//It should be equal to 20+34 = 54
ok = EstackLen(t, vm, 1)
assert.True(t, ok)
ok = peekTopEStackIsValue(t, vm, 54)
assert.True(t, ok)
// If we try to step again, we should get a nil error and HALT
// because we have gone over the instruction pointer
// error is nil because when there are nomore instructions, the vm
// will add a RET opcode and return
state, err = vm.step()
assert.Equal(t, HALT, int(state))
assert.Nil(t, err)
}
func TestSimpleRun(t *testing.T) {
// Program pushes 20 and 34 to the stack
// Adds them together
// pushes 54 to the stack
// Checks if result of addition and 54 are equal
// Faults if not
// Push(20)
// Push(34)
// Add
// Push(54)
// Equal
//THROWIFNOT
builder := stack.NewBuilder()
builder.EmitInt(20).EmitInt(34).EmitOpcode(stack.ADD)
builder.EmitInt(54).EmitOpcode(stack.EQUAL).EmitOpcode(stack.THROWIFNOT)
// Pass program to VM
vm := NewVM(builder.Bytes())
// Runs vm with program
_, err := vm.Run()
assert.Nil(t, err)
// ResultStack should be nil
assert.Equal(t, -1, vm.ResultStack.Len())
}
// returns true if the value at the top of the evaluation stack is a integer
// and equals the value passed in
func peekTopEStackIsValue(t *testing.T, vm *VM, value int64) bool {
item := peakTopEstack(t, vm)
integer, err := item.Integer()
assert.Nil(t, err)
return value == integer.Value().Int64()
}
// peaks the stack item on the top of the evaluation stack
// if the current context and returns it
func peakTopEstack(t *testing.T, vm *VM) stack.Item {
ctx, err := vm.InvocationStack.CurrentContext()
fmt.Println(err)
assert.Nil(t, err)
item, err := ctx.Estack.Peek(0)
assert.Nil(t, err)
return item
}
// returns true if the total number of items on the evaluation stack is equal to value
func EstackLen(t *testing.T, vm *VM, value int) bool {
ctx, err := vm.InvocationStack.CurrentContext()
assert.Nil(t, err)
return value == ctx.Estack.Len()
}