neo-go/pkg/vm/stack/context.go
decentralisedkev c1b6738bdb
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
2019-03-18 21:40:21 +00:00

152 lines
3.1 KiB
Go

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))
}