neoneo-go/pkg/vm/context.go
Evgenii Stratonikov 42ae226f9e native: use proper stack for result
When native method calls other contract result should be put
on the stack of current context. With oracles this problem wasn't
noticed because of void return type.
2020-11-24 12:17:28 +03:00

269 lines
6.7 KiB
Go

package vm
import (
"encoding/binary"
"errors"
"math/big"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
// 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
// Breakpoints.
breakPoints []int
// Evaluation stack pointer.
estack *Stack
static *Slot
local *Slot
arguments *Slot
// Exception context stack pointer.
tryStack *Stack
// Script hash of the prog.
scriptHash util.Uint160
// Caller's contract script hash.
callingScriptHash util.Uint160
// Call flags this context was created with.
callFlag smartcontract.CallFlag
// CheckReturn specifies if amount of return values needs to be checked.
CheckReturn CheckReturnState
}
// CheckReturnState represents possible states of stack after opcode.RET was processed.
type CheckReturnState byte
const (
// NoCheck performs no return values check.
NoCheck CheckReturnState = 0
// EnsureIsEmpty checks that stack is empty and panics if not.
EnsureIsEmpty CheckReturnState = 1
// EnsureNotEmpty checks that stack contains not more than 1 element and panics if not.
// It pushes stackitem.Null on stack in case if there's no elements.
EnsureNotEmpty CheckReturnState = 2
)
var errNoInstParam = errors.New("failed to read instruction parameter")
// NewContext returns a new Context object.
func NewContext(b []byte) *Context {
return &Context{
prog: b,
breakPoints: []int{},
}
}
// Estack returns the evaluation stack of c.
func (c *Context) Estack() *Stack {
return c.estack
}
// NextIP returns next instruction pointer.
func (c *Context) NextIP() int {
return c.nextip
}
// 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) {
var err error
c.ip = c.nextip
if c.ip >= len(c.prog) {
return opcode.RET, nil, nil
}
var instrbyte = c.prog[c.ip]
instr := opcode.Opcode(instrbyte)
c.nextip++
var numtoread int
switch instr {
case opcode.PUSHDATA1:
if c.nextip >= len(c.prog) {
err = errNoInstParam
} else {
numtoread = int(c.prog[c.nextip])
c.nextip++
}
case opcode.PUSHDATA2:
if c.nextip+1 >= len(c.prog) {
err = errNoInstParam
} else {
numtoread = int(binary.LittleEndian.Uint16(c.prog[c.nextip : c.nextip+2]))
c.nextip += 2
}
case opcode.PUSHDATA4:
if c.nextip+3 >= len(c.prog) {
err = errNoInstParam
} else {
var n = binary.LittleEndian.Uint32(c.prog[c.nextip : c.nextip+4])
if n > stackitem.MaxSize {
return instr, nil, errors.New("parameter is too big")
}
numtoread = int(n)
c.nextip += 4
}
case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.JMPEQ, opcode.JMPNE,
opcode.JMPGT, opcode.JMPGE, opcode.JMPLT, opcode.JMPLE,
opcode.CALL, opcode.ISTYPE, opcode.CONVERT, opcode.NEWARRAYT,
opcode.ENDTRY,
opcode.INITSSLOT, opcode.LDSFLD, opcode.STSFLD, opcode.LDARG, opcode.STARG, opcode.LDLOC, opcode.STLOC:
numtoread = 1
case opcode.INITSLOT, opcode.TRY:
numtoread = 2
case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL, opcode.JMPEQL, opcode.JMPNEL,
opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLTL, opcode.JMPLEL,
opcode.ENDTRYL,
opcode.CALLL, opcode.SYSCALL, opcode.PUSHA:
numtoread = 4
case opcode.TRYL:
numtoread = 8
default:
if instr <= opcode.PUSHINT256 {
numtoread = 1 << instr
} else {
// No parameters, can just return.
return instr, nil, nil
}
}
if c.nextip+numtoread-1 >= len(c.prog) {
err = errNoInstParam
}
if err != nil {
return instr, nil, err
}
parameter := make([]byte, numtoread)
copy(parameter, c.prog[c.nextip:c.nextip+numtoread])
c.nextip += numtoread
return instr, parameter, nil
}
// IP returns current instruction offset in the context script.
func (c *Context) IP() int {
return c.ip
}
// 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
}
// GetCallFlags returns calling flags context was created with.
func (c *Context) GetCallFlags() smartcontract.CallFlag {
return c.callFlag
}
// 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.Item interface.
func (c *Context) Value() interface{} {
return c
}
// Dup implements stackitem.Item interface.
func (c *Context) Dup() stackitem.Item {
return c
}
// TryBool implements stackitem.Item interface.
func (c *Context) TryBool() (bool, error) { panic("can't convert Context to Bool") }
// TryBytes implements stackitem.Item interface.
func (c *Context) TryBytes() ([]byte, error) {
return nil, errors.New("can't convert Context to ByteArray")
}
// TryInteger implements stackitem.Item interface.
func (c *Context) TryInteger() (*big.Int, error) {
return nil, errors.New("can't convert Context to Integer")
}
// Type implements stackitem.Item interface.
func (c *Context) Type() stackitem.Type { panic("Context cannot appear on evaluation stack") }
// Convert implements stackitem.Item interface.
func (c *Context) Convert(_ stackitem.Type) (stackitem.Item, error) {
panic("Context cannot be converted to anything")
}
// Equals implements stackitem.Item interface.
func (c *Context) Equals(s stackitem.Item) bool {
return c == s
}
func (c *Context) atBreakPoint() bool {
for _, n := range c.breakPoints {
if n == c.nextip {
return true
}
}
return false
}
func (c *Context) String() string {
return "execution context"
}
// getContextScriptHash returns script hash of the invocation stack element
// number n.
func (v *VM) getContextScriptHash(n int) util.Uint160 {
element := v.Istack().Peek(n)
if element == nil {
return util.Uint160{}
}
ctxIface := element.Value()
ctx := ctxIface.(*Context)
return ctx.ScriptHash()
}
// PushContextScriptHash pushes to evaluation stack the script hash of the
// invocation stack element number n.
func (v *VM) PushContextScriptHash(n int) error {
h := v.getContextScriptHash(n)
v.Estack().PushVal(h.BytesBE())
return nil
}