2018-03-30 16:15:06 +00:00
|
|
|
package vm
|
|
|
|
|
2018-04-02 15:04:42 +00:00
|
|
|
import (
|
2019-12-13 16:31:13 +00:00
|
|
|
"encoding/binary"
|
2021-09-08 14:27:11 +00:00
|
|
|
"encoding/json"
|
2019-10-03 13:54:14 +00:00
|
|
|
"errors"
|
2021-02-09 14:03:06 +00:00
|
|
|
"fmt"
|
2020-03-19 15:21:56 +00:00
|
|
|
"math/big"
|
2019-10-03 13:54:14 +00:00
|
|
|
|
2020-03-03 14:21:42 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
2020-12-29 10:45:49 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
2021-01-19 08:23:39 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
2020-03-03 14:21:42 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
2020-06-03 12:55:06 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
2018-04-02 15:04:42 +00:00
|
|
|
)
|
2018-03-30 16:15:06 +00:00
|
|
|
|
2019-10-22 14:56:03 +00:00
|
|
|
// Context represents the current execution context of the VM.
|
2018-03-30 16:15:06 +00:00
|
|
|
type Context struct {
|
|
|
|
// Instruction pointer.
|
|
|
|
ip int
|
|
|
|
|
2019-10-03 13:54:14 +00:00
|
|
|
// The next instruction pointer.
|
|
|
|
nextip int
|
|
|
|
|
2018-03-30 16:15:06 +00:00
|
|
|
// The raw program script.
|
|
|
|
prog []byte
|
|
|
|
|
2019-10-22 14:56:03 +00:00
|
|
|
// Breakpoints.
|
2018-03-30 16:15:06 +00:00
|
|
|
breakPoints []int
|
2019-10-25 14:25:46 +00:00
|
|
|
|
|
|
|
// Evaluation stack pointer.
|
|
|
|
estack *Stack
|
|
|
|
|
2021-11-30 12:03:01 +00:00
|
|
|
static *slot
|
|
|
|
local slot
|
|
|
|
arguments slot
|
2020-05-07 08:54:35 +00:00
|
|
|
|
2021-08-20 20:11:59 +00:00
|
|
|
// Exception context stack.
|
|
|
|
tryStack Stack
|
2020-07-22 09:05:46 +00:00
|
|
|
|
2019-12-13 14:05:03 +00:00
|
|
|
// Script hash of the prog.
|
|
|
|
scriptHash util.Uint160
|
2020-06-10 12:51:28 +00:00
|
|
|
|
2020-06-23 18:39:26 +00:00
|
|
|
// Caller's contract script hash.
|
|
|
|
callingScriptHash util.Uint160
|
|
|
|
|
2020-06-10 12:51:28 +00:00
|
|
|
// Call flags this context was created with.
|
2020-12-29 10:45:49 +00:00
|
|
|
callFlag callflag.CallFlag
|
2020-08-10 08:52:32 +00:00
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// retCount specifies the number of return values.
|
2021-11-19 16:36:42 +00:00
|
|
|
retCount int
|
2022-04-20 18:30:09 +00:00
|
|
|
// NEF represents a NEF file for the current contract.
|
2021-01-19 08:23:39 +00:00
|
|
|
NEF *nef.File
|
2021-11-19 20:50:12 +00:00
|
|
|
// invTree is an invocation tree (or branch of it) for this context.
|
|
|
|
invTree *InvocationTree
|
2022-05-23 08:35:01 +00:00
|
|
|
// onUnload is a callback that should be called after current context unloading
|
|
|
|
// if no exception occurs.
|
|
|
|
onUnload ContextUnloadCallback
|
2018-03-30 16:15:06 +00:00
|
|
|
}
|
|
|
|
|
2022-05-23 08:35:01 +00:00
|
|
|
// ContextUnloadCallback is a callback method used on context unloading from istack.
|
2022-05-25 07:00:02 +00:00
|
|
|
type ContextUnloadCallback func(commit bool) error
|
2022-05-23 08:35:01 +00:00
|
|
|
|
2019-12-13 16:31:13 +00:00
|
|
|
var errNoInstParam = errors.New("failed to read instruction parameter")
|
|
|
|
|
2019-10-22 14:56:03 +00:00
|
|
|
// NewContext returns a new Context object.
|
2018-03-30 16:15:06 +00:00
|
|
|
func NewContext(b []byte) *Context {
|
2022-05-25 07:00:02 +00:00
|
|
|
return NewContextWithParams(b, -1, 0)
|
2020-12-29 10:44:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewContextWithParams creates new Context objects using script, parameter count,
|
|
|
|
// return value count and initial position in script.
|
2022-05-25 07:00:02 +00:00
|
|
|
func NewContextWithParams(b []byte, rvcount int, pos int) *Context {
|
2018-03-30 16:15:06 +00:00
|
|
|
return &Context{
|
2022-05-25 07:00:02 +00:00
|
|
|
prog: b,
|
|
|
|
retCount: rvcount,
|
|
|
|
nextip: pos,
|
2018-03-30 16:15:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-19 15:02:21 +00:00
|
|
|
// Estack returns the evaluation stack of c.
|
|
|
|
func (c *Context) Estack() *Stack {
|
|
|
|
return c.estack
|
|
|
|
}
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// NextIP returns the next instruction pointer.
|
2020-02-03 09:10:07 +00:00
|
|
|
func (c *Context) NextIP() int {
|
|
|
|
return c.nextip
|
|
|
|
}
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// Jump unconditionally moves the next instruction pointer to the specified location.
|
2021-11-19 13:46:29 +00:00
|
|
|
func (c *Context) Jump(pos int) {
|
2022-06-06 08:52:26 +00:00
|
|
|
if pos < 0 || pos >= len(c.prog) {
|
2022-05-11 10:47:26 +00:00
|
|
|
panic("instruction offset is out of range")
|
|
|
|
}
|
2021-11-19 13:46:29 +00:00
|
|
|
c.nextip = pos
|
|
|
|
}
|
|
|
|
|
2021-08-10 12:31:16 +00:00
|
|
|
// Next returns the next instruction to execute with its parameter if any.
|
2022-04-20 18:30:09 +00:00
|
|
|
// The parameter is not copied and shouldn't be written to. After its invocation,
|
|
|
|
// the instruction pointer points to the instruction returned.
|
2019-12-03 14:05:06 +00:00
|
|
|
func (c *Context) Next() (opcode.Opcode, []byte, error) {
|
2019-12-13 16:31:13 +00:00
|
|
|
var err error
|
|
|
|
|
2019-10-03 13:54:14 +00:00
|
|
|
c.ip = c.nextip
|
2018-04-04 19:41:19 +00:00
|
|
|
if c.ip >= len(c.prog) {
|
2019-12-03 14:05:06 +00:00
|
|
|
return opcode.RET, nil, nil
|
2019-10-03 13:54:14 +00:00
|
|
|
}
|
|
|
|
|
2019-12-13 16:31:13 +00:00
|
|
|
var instrbyte = c.prog[c.ip]
|
2019-12-03 14:05:06 +00:00
|
|
|
instr := opcode.Opcode(instrbyte)
|
2021-02-09 14:03:06 +00:00
|
|
|
if !opcode.IsValid(instr) {
|
|
|
|
return instr, nil, fmt.Errorf("incorrect opcode %s", instr.String())
|
|
|
|
}
|
2019-10-03 13:54:14 +00:00
|
|
|
c.nextip++
|
|
|
|
|
|
|
|
var numtoread int
|
|
|
|
switch instr {
|
2020-04-15 14:40:05 +00:00
|
|
|
case opcode.PUSHDATA1:
|
2019-12-13 16:31:13 +00:00
|
|
|
if c.nextip >= len(c.prog) {
|
|
|
|
err = errNoInstParam
|
|
|
|
} else {
|
|
|
|
numtoread = int(c.prog[c.nextip])
|
|
|
|
c.nextip++
|
|
|
|
}
|
2019-12-03 14:05:06 +00:00
|
|
|
case opcode.PUSHDATA2:
|
2019-12-13 16:31:13 +00:00
|
|
|
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
|
|
|
|
}
|
2019-12-03 14:05:06 +00:00
|
|
|
case opcode.PUSHDATA4:
|
2019-12-13 16:31:13 +00:00
|
|
|
if c.nextip+3 >= len(c.prog) {
|
|
|
|
err = errNoInstParam
|
|
|
|
} else {
|
|
|
|
var n = binary.LittleEndian.Uint32(c.prog[c.nextip : c.nextip+4])
|
2020-06-11 13:31:31 +00:00
|
|
|
if n > stackitem.MaxSize {
|
2019-12-13 16:31:13 +00:00
|
|
|
return instr, nil, errors.New("parameter is too big")
|
|
|
|
}
|
|
|
|
numtoread = int(n)
|
|
|
|
c.nextip += 4
|
2019-10-17 14:10:00 +00:00
|
|
|
}
|
2020-04-23 08:56:36 +00:00
|
|
|
case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.JMPEQ, opcode.JMPNE,
|
|
|
|
opcode.JMPGT, opcode.JMPGE, opcode.JMPLT, opcode.JMPLE,
|
2020-05-07 08:54:35 +00:00
|
|
|
opcode.CALL, opcode.ISTYPE, opcode.CONVERT, opcode.NEWARRAYT,
|
2020-07-22 09:05:46 +00:00
|
|
|
opcode.ENDTRY,
|
2020-05-07 08:54:35 +00:00
|
|
|
opcode.INITSSLOT, opcode.LDSFLD, opcode.STSFLD, opcode.LDARG, opcode.STARG, opcode.LDLOC, opcode.STLOC:
|
2020-04-23 08:56:36 +00:00
|
|
|
numtoread = 1
|
2020-12-29 09:04:53 +00:00
|
|
|
case opcode.INITSLOT, opcode.TRY, opcode.CALLT:
|
2020-05-07 08:54:35 +00:00
|
|
|
numtoread = 2
|
2020-04-23 08:56:36 +00:00
|
|
|
case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL, opcode.JMPEQL, opcode.JMPNEL,
|
|
|
|
opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLTL, opcode.JMPLEL,
|
2020-07-22 09:05:46 +00:00
|
|
|
opcode.ENDTRYL,
|
2020-05-06 13:55:30 +00:00
|
|
|
opcode.CALLL, opcode.SYSCALL, opcode.PUSHA:
|
2019-10-25 14:25:46 +00:00
|
|
|
numtoread = 4
|
2020-07-22 09:05:46 +00:00
|
|
|
case opcode.TRYL:
|
|
|
|
numtoread = 8
|
2019-10-03 13:54:14 +00:00
|
|
|
default:
|
2020-04-21 13:45:48 +00:00
|
|
|
if instr <= opcode.PUSHINT256 {
|
|
|
|
numtoread = 1 << instr
|
2019-10-03 13:54:14 +00:00
|
|
|
} else {
|
|
|
|
// No parameters, can just return.
|
|
|
|
return instr, nil, nil
|
|
|
|
}
|
|
|
|
}
|
2019-12-13 16:31:13 +00:00
|
|
|
if c.nextip+numtoread-1 >= len(c.prog) {
|
|
|
|
err = errNoInstParam
|
2018-04-04 19:41:19 +00:00
|
|
|
}
|
2019-12-13 16:31:13 +00:00
|
|
|
if err != nil {
|
|
|
|
return instr, nil, err
|
|
|
|
}
|
2021-08-10 12:31:16 +00:00
|
|
|
parameter := c.prog[c.nextip : c.nextip+numtoread]
|
2019-10-03 13:54:14 +00:00
|
|
|
c.nextip += numtoread
|
|
|
|
return instr, parameter, nil
|
2018-03-30 16:15:06 +00:00
|
|
|
}
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// IP returns the current instruction offset in the context script.
|
2018-03-30 16:15:06 +00:00
|
|
|
func (c *Context) IP() int {
|
2020-08-19 09:37:31 +00:00
|
|
|
return c.ip
|
2018-03-30 16:15:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// LenInstr returns the number of instructions loaded.
|
|
|
|
func (c *Context) LenInstr() int {
|
|
|
|
return len(c.prog)
|
|
|
|
}
|
|
|
|
|
|
|
|
// CurrInstr returns the current instruction and opcode.
|
2019-12-03 14:05:06 +00:00
|
|
|
func (c *Context) CurrInstr() (int, opcode.Opcode) {
|
|
|
|
return c.ip, opcode.Opcode(c.prog[c.ip])
|
2018-03-30 16:15:06 +00:00
|
|
|
}
|
|
|
|
|
2020-12-01 13:53:38 +00:00
|
|
|
// NextInstr returns the next instruction and opcode.
|
|
|
|
func (c *Context) NextInstr() (int, opcode.Opcode) {
|
|
|
|
op := opcode.RET
|
|
|
|
if c.nextip < len(c.prog) {
|
|
|
|
op = opcode.Opcode(c.prog[c.nextip])
|
|
|
|
}
|
|
|
|
return c.nextip, op
|
|
|
|
}
|
|
|
|
|
2018-03-30 16:15:06 +00:00
|
|
|
// Copy returns an new exact copy of c.
|
|
|
|
func (c *Context) Copy() *Context {
|
2019-10-03 13:54:14 +00:00
|
|
|
ctx := new(Context)
|
|
|
|
*ctx = *c
|
|
|
|
return ctx
|
2018-03-30 16:15:06 +00:00
|
|
|
}
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// GetCallFlags returns the calling flags which the context was created with.
|
2020-12-29 10:45:49 +00:00
|
|
|
func (c *Context) GetCallFlags() callflag.CallFlag {
|
2020-06-10 14:21:26 +00:00
|
|
|
return c.callFlag
|
|
|
|
}
|
|
|
|
|
2018-03-30 16:15:06 +00:00
|
|
|
// Program returns the loaded program.
|
|
|
|
func (c *Context) Program() []byte {
|
|
|
|
return c.prog
|
|
|
|
}
|
|
|
|
|
2019-12-13 14:05:03 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// Value implements the stackitem.Item interface.
|
2018-03-30 16:15:06 +00:00
|
|
|
func (c *Context) Value() interface{} {
|
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// Dup implements the stackitem.Item interface.
|
2020-06-03 12:55:06 +00:00
|
|
|
func (c *Context) Dup() stackitem.Item {
|
2019-12-17 13:38:42 +00:00
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// TryBool implements the stackitem.Item interface.
|
2020-08-21 17:55:20 +00:00
|
|
|
func (c *Context) TryBool() (bool, error) { panic("can't convert Context to Bool") }
|
2020-04-28 08:23:58 +00:00
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// TryBytes implements the stackitem.Item interface.
|
2020-03-11 13:04:28 +00:00
|
|
|
func (c *Context) TryBytes() ([]byte, error) {
|
|
|
|
return nil, errors.New("can't convert Context to ByteArray")
|
|
|
|
}
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// TryInteger implements the stackitem.Item interface.
|
2020-03-19 15:21:56 +00:00
|
|
|
func (c *Context) TryInteger() (*big.Int, error) {
|
|
|
|
return nil, errors.New("can't convert Context to Integer")
|
|
|
|
}
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// Type implements the stackitem.Item interface.
|
2020-06-03 12:55:06 +00:00
|
|
|
func (c *Context) Type() stackitem.Type { panic("Context cannot appear on evaluation stack") }
|
2020-04-24 10:46:46 +00:00
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// Convert implements the stackitem.Item interface.
|
2020-06-03 12:55:06 +00:00
|
|
|
func (c *Context) Convert(_ stackitem.Type) (stackitem.Item, error) {
|
2020-04-28 08:24:02 +00:00
|
|
|
panic("Context cannot be converted to anything")
|
|
|
|
}
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// Equals implements the stackitem.Item interface.
|
2020-06-03 12:55:06 +00:00
|
|
|
func (c *Context) Equals(s stackitem.Item) bool {
|
2020-03-11 13:44:10 +00:00
|
|
|
return c == s
|
|
|
|
}
|
|
|
|
|
2018-03-30 16:15:06 +00:00
|
|
|
func (c *Context) atBreakPoint() bool {
|
|
|
|
for _, n := range c.breakPoints {
|
2020-08-18 08:13:09 +00:00
|
|
|
if n == c.nextip {
|
2018-03-30 16:15:06 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Context) String() string {
|
|
|
|
return "execution context"
|
|
|
|
}
|
2020-04-13 12:37:44 +00:00
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// IsDeployed returns whether this context contains a deployed contract.
|
2020-11-26 20:02:00 +00:00
|
|
|
func (c *Context) IsDeployed() bool {
|
2021-01-19 08:23:39 +00:00
|
|
|
return c.NEF != nil
|
2020-11-26 20:02:00 +00:00
|
|
|
}
|
|
|
|
|
2021-09-08 14:27:11 +00:00
|
|
|
// DumpStaticSlot returns json formatted representation of the given slot.
|
|
|
|
func (c *Context) DumpStaticSlot() string {
|
|
|
|
return dumpSlot(c.static)
|
|
|
|
}
|
|
|
|
|
|
|
|
// DumpLocalSlot returns json formatted representation of the given slot.
|
|
|
|
func (c *Context) DumpLocalSlot() string {
|
2021-11-30 12:03:01 +00:00
|
|
|
return dumpSlot(&c.local)
|
2021-09-08 14:27:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// DumpArgumentsSlot returns json formatted representation of the given slot.
|
|
|
|
func (c *Context) DumpArgumentsSlot() string {
|
2021-11-30 12:03:01 +00:00
|
|
|
return dumpSlot(&c.arguments)
|
2021-09-08 14:27:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// dumpSlot returns json formatted representation of the given slot.
|
2021-11-30 12:03:01 +00:00
|
|
|
func dumpSlot(s *slot) string {
|
|
|
|
if s == nil || *s == nil {
|
|
|
|
return "[]"
|
|
|
|
}
|
2021-09-08 14:27:11 +00:00
|
|
|
b, _ := json.MarshalIndent(s, "", " ")
|
|
|
|
return string(b)
|
|
|
|
}
|
|
|
|
|
2020-05-04 08:41:41 +00:00
|
|
|
// getContextScriptHash returns script hash of the invocation stack element
|
2020-04-13 12:37:44 +00:00
|
|
|
// number n.
|
2020-05-04 08:41:41 +00:00
|
|
|
func (v *VM) getContextScriptHash(n int) util.Uint160 {
|
2021-08-21 16:09:44 +00:00
|
|
|
istack := v.Istack()
|
|
|
|
if istack.Len() <= n {
|
2020-04-30 16:19:31 +00:00
|
|
|
return util.Uint160{}
|
|
|
|
}
|
2021-08-21 16:09:44 +00:00
|
|
|
element := istack.Peek(n)
|
2021-08-28 19:31:08 +00:00
|
|
|
ctx := element.value.(*Context)
|
2020-04-13 12:37:44 +00:00
|
|
|
return ctx.ScriptHash()
|
|
|
|
}
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// PushContextScriptHash pushes the script hash of the
|
|
|
|
// invocation stack element number n to the evaluation stack.
|
2020-04-13 12:37:44 +00:00
|
|
|
func (v *VM) PushContextScriptHash(n int) error {
|
2020-05-04 08:41:41 +00:00
|
|
|
h := v.getContextScriptHash(n)
|
2021-08-28 19:35:43 +00:00
|
|
|
v.Estack().PushItem(stackitem.NewByteArray(h.BytesBE()))
|
2020-04-13 12:37:44 +00:00
|
|
|
return nil
|
|
|
|
}
|
2022-05-25 07:00:02 +00:00
|
|
|
|
|
|
|
func (c *Context) HasTryBlock() bool {
|
|
|
|
for i := 0; i < c.tryStack.Len(); i++ {
|
|
|
|
eCtx := c.tryStack.Peek(i).Value().(*exceptionHandlingContext)
|
|
|
|
if eCtx.State == eTry {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|