neo-go/pkg/vm/context.go

158 lines
3.4 KiB
Go
Raw Normal View History

package vm
import (
"errors"
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
"github.com/CityOfZion/neo-go/pkg/io"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/CityOfZion/neo-go/pkg/vm/opcode"
)
2019-10-22 14:56:03 +00:00
// Context represents the current execution context of the VM.
type Context struct {
// Instruction pointer.
ip int
// The next instruction pointer.
nextip int
// The raw program script.
prog []byte
2019-10-22 14:56:03 +00:00
// Breakpoints.
breakPoints []int
// Return value count, -1 is unspecified.
rvcount int
// Evaluation stack pointer.
estack *Stack
// Alt stack pointer.
astack *Stack
// Script hash of the prog.
scriptHash util.Uint160
}
2019-10-22 14:56:03 +00:00
// NewContext returns a new Context object.
func NewContext(b []byte) *Context {
return &Context{
prog: b,
breakPoints: []int{},
rvcount: -1,
}
}
// Next returns the next instruction to execute with its parameter if any. After
// its invocation the instruction pointer points to the instruction being
// returned.
func (c *Context) Next() (opcode.Opcode, []byte, error) {
c.ip = c.nextip
if c.ip >= len(c.prog) {
return opcode.RET, nil, nil
}
r := io.NewBinReaderFromBuf(c.prog[c.ip:])
var instrbyte = r.ReadB()
instr := opcode.Opcode(instrbyte)
c.nextip++
var numtoread int
switch instr {
case opcode.PUSHDATA1, opcode.SYSCALL:
var n = r.ReadB()
numtoread = int(n)
c.nextip++
case opcode.PUSHDATA2:
var n = r.ReadU16LE()
numtoread = int(n)
c.nextip += 2
case opcode.PUSHDATA4:
var n = r.ReadU32LE()
if n > MaxItemSize {
return instr, nil, errors.New("parameter is too big")
}
numtoread = int(n)
c.nextip += 4
case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.CALL, opcode.CALLED, opcode.CALLEDT:
numtoread = 2
case opcode.CALLI:
numtoread = 4
case opcode.APPCALL, opcode.TAILCALL:
numtoread = 20
case opcode.CALLE, opcode.CALLET:
numtoread = 22
default:
if instr >= opcode.PUSHBYTES1 && instr <= opcode.PUSHBYTES75 {
numtoread = int(instr)
} else {
// No parameters, can just return.
return instr, nil, nil
}
}
parameter := make([]byte, numtoread)
r.ReadBytes(parameter)
if r.Err != nil {
return instr, nil, errors.New("failed to read instruction parameter")
}
c.nextip += numtoread
return instr, parameter, 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, opcode.Opcode) {
return c.ip, opcode.Opcode(c.prog[c.ip])
}
// Copy returns an new exact copy of c.
func (c *Context) Copy() *Context {
ctx := new(Context)
*ctx = *c
return ctx
}
// Program returns the loaded program.
func (c *Context) Program() []byte {
return c.prog
}
// ScriptHash returns a hash of the script in the current context.
func (c *Context) ScriptHash() util.Uint160 {
if c.scriptHash.Equals(util.Uint160{}) {
c.scriptHash = hash.Hash160(c.prog)
}
return c.scriptHash
}
// Value implements StackItem interface.
func (c *Context) Value() interface{} {
return c
}
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"
}