forked from TrueCloudLab/neoneo-go
a7457d08a1
This solves two problems: * adds support for shortened SYSCALL form that uses IDs (similar to #434, but for NEO 2.0, supporting both forms), which is important for compatibility with C# node and mainnet chain that uses it from some height * reworks interop plugging to use callbacks rather than appending to the map, these map mangling functions are clearly visible in the VM profiling statistics and we want spawning a VM to be fast, so it makes sense optimizing it. This change moves most of the work to the init() phase making VM setup cheaper. Caveats: * InteropNameToID accepts `[]byte` because that's the thing we have in SYSCALL processing and that's the most often usecase for it, it leads to some conversions in other places but that's acceptable because those are either tests or init() * three getInterop functions are: `getDefaultVMInterop`, `getSystemInterop` and `getNeoInterop` Our 100K (1.4M->1.5M) block import time improves by ~4% with this change.
1430 lines
32 KiB
Go
1430 lines
32 KiB
Go
package vm
|
|
|
|
import (
|
|
"crypto/sha1"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"math/big"
|
|
"os"
|
|
"reflect"
|
|
"text/tabwriter"
|
|
"unicode/utf8"
|
|
|
|
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
|
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
|
"github.com/CityOfZion/neo-go/pkg/util"
|
|
"github.com/CityOfZion/neo-go/pkg/vm/opcode"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type errorAtInstruct struct {
|
|
ip int
|
|
op opcode.Opcode
|
|
err interface{}
|
|
}
|
|
|
|
func (e *errorAtInstruct) Error() string {
|
|
return fmt.Sprintf("error encountered at instruction %d (%s): %s", e.ip, e.op, e.err)
|
|
}
|
|
|
|
func newError(ip int, op opcode.Opcode, err interface{}) *errorAtInstruct {
|
|
return &errorAtInstruct{ip: ip, op: op, err: err}
|
|
}
|
|
|
|
// StateMessage is a vm state message which could be used as additional info for example by cli.
|
|
type StateMessage string
|
|
|
|
const (
|
|
// MaxArraySize is the maximum array size allowed in the VM.
|
|
MaxArraySize = 1024
|
|
|
|
// MaxItemSize is the maximum item size allowed in the VM.
|
|
MaxItemSize = 1024 * 1024
|
|
|
|
// MaxInvocationStackSize is the maximum size of an invocation stack.
|
|
MaxInvocationStackSize = 1024
|
|
|
|
// MaxBigIntegerSizeBits is the maximum size of BigInt item in bits.
|
|
MaxBigIntegerSizeBits = 32 * 8
|
|
|
|
// MaxStackSize is the maximum number of items allowed to be
|
|
// on all stacks at once.
|
|
MaxStackSize = 2 * 1024
|
|
|
|
maxSHLArg = MaxBigIntegerSizeBits
|
|
minSHLArg = -MaxBigIntegerSizeBits
|
|
)
|
|
|
|
// VM represents the virtual machine.
|
|
type VM struct {
|
|
state State
|
|
|
|
// callbacks to get interops.
|
|
getInterop []InteropGetterFunc
|
|
|
|
// callback to get scripts.
|
|
getScript func(util.Uint160) []byte
|
|
|
|
istack *Stack // invocation stack.
|
|
estack *Stack // execution stack.
|
|
astack *Stack // alt stack.
|
|
|
|
// Hash to verify in CHECKSIG/CHECKMULTISIG.
|
|
checkhash []byte
|
|
|
|
itemCount map[StackItem]int
|
|
size int
|
|
|
|
// Public keys cache.
|
|
keys map[string]*keys.PublicKey
|
|
}
|
|
|
|
// New returns a new VM object ready to load .avm bytecode scripts.
|
|
func New() *VM {
|
|
vm := &VM{
|
|
getInterop: make([]InteropGetterFunc, 0, 3), // 3 functions is typical for our default usage.
|
|
getScript: nil,
|
|
state: haltState,
|
|
istack: NewStack("invocation"),
|
|
|
|
itemCount: make(map[StackItem]int),
|
|
keys: make(map[string]*keys.PublicKey),
|
|
}
|
|
|
|
vm.estack = vm.newItemStack("evaluation")
|
|
vm.astack = vm.newItemStack("alt")
|
|
|
|
vm.RegisterInteropGetter(getDefaultVMInterop)
|
|
return vm
|
|
}
|
|
|
|
func (v *VM) newItemStack(n string) *Stack {
|
|
s := NewStack(n)
|
|
s.size = &v.size
|
|
s.itemCount = v.itemCount
|
|
|
|
return s
|
|
}
|
|
|
|
// RegisterInteropGetter registers the given InteropGetterFunc into VM. There
|
|
// can be many interop getters and they're probed in LIFO order wrt their
|
|
// registration time.
|
|
func (v *VM) RegisterInteropGetter(f InteropGetterFunc) {
|
|
v.getInterop = append(v.getInterop, f)
|
|
}
|
|
|
|
// Estack returns the evaluation stack so interop hooks can utilize this.
|
|
func (v *VM) Estack() *Stack {
|
|
return v.estack
|
|
}
|
|
|
|
// Astack returns the alt stack so interop hooks can utilize this.
|
|
func (v *VM) Astack() *Stack {
|
|
return v.astack
|
|
}
|
|
|
|
// Istack returns the invocation stack so interop hooks can utilize this.
|
|
func (v *VM) Istack() *Stack {
|
|
return v.istack
|
|
}
|
|
|
|
// SetPublicKeys sets internal key cache to the specified value (note
|
|
// that it doesn't copy them).
|
|
func (v *VM) SetPublicKeys(keys map[string]*keys.PublicKey) {
|
|
v.keys = keys
|
|
}
|
|
|
|
// GetPublicKeys returns internal key cache (note that it doesn't copy it).
|
|
func (v *VM) GetPublicKeys() map[string]*keys.PublicKey {
|
|
return v.keys
|
|
}
|
|
|
|
// LoadArgs loads in the arguments used in the Mian entry point.
|
|
func (v *VM) LoadArgs(method []byte, args []StackItem) {
|
|
if len(args) > 0 {
|
|
v.estack.PushVal(args)
|
|
}
|
|
if method != nil {
|
|
v.estack.PushVal(method)
|
|
}
|
|
}
|
|
|
|
// PrintOps prints the opcodes of the current loaded program to stdout.
|
|
func (v *VM) PrintOps() {
|
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 4, ' ', 0)
|
|
fmt.Fprintln(w, "INDEX\tOPCODE\tPARAMETER\t")
|
|
realctx := v.Context()
|
|
ctx := realctx.Copy()
|
|
ctx.ip = 0
|
|
ctx.nextip = 0
|
|
for {
|
|
cursor := ""
|
|
instr, parameter, err := ctx.Next()
|
|
if ctx.ip == realctx.ip {
|
|
cursor = "<<"
|
|
}
|
|
if err != nil {
|
|
fmt.Fprintf(w, "%d\t%s\tERROR: %s\t%s\n", ctx.ip, instr, err, cursor)
|
|
break
|
|
}
|
|
var desc = ""
|
|
if parameter != nil {
|
|
switch instr {
|
|
case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.CALL:
|
|
offset := int16(binary.LittleEndian.Uint16(parameter))
|
|
desc = fmt.Sprintf("%d (%d/%x)", ctx.ip+int(offset), offset, parameter)
|
|
case opcode.SYSCALL:
|
|
desc = fmt.Sprintf("%q", parameter)
|
|
case opcode.APPCALL, opcode.TAILCALL:
|
|
desc = fmt.Sprintf("%x", parameter)
|
|
default:
|
|
if utf8.Valid(parameter) {
|
|
desc = fmt.Sprintf("%x (%q)", parameter, parameter)
|
|
} else {
|
|
desc = fmt.Sprintf("%x", parameter)
|
|
}
|
|
}
|
|
}
|
|
|
|
fmt.Fprintf(w, "%d\t%s\t%s\t%s\n", ctx.ip, instr, desc, cursor)
|
|
if ctx.nextip >= len(ctx.prog) {
|
|
break
|
|
}
|
|
}
|
|
w.Flush()
|
|
}
|
|
|
|
// AddBreakPoint adds a breakpoint to the current context.
|
|
func (v *VM) AddBreakPoint(n int) {
|
|
ctx := v.Context()
|
|
ctx.breakPoints = append(ctx.breakPoints, n)
|
|
}
|
|
|
|
// AddBreakPointRel adds a breakpoint relative to the current
|
|
// instruction pointer.
|
|
func (v *VM) AddBreakPointRel(n int) {
|
|
ctx := v.Context()
|
|
v.AddBreakPoint(ctx.ip + n)
|
|
}
|
|
|
|
// LoadFile loads a program from the given path, ready to execute it.
|
|
func (v *VM) LoadFile(path string) error {
|
|
b, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
v.Load(b)
|
|
return nil
|
|
}
|
|
|
|
// Load initializes the VM with the program given.
|
|
func (v *VM) Load(prog []byte) {
|
|
// Clear all stacks and state, it could be a reload.
|
|
v.istack.Clear()
|
|
v.estack.Clear()
|
|
v.astack.Clear()
|
|
v.state = noneState
|
|
v.LoadScript(prog)
|
|
}
|
|
|
|
// LoadScript loads a script from the internal script table. It
|
|
// will immediately push a new context created from this script to
|
|
// the invocation stack and starts executing it.
|
|
func (v *VM) LoadScript(b []byte) {
|
|
ctx := NewContext(b)
|
|
ctx.estack = v.estack
|
|
ctx.astack = v.astack
|
|
v.istack.PushVal(ctx)
|
|
}
|
|
|
|
// Context returns the current executed context. Nil if there is no context,
|
|
// which implies no program is loaded.
|
|
func (v *VM) Context() *Context {
|
|
if v.istack.Len() == 0 {
|
|
return nil
|
|
}
|
|
return v.istack.Peek(0).Value().(*Context)
|
|
}
|
|
|
|
// PopResult is used to pop the first item of the evaluation stack. This allows
|
|
// us to test compiler and vm in a bi-directional way.
|
|
func (v *VM) PopResult() interface{} {
|
|
e := v.estack.Pop()
|
|
if e != nil {
|
|
return e.Value()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Stack returns json formatted representation of the given stack.
|
|
func (v *VM) Stack(n string) string {
|
|
var s *Stack
|
|
if n == "astack" {
|
|
s = v.astack
|
|
}
|
|
if n == "istack" {
|
|
s = v.istack
|
|
}
|
|
if n == "estack" {
|
|
s = v.estack
|
|
}
|
|
return buildStackOutput(s)
|
|
}
|
|
|
|
// State returns string representation of the state for the VM.
|
|
func (v *VM) State() string {
|
|
return v.state.String()
|
|
}
|
|
|
|
// Ready returns true if the VM ready to execute the loaded program.
|
|
// Will return false if no program is loaded.
|
|
func (v *VM) Ready() bool {
|
|
return v.istack.Len() > 0
|
|
}
|
|
|
|
// Run starts the execution of the loaded program.
|
|
func (v *VM) Run() error {
|
|
if !v.Ready() {
|
|
v.state = faultState
|
|
return errors.New("no program loaded")
|
|
}
|
|
|
|
if v.state.HasFlag(faultState) {
|
|
// VM already ran something and failed, in general its state is
|
|
// undefined in this case so we can't run anything.
|
|
return errors.New("VM has failed")
|
|
}
|
|
// haltState (the default) or breakState are safe to continue.
|
|
v.state = noneState
|
|
for {
|
|
// check for breakpoint before executing the next instruction
|
|
ctx := v.Context()
|
|
if ctx != nil && ctx.atBreakPoint() {
|
|
v.state |= breakState
|
|
}
|
|
switch {
|
|
case v.state.HasFlag(faultState):
|
|
// Should be caught and reported already by the v.Step(),
|
|
// but we're checking here anyway just in case.
|
|
return errors.New("VM has failed")
|
|
case v.state.HasFlag(haltState), v.state.HasFlag(breakState):
|
|
// Normal exit from this loop.
|
|
return nil
|
|
case v.state == noneState:
|
|
if err := v.Step(); err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
v.state = faultState
|
|
return errors.New("unknown state")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Step 1 instruction in the program.
|
|
func (v *VM) Step() error {
|
|
ctx := v.Context()
|
|
op, param, err := ctx.Next()
|
|
if err != nil {
|
|
v.state = faultState
|
|
return newError(ctx.ip, op, err)
|
|
}
|
|
return v.execute(ctx, op, param)
|
|
}
|
|
|
|
// StepInto behaves the same as “step over” in case if the line does not contain a function. Otherwise
|
|
// the debugger will enter the called function and continue line-by-line debugging there.
|
|
func (v *VM) StepInto() error {
|
|
ctx := v.Context()
|
|
|
|
if ctx == nil {
|
|
v.state |= haltState
|
|
}
|
|
|
|
if v.HasStopped() {
|
|
return nil
|
|
}
|
|
|
|
if ctx != nil && ctx.prog != nil {
|
|
op, param, err := ctx.Next()
|
|
if err != nil {
|
|
v.state = faultState
|
|
return newError(ctx.ip, op, err)
|
|
}
|
|
vErr := v.execute(ctx, op, param)
|
|
if vErr != nil {
|
|
return vErr
|
|
}
|
|
}
|
|
|
|
cctx := v.Context()
|
|
if cctx != nil && cctx.atBreakPoint() {
|
|
v.state = breakState
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// StepOut takes the debugger to the line where the current function was called.
|
|
func (v *VM) StepOut() error {
|
|
var err error
|
|
if v.state == breakState {
|
|
v.state = noneState
|
|
} else {
|
|
v.state = breakState
|
|
}
|
|
|
|
expSize := v.istack.len
|
|
for v.state == noneState && v.istack.len >= expSize {
|
|
err = v.StepInto()
|
|
}
|
|
return err
|
|
}
|
|
|
|
// StepOver takes the debugger to the line that will step over a given line.
|
|
// If the line contains a function the function will be executed and the result returned without debugging each line.
|
|
func (v *VM) StepOver() error {
|
|
var err error
|
|
if v.HasStopped() {
|
|
return err
|
|
}
|
|
|
|
if v.state == breakState {
|
|
v.state = noneState
|
|
} else {
|
|
v.state = breakState
|
|
}
|
|
|
|
expSize := v.istack.len
|
|
for {
|
|
err = v.StepInto()
|
|
if !(v.state == noneState && v.istack.len > expSize) {
|
|
break
|
|
}
|
|
}
|
|
|
|
if v.state == noneState {
|
|
v.state = breakState
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// HasFailed returns whether VM is in the failed state now. Usually used to
|
|
// check status after Run.
|
|
func (v *VM) HasFailed() bool {
|
|
return v.state.HasFlag(faultState)
|
|
}
|
|
|
|
// HasStopped returns whether VM is in Halt or Failed state.
|
|
func (v *VM) HasStopped() bool {
|
|
return v.state.HasFlag(haltState) || v.state.HasFlag(faultState)
|
|
}
|
|
|
|
// HasHalted returns whether VM is in Halt state.
|
|
func (v *VM) HasHalted() bool {
|
|
return v.state.HasFlag(haltState)
|
|
}
|
|
|
|
// AtBreakpoint returns whether VM is at breakpoint.
|
|
func (v *VM) AtBreakpoint() bool {
|
|
return v.state.HasFlag(breakState)
|
|
}
|
|
|
|
// SetCheckedHash sets checked hash for CHECKSIG and CHECKMULTISIG instructions.
|
|
func (v *VM) SetCheckedHash(h []byte) {
|
|
v.checkhash = make([]byte, len(h))
|
|
copy(v.checkhash, h)
|
|
}
|
|
|
|
// SetScriptGetter sets the script getter for CALL instructions.
|
|
func (v *VM) SetScriptGetter(gs func(util.Uint160) []byte) {
|
|
v.getScript = gs
|
|
}
|
|
|
|
// execute performs an instruction cycle in the VM. Acting on the instruction (opcode).
|
|
func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err error) {
|
|
// Instead of polluting the whole VM logic with error handling, we will recover
|
|
// each panic at a central point, putting the VM in a fault state and setting error.
|
|
defer func() {
|
|
if errRecover := recover(); errRecover != nil {
|
|
v.state = faultState
|
|
err = newError(ctx.ip, op, errRecover)
|
|
} else if v.size > MaxStackSize {
|
|
v.state = faultState
|
|
err = newError(ctx.ip, op, "stack is too big")
|
|
}
|
|
}()
|
|
|
|
if op >= opcode.PUSHBYTES1 && op <= opcode.PUSHBYTES75 {
|
|
v.estack.PushVal(parameter)
|
|
return
|
|
}
|
|
|
|
switch op {
|
|
case opcode.PUSHM1, opcode.PUSH1, opcode.PUSH2, opcode.PUSH3,
|
|
opcode.PUSH4, opcode.PUSH5, opcode.PUSH6, opcode.PUSH7,
|
|
opcode.PUSH8, opcode.PUSH9, opcode.PUSH10, opcode.PUSH11,
|
|
opcode.PUSH12, opcode.PUSH13, opcode.PUSH14, opcode.PUSH15,
|
|
opcode.PUSH16:
|
|
val := int(op) - int(opcode.PUSH1) + 1
|
|
v.estack.PushVal(val)
|
|
|
|
case opcode.PUSH0:
|
|
v.estack.PushVal([]byte{})
|
|
|
|
case opcode.PUSHDATA1, opcode.PUSHDATA2, opcode.PUSHDATA4:
|
|
v.estack.PushVal(parameter)
|
|
|
|
// Stack operations.
|
|
case opcode.TOALTSTACK:
|
|
v.astack.Push(v.estack.Pop())
|
|
|
|
case opcode.FROMALTSTACK:
|
|
v.estack.Push(v.astack.Pop())
|
|
|
|
case opcode.DUPFROMALTSTACK:
|
|
v.estack.Push(v.astack.Dup(0))
|
|
|
|
case opcode.DUP:
|
|
v.estack.Push(v.estack.Dup(0))
|
|
|
|
case opcode.SWAP:
|
|
err := v.estack.Swap(1, 0)
|
|
if err != nil {
|
|
panic(err.Error())
|
|
}
|
|
|
|
case opcode.TUCK:
|
|
a := v.estack.Dup(0)
|
|
if a == nil {
|
|
panic("no top-level element found")
|
|
}
|
|
if v.estack.Len() < 2 {
|
|
panic("can't TUCK with a one-element stack")
|
|
}
|
|
v.estack.InsertAt(a, 2)
|
|
|
|
case opcode.CAT:
|
|
b := v.estack.Pop().Bytes()
|
|
a := v.estack.Pop().Bytes()
|
|
if l := len(a) + len(b); l > MaxItemSize {
|
|
panic(fmt.Sprintf("too big item: %d", l))
|
|
}
|
|
ab := append(a, b...)
|
|
v.estack.PushVal(ab)
|
|
|
|
case opcode.SUBSTR:
|
|
l := int(v.estack.Pop().BigInt().Int64())
|
|
if l < 0 {
|
|
panic("negative length")
|
|
}
|
|
o := int(v.estack.Pop().BigInt().Int64())
|
|
if o < 0 {
|
|
panic("negative index")
|
|
}
|
|
s := v.estack.Pop().Bytes()
|
|
if o > len(s) {
|
|
// panic("invalid offset")
|
|
// FIXME revert when NEO 3.0 https://github.com/nspcc-dev/neo-go/issues/477
|
|
v.estack.PushVal("")
|
|
break
|
|
}
|
|
last := l + o
|
|
if last > len(s) {
|
|
last = len(s)
|
|
}
|
|
v.estack.PushVal(s[o:last])
|
|
|
|
case opcode.LEFT:
|
|
l := int(v.estack.Pop().BigInt().Int64())
|
|
if l < 0 {
|
|
panic("negative length")
|
|
}
|
|
s := v.estack.Pop().Bytes()
|
|
if t := len(s); l > t {
|
|
l = t
|
|
}
|
|
v.estack.PushVal(s[:l])
|
|
|
|
case opcode.RIGHT:
|
|
l := int(v.estack.Pop().BigInt().Int64())
|
|
if l < 0 {
|
|
panic("negative length")
|
|
}
|
|
s := v.estack.Pop().Bytes()
|
|
v.estack.PushVal(s[len(s)-l:])
|
|
|
|
case opcode.XDROP:
|
|
n := int(v.estack.Pop().BigInt().Int64())
|
|
if n < 0 {
|
|
panic("invalid length")
|
|
}
|
|
e := v.estack.RemoveAt(n)
|
|
if e == nil {
|
|
panic("bad index")
|
|
}
|
|
|
|
case opcode.XSWAP:
|
|
n := int(v.estack.Pop().BigInt().Int64())
|
|
err := v.estack.Swap(n, 0)
|
|
if err != nil {
|
|
panic(err.Error())
|
|
}
|
|
|
|
case opcode.XTUCK:
|
|
n := int(v.estack.Pop().BigInt().Int64())
|
|
if n <= 0 {
|
|
panic("XTUCK: invalid length")
|
|
}
|
|
a := v.estack.Dup(0)
|
|
if a == nil {
|
|
panic("no top-level element found")
|
|
}
|
|
if n > v.estack.Len() {
|
|
panic("can't push to the position specified")
|
|
}
|
|
v.estack.InsertAt(a, n)
|
|
|
|
case opcode.ROT:
|
|
err := v.estack.Roll(2)
|
|
if err != nil {
|
|
panic(err.Error())
|
|
}
|
|
|
|
case opcode.DEPTH:
|
|
v.estack.PushVal(v.estack.Len())
|
|
|
|
case opcode.NIP:
|
|
elem := v.estack.RemoveAt(1)
|
|
if elem == nil {
|
|
panic("no second element found")
|
|
}
|
|
|
|
case opcode.OVER:
|
|
a := v.estack.Dup(1)
|
|
if a == nil {
|
|
panic("no second element found")
|
|
}
|
|
v.estack.Push(a)
|
|
|
|
case opcode.PICK:
|
|
n := int(v.estack.Pop().BigInt().Int64())
|
|
if n < 0 {
|
|
panic("negative stack item returned")
|
|
}
|
|
a := v.estack.Dup(n)
|
|
if a == nil {
|
|
panic("no nth element found")
|
|
}
|
|
v.estack.Push(a)
|
|
|
|
case opcode.ROLL:
|
|
n := int(v.estack.Pop().BigInt().Int64())
|
|
err := v.estack.Roll(n)
|
|
if err != nil {
|
|
panic(err.Error())
|
|
}
|
|
|
|
case opcode.DROP:
|
|
if v.estack.Len() < 1 {
|
|
panic("stack is too small")
|
|
}
|
|
v.estack.Pop()
|
|
|
|
case opcode.EQUAL:
|
|
b := v.estack.Pop()
|
|
if b == nil {
|
|
panic("no top-level element found")
|
|
}
|
|
a := v.estack.Pop()
|
|
if a == nil {
|
|
panic("no second-to-the-top element found")
|
|
}
|
|
if ta, ok := a.value.(*ArrayItem); ok {
|
|
if tb, ok := b.value.(*ArrayItem); ok {
|
|
v.estack.PushVal(ta == tb)
|
|
break
|
|
}
|
|
} else if ma, ok := a.value.(*MapItem); ok {
|
|
if mb, ok := b.value.(*MapItem); ok {
|
|
v.estack.PushVal(ma == mb)
|
|
break
|
|
}
|
|
}
|
|
v.estack.PushVal(reflect.DeepEqual(a, b))
|
|
|
|
// Bit operations.
|
|
case opcode.INVERT:
|
|
// inplace
|
|
e := v.estack.Peek(0)
|
|
i := e.BigInt()
|
|
e.value = makeStackItem(i.Not(i))
|
|
|
|
case opcode.AND:
|
|
b := v.estack.Pop().BigInt()
|
|
a := v.estack.Pop().BigInt()
|
|
v.estack.PushVal(new(big.Int).And(b, a))
|
|
|
|
case opcode.OR:
|
|
b := v.estack.Pop().BigInt()
|
|
a := v.estack.Pop().BigInt()
|
|
v.estack.PushVal(new(big.Int).Or(b, a))
|
|
|
|
case opcode.XOR:
|
|
b := v.estack.Pop().BigInt()
|
|
a := v.estack.Pop().BigInt()
|
|
v.estack.PushVal(new(big.Int).Xor(b, a))
|
|
|
|
// Numeric operations.
|
|
case opcode.ADD:
|
|
a := v.estack.Pop().BigInt()
|
|
v.checkBigIntSize(a)
|
|
b := v.estack.Pop().BigInt()
|
|
v.checkBigIntSize(b)
|
|
|
|
c := new(big.Int).Add(a, b)
|
|
v.checkBigIntSize(c)
|
|
v.estack.PushVal(c)
|
|
|
|
case opcode.SUB:
|
|
b := v.estack.Pop().BigInt()
|
|
v.checkBigIntSize(b)
|
|
a := v.estack.Pop().BigInt()
|
|
v.checkBigIntSize(a)
|
|
|
|
c := new(big.Int).Sub(a, b)
|
|
v.checkBigIntSize(c)
|
|
v.estack.PushVal(c)
|
|
|
|
case opcode.DIV:
|
|
b := v.estack.Pop().BigInt()
|
|
v.checkBigIntSize(b)
|
|
a := v.estack.Pop().BigInt()
|
|
v.checkBigIntSize(a)
|
|
|
|
v.estack.PushVal(new(big.Int).Div(a, b))
|
|
|
|
case opcode.MUL:
|
|
a := v.estack.Pop().BigInt()
|
|
v.checkBigIntSize(a)
|
|
b := v.estack.Pop().BigInt()
|
|
v.checkBigIntSize(b)
|
|
|
|
c := new(big.Int).Mul(a, b)
|
|
v.checkBigIntSize(c)
|
|
v.estack.PushVal(c)
|
|
|
|
case opcode.MOD:
|
|
b := v.estack.Pop().BigInt()
|
|
v.checkBigIntSize(b)
|
|
a := v.estack.Pop().BigInt()
|
|
v.checkBigIntSize(a)
|
|
|
|
v.estack.PushVal(new(big.Int).Mod(a, b))
|
|
|
|
case opcode.SHL, opcode.SHR:
|
|
b := v.estack.Pop().BigInt().Int64()
|
|
if b == 0 {
|
|
return
|
|
} else if b < minSHLArg || b > maxSHLArg {
|
|
panic(fmt.Sprintf("operand must be between %d and %d", minSHLArg, maxSHLArg))
|
|
}
|
|
a := v.estack.Pop().BigInt()
|
|
v.checkBigIntSize(a)
|
|
|
|
var item big.Int
|
|
if op == opcode.SHL {
|
|
item.Lsh(a, uint(b))
|
|
} else {
|
|
item.Rsh(a, uint(b))
|
|
}
|
|
|
|
v.checkBigIntSize(&item)
|
|
v.estack.PushVal(&item)
|
|
|
|
case opcode.BOOLAND:
|
|
b := v.estack.Pop().Bool()
|
|
a := v.estack.Pop().Bool()
|
|
v.estack.PushVal(a && b)
|
|
|
|
case opcode.BOOLOR:
|
|
b := v.estack.Pop().Bool()
|
|
a := v.estack.Pop().Bool()
|
|
v.estack.PushVal(a || b)
|
|
|
|
case opcode.NUMEQUAL:
|
|
b := v.estack.Pop().BigInt()
|
|
a := v.estack.Pop().BigInt()
|
|
v.estack.PushVal(a.Cmp(b) == 0)
|
|
|
|
case opcode.NUMNOTEQUAL:
|
|
b := v.estack.Pop().BigInt()
|
|
a := v.estack.Pop().BigInt()
|
|
v.estack.PushVal(a.Cmp(b) != 0)
|
|
|
|
case opcode.LT:
|
|
b := v.estack.Pop().BigInt()
|
|
a := v.estack.Pop().BigInt()
|
|
v.estack.PushVal(a.Cmp(b) == -1)
|
|
|
|
case opcode.GT:
|
|
b := v.estack.Pop().BigInt()
|
|
a := v.estack.Pop().BigInt()
|
|
v.estack.PushVal(a.Cmp(b) == 1)
|
|
|
|
case opcode.LTE:
|
|
b := v.estack.Pop().BigInt()
|
|
a := v.estack.Pop().BigInt()
|
|
v.estack.PushVal(a.Cmp(b) <= 0)
|
|
|
|
case opcode.GTE:
|
|
b := v.estack.Pop().BigInt()
|
|
a := v.estack.Pop().BigInt()
|
|
v.estack.PushVal(a.Cmp(b) >= 0)
|
|
|
|
case opcode.MIN:
|
|
b := v.estack.Pop().BigInt()
|
|
a := v.estack.Pop().BigInt()
|
|
val := a
|
|
if a.Cmp(b) == 1 {
|
|
val = b
|
|
}
|
|
v.estack.PushVal(val)
|
|
|
|
case opcode.MAX:
|
|
b := v.estack.Pop().BigInt()
|
|
a := v.estack.Pop().BigInt()
|
|
val := a
|
|
if a.Cmp(b) == -1 {
|
|
val = b
|
|
}
|
|
v.estack.PushVal(val)
|
|
|
|
case opcode.WITHIN:
|
|
b := v.estack.Pop().BigInt()
|
|
a := v.estack.Pop().BigInt()
|
|
x := v.estack.Pop().BigInt()
|
|
v.estack.PushVal(a.Cmp(x) <= 0 && x.Cmp(b) == -1)
|
|
|
|
case opcode.INC:
|
|
x := v.estack.Pop().BigInt()
|
|
a := new(big.Int).Add(x, big.NewInt(1))
|
|
v.checkBigIntSize(a)
|
|
v.estack.PushVal(a)
|
|
|
|
case opcode.DEC:
|
|
x := v.estack.Pop().BigInt()
|
|
a := new(big.Int).Sub(x, big.NewInt(1))
|
|
v.checkBigIntSize(a)
|
|
v.estack.PushVal(a)
|
|
|
|
case opcode.SIGN:
|
|
x := v.estack.Pop().BigInt()
|
|
v.estack.PushVal(x.Sign())
|
|
|
|
case opcode.NEGATE:
|
|
x := v.estack.Pop().BigInt()
|
|
v.estack.PushVal(x.Neg(x))
|
|
|
|
case opcode.ABS:
|
|
x := v.estack.Pop().BigInt()
|
|
v.estack.PushVal(x.Abs(x))
|
|
|
|
case opcode.NOT:
|
|
x := v.estack.Pop().Bool()
|
|
v.estack.PushVal(!x)
|
|
|
|
case opcode.NZ:
|
|
x := v.estack.Pop().BigInt()
|
|
v.estack.PushVal(x.Cmp(big.NewInt(0)) != 0)
|
|
|
|
// Object operations.
|
|
case opcode.NEWARRAY:
|
|
item := v.estack.Pop()
|
|
switch t := item.value.(type) {
|
|
case *StructItem:
|
|
arr := make([]StackItem, len(t.value))
|
|
copy(arr, t.value)
|
|
v.estack.PushVal(&ArrayItem{arr})
|
|
case *ArrayItem:
|
|
v.estack.PushVal(t)
|
|
default:
|
|
n := item.BigInt().Int64()
|
|
if n > MaxArraySize {
|
|
panic("too long array")
|
|
}
|
|
items := makeArrayOfFalses(int(n))
|
|
v.estack.PushVal(&ArrayItem{items})
|
|
}
|
|
|
|
case opcode.NEWSTRUCT:
|
|
item := v.estack.Pop()
|
|
switch t := item.value.(type) {
|
|
case *ArrayItem:
|
|
arr := make([]StackItem, len(t.value))
|
|
copy(arr, t.value)
|
|
v.estack.PushVal(&StructItem{arr})
|
|
case *StructItem:
|
|
v.estack.PushVal(t)
|
|
default:
|
|
n := item.BigInt().Int64()
|
|
if n > MaxArraySize {
|
|
panic("too long struct")
|
|
}
|
|
items := makeArrayOfFalses(int(n))
|
|
v.estack.PushVal(&StructItem{items})
|
|
}
|
|
|
|
case opcode.APPEND:
|
|
itemElem := v.estack.Pop()
|
|
arrElem := v.estack.Pop()
|
|
|
|
val := cloneIfStruct(itemElem.value)
|
|
|
|
switch t := arrElem.value.(type) {
|
|
case *ArrayItem:
|
|
arr := t.Value().([]StackItem)
|
|
if len(arr) >= MaxArraySize {
|
|
panic("too long array")
|
|
}
|
|
arr = append(arr, val)
|
|
t.value = arr
|
|
case *StructItem:
|
|
arr := t.Value().([]StackItem)
|
|
if len(arr) >= MaxArraySize {
|
|
panic("too long struct")
|
|
}
|
|
arr = append(arr, val)
|
|
t.value = arr
|
|
default:
|
|
panic("APPEND: not of underlying type Array")
|
|
}
|
|
|
|
v.estack.updateSizeAdd(val)
|
|
|
|
case opcode.PACK:
|
|
n := int(v.estack.Pop().BigInt().Int64())
|
|
if n < 0 || n > v.estack.Len() || n > MaxArraySize {
|
|
panic("OPACK: invalid length")
|
|
}
|
|
|
|
items := make([]StackItem, n)
|
|
for i := 0; i < n; i++ {
|
|
items[i] = v.estack.Pop().value
|
|
}
|
|
|
|
v.estack.PushVal(items)
|
|
|
|
case opcode.UNPACK:
|
|
a := v.estack.Pop().Array()
|
|
l := len(a)
|
|
for i := l - 1; i >= 0; i-- {
|
|
v.estack.PushVal(a[i])
|
|
}
|
|
v.estack.PushVal(l)
|
|
|
|
case opcode.PICKITEM:
|
|
key := v.estack.Pop()
|
|
validateMapKey(key)
|
|
|
|
obj := v.estack.Pop()
|
|
index := int(key.BigInt().Int64())
|
|
|
|
switch t := obj.value.(type) {
|
|
// Struct and Array items have their underlying value as []StackItem.
|
|
case *ArrayItem, *StructItem:
|
|
arr := t.Value().([]StackItem)
|
|
if index < 0 || index >= len(arr) {
|
|
panic("PICKITEM: invalid index")
|
|
}
|
|
item := arr[index].Dup()
|
|
v.estack.PushVal(item)
|
|
case *MapItem:
|
|
if !t.Has(key.value) {
|
|
panic("invalid key")
|
|
}
|
|
k := toMapKey(key.value)
|
|
v.estack.Push(&Element{value: t.value[k].Dup()})
|
|
default:
|
|
arr := obj.Bytes()
|
|
if index < 0 || index >= len(arr) {
|
|
panic("PICKITEM: invalid index")
|
|
}
|
|
item := arr[index]
|
|
v.estack.PushVal(int(item))
|
|
}
|
|
|
|
case opcode.SETITEM:
|
|
item := v.estack.Pop().value
|
|
key := v.estack.Pop()
|
|
validateMapKey(key)
|
|
|
|
obj := v.estack.Pop()
|
|
|
|
switch t := obj.value.(type) {
|
|
// Struct and Array items have their underlying value as []StackItem.
|
|
case *ArrayItem, *StructItem:
|
|
arr := t.Value().([]StackItem)
|
|
index := int(key.BigInt().Int64())
|
|
if index < 0 || index >= len(arr) {
|
|
panic("SETITEM: invalid index")
|
|
}
|
|
v.estack.updateSizeRemove(arr[index])
|
|
arr[index] = item
|
|
v.estack.updateSizeAdd(arr[index])
|
|
case *MapItem:
|
|
if t.Has(key.value) {
|
|
v.estack.updateSizeRemove(item)
|
|
} else if len(t.value) >= MaxArraySize {
|
|
panic("too big map")
|
|
}
|
|
t.Add(key.value, item)
|
|
v.estack.updateSizeAdd(item)
|
|
|
|
default:
|
|
panic(fmt.Sprintf("SETITEM: invalid item type %s", t))
|
|
}
|
|
|
|
case opcode.REVERSE:
|
|
a := v.estack.Pop().Array()
|
|
if len(a) > 1 {
|
|
for i, j := 0, len(a)-1; i <= j; i, j = i+1, j-1 {
|
|
a[i], a[j] = a[j], a[i]
|
|
}
|
|
}
|
|
case opcode.REMOVE:
|
|
key := v.estack.Pop()
|
|
validateMapKey(key)
|
|
|
|
elem := v.estack.Pop()
|
|
switch t := elem.value.(type) {
|
|
case *ArrayItem:
|
|
a := t.value
|
|
k := int(key.BigInt().Int64())
|
|
if k < 0 || k >= len(a) {
|
|
panic("REMOVE: invalid index")
|
|
}
|
|
v.estack.updateSizeRemove(a[k])
|
|
a = append(a[:k], a[k+1:]...)
|
|
t.value = a
|
|
case *StructItem:
|
|
a := t.value
|
|
k := int(key.BigInt().Int64())
|
|
if k < 0 || k >= len(a) {
|
|
panic("REMOVE: invalid index")
|
|
}
|
|
v.estack.updateSizeRemove(a[k])
|
|
a = append(a[:k], a[k+1:]...)
|
|
t.value = a
|
|
case *MapItem:
|
|
m := t.value
|
|
k := toMapKey(key.value)
|
|
v.estack.updateSizeRemove(m[k])
|
|
delete(m, k)
|
|
default:
|
|
panic("REMOVE: invalid type")
|
|
}
|
|
|
|
case opcode.ARRAYSIZE:
|
|
elem := v.estack.Pop()
|
|
// Cause there is no native (byte) item type here, hence we need to check
|
|
// the type of the item for array size operations.
|
|
switch t := elem.Value().(type) {
|
|
case []StackItem:
|
|
v.estack.PushVal(len(t))
|
|
case map[interface{}]StackItem:
|
|
v.estack.PushVal(len(t))
|
|
default:
|
|
v.estack.PushVal(len(elem.Bytes()))
|
|
}
|
|
|
|
case opcode.SIZE:
|
|
elem := v.estack.Pop()
|
|
arr := elem.Bytes()
|
|
v.estack.PushVal(len(arr))
|
|
|
|
case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT:
|
|
var (
|
|
rOffset = int16(binary.LittleEndian.Uint16(parameter))
|
|
offset = ctx.ip + int(rOffset)
|
|
)
|
|
if offset < 0 || offset > len(ctx.prog) {
|
|
panic(fmt.Sprintf("JMP: invalid offset %d ip at %d", offset, ctx.ip))
|
|
}
|
|
cond := true
|
|
if op > opcode.JMP {
|
|
cond = v.estack.Pop().Bool()
|
|
if op == opcode.JMPIFNOT {
|
|
cond = !cond
|
|
}
|
|
}
|
|
if cond {
|
|
ctx.nextip = offset
|
|
}
|
|
|
|
case opcode.CALL:
|
|
v.checkInvocationStackSize()
|
|
|
|
newCtx := ctx.Copy()
|
|
newCtx.rvcount = -1
|
|
v.istack.PushVal(newCtx)
|
|
err = v.execute(v.Context(), opcode.JMP, parameter)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
case opcode.SYSCALL:
|
|
var ifunc *InteropFuncPrice
|
|
var interopID uint32
|
|
|
|
if len(parameter) == 4 {
|
|
interopID = binary.LittleEndian.Uint32(parameter)
|
|
} else {
|
|
interopID = InteropNameToID(parameter)
|
|
}
|
|
// LIFO interpretation of callbacks.
|
|
for i := len(v.getInterop) - 1; i >= 0; i-- {
|
|
ifunc = v.getInterop[i](interopID)
|
|
if ifunc != nil {
|
|
break
|
|
}
|
|
}
|
|
if ifunc == nil {
|
|
panic(fmt.Sprintf("interop hook (%q/0x%x) not registered", parameter, interopID))
|
|
}
|
|
if err := ifunc.Func(v); err != nil {
|
|
panic(fmt.Sprintf("failed to invoke syscall: %s", err))
|
|
}
|
|
|
|
case opcode.APPCALL, opcode.TAILCALL:
|
|
if v.getScript == nil {
|
|
panic("no getScript callback is set up")
|
|
}
|
|
|
|
if op == opcode.APPCALL {
|
|
v.checkInvocationStackSize()
|
|
}
|
|
|
|
hash, err := util.Uint160DecodeBytesBE(parameter)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
script := v.getScript(hash)
|
|
if script == nil {
|
|
panic("could not find script")
|
|
}
|
|
|
|
if op == opcode.TAILCALL {
|
|
_ = v.istack.Pop()
|
|
}
|
|
|
|
v.LoadScript(script)
|
|
|
|
case opcode.RET:
|
|
oldCtx := v.istack.Pop().Value().(*Context)
|
|
rvcount := oldCtx.rvcount
|
|
oldEstack := v.estack
|
|
|
|
if rvcount > 0 && oldEstack.Len() < rvcount {
|
|
panic("missing some return elements")
|
|
}
|
|
if v.istack.Len() == 0 {
|
|
v.state = haltState
|
|
break
|
|
}
|
|
|
|
newEstack := v.Context().estack
|
|
if oldEstack != newEstack {
|
|
if rvcount < 0 {
|
|
rvcount = oldEstack.Len()
|
|
}
|
|
for i := rvcount; i > 0; i-- {
|
|
elem := oldEstack.RemoveAt(i - 1)
|
|
newEstack.Push(elem)
|
|
}
|
|
v.estack = newEstack
|
|
v.astack = v.Context().astack
|
|
}
|
|
|
|
case opcode.CHECKSIG, opcode.VERIFY:
|
|
var hashToCheck []byte
|
|
|
|
keyb := v.estack.Pop().Bytes()
|
|
signature := v.estack.Pop().Bytes()
|
|
if op == opcode.CHECKSIG {
|
|
if v.checkhash == nil {
|
|
panic("VM is not set up properly for signature checks")
|
|
}
|
|
hashToCheck = v.checkhash
|
|
} else { // VERIFY
|
|
msg := v.estack.Pop().Bytes()
|
|
hashToCheck = hash.Sha256(msg).BytesBE()
|
|
}
|
|
pkey := v.bytesToPublicKey(keyb)
|
|
res := pkey.Verify(signature, hashToCheck)
|
|
v.estack.PushVal(res)
|
|
|
|
case opcode.CHECKMULTISIG:
|
|
pkeys, err := v.estack.popSigElements()
|
|
if err != nil {
|
|
panic(fmt.Sprintf("wrong parameters: %s", err.Error()))
|
|
}
|
|
sigs, err := v.estack.popSigElements()
|
|
if err != nil {
|
|
panic(fmt.Sprintf("wrong parameters: %s", err.Error()))
|
|
}
|
|
// It's ok to have more keys than there are signatures (it would
|
|
// just mean that some keys didn't sign), but not the other way around.
|
|
if len(pkeys) < len(sigs) {
|
|
panic("more signatures than there are keys")
|
|
}
|
|
if v.checkhash == nil {
|
|
panic("VM is not set up properly for signature checks")
|
|
}
|
|
sigok := true
|
|
// j counts keys and i counts signatures.
|
|
j := 0
|
|
for i := 0; sigok && j < len(pkeys) && i < len(sigs); {
|
|
pkey := v.bytesToPublicKey(pkeys[j])
|
|
// We only move to the next signature if the check was
|
|
// successful, but if it's not maybe the next key will
|
|
// fit, so we always move to the next key.
|
|
if pkey.Verify(sigs[i], v.checkhash) {
|
|
i++
|
|
}
|
|
j++
|
|
// When there are more signatures left to check than
|
|
// there are keys the check won't successed for sure.
|
|
if len(sigs)-i > len(pkeys)-j {
|
|
sigok = false
|
|
}
|
|
}
|
|
v.estack.PushVal(sigok)
|
|
|
|
case opcode.NEWMAP:
|
|
v.estack.Push(&Element{value: NewMapItem()})
|
|
|
|
case opcode.KEYS:
|
|
item := v.estack.Pop()
|
|
if item == nil {
|
|
panic("no argument")
|
|
}
|
|
|
|
m, ok := item.value.(*MapItem)
|
|
if !ok {
|
|
panic("not a Map")
|
|
}
|
|
|
|
arr := make([]StackItem, 0, len(m.value))
|
|
for k := range m.value {
|
|
arr = append(arr, makeStackItem(k))
|
|
}
|
|
v.estack.PushVal(arr)
|
|
|
|
case opcode.VALUES:
|
|
item := v.estack.Pop()
|
|
if item == nil {
|
|
panic("no argument")
|
|
}
|
|
|
|
var arr []StackItem
|
|
switch t := item.value.(type) {
|
|
case *ArrayItem, *StructItem:
|
|
src := t.Value().([]StackItem)
|
|
arr = make([]StackItem, len(src))
|
|
for i := range src {
|
|
arr[i] = cloneIfStruct(src[i])
|
|
}
|
|
case *MapItem:
|
|
arr = make([]StackItem, 0, len(t.value))
|
|
for k := range t.value {
|
|
arr = append(arr, cloneIfStruct(t.value[k]))
|
|
}
|
|
default:
|
|
panic("not a Map, Array or Struct")
|
|
}
|
|
|
|
v.estack.PushVal(arr)
|
|
|
|
case opcode.HASKEY:
|
|
key := v.estack.Pop()
|
|
validateMapKey(key)
|
|
|
|
c := v.estack.Pop()
|
|
if c == nil {
|
|
panic("no value found")
|
|
}
|
|
switch t := c.value.(type) {
|
|
case *ArrayItem, *StructItem:
|
|
index := key.BigInt().Int64()
|
|
if index < 0 {
|
|
panic("negative index")
|
|
}
|
|
v.estack.PushVal(index < int64(len(c.Array())))
|
|
case *MapItem:
|
|
v.estack.PushVal(t.Has(key.value))
|
|
default:
|
|
panic("wrong collection type")
|
|
}
|
|
|
|
// Cryptographic operations.
|
|
case opcode.SHA1:
|
|
b := v.estack.Pop().Bytes()
|
|
sha := sha1.New()
|
|
sha.Write(b)
|
|
v.estack.PushVal(sha.Sum(nil))
|
|
|
|
case opcode.SHA256:
|
|
b := v.estack.Pop().Bytes()
|
|
v.estack.PushVal(hash.Sha256(b).BytesBE())
|
|
|
|
case opcode.HASH160:
|
|
b := v.estack.Pop().Bytes()
|
|
v.estack.PushVal(hash.Hash160(b).BytesBE())
|
|
|
|
case opcode.HASH256:
|
|
b := v.estack.Pop().Bytes()
|
|
v.estack.PushVal(hash.DoubleSha256(b).BytesBE())
|
|
|
|
case opcode.NOP:
|
|
// unlucky ^^
|
|
|
|
case opcode.CALLI, opcode.CALLE, opcode.CALLED, opcode.CALLET, opcode.CALLEDT:
|
|
var (
|
|
tailCall = (op == opcode.CALLET || op == opcode.CALLEDT)
|
|
hashOnStack = (op == opcode.CALLED || op == opcode.CALLEDT)
|
|
addElement int
|
|
newCtx *Context
|
|
)
|
|
|
|
if hashOnStack {
|
|
addElement = 1
|
|
}
|
|
|
|
rvcount := int(parameter[0])
|
|
pcount := int(parameter[1])
|
|
if v.estack.Len() < pcount+addElement {
|
|
panic("missing some parameters")
|
|
}
|
|
if tailCall {
|
|
if ctx.rvcount != rvcount {
|
|
panic("context and parameter rvcount mismatch")
|
|
}
|
|
} else {
|
|
v.checkInvocationStackSize()
|
|
}
|
|
|
|
if op == opcode.CALLI {
|
|
newCtx = ctx.Copy()
|
|
} else {
|
|
var hashBytes []byte
|
|
|
|
if hashOnStack {
|
|
hashBytes = v.estack.Pop().Bytes()
|
|
} else {
|
|
hashBytes = parameter[2:]
|
|
}
|
|
|
|
hash, err := util.Uint160DecodeBytesBE(hashBytes)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
script := v.getScript(hash)
|
|
if script == nil {
|
|
panic(fmt.Sprintf("could not find script %s", hash))
|
|
}
|
|
newCtx = NewContext(script)
|
|
}
|
|
newCtx.rvcount = rvcount
|
|
newCtx.estack = NewStack("evaluation")
|
|
newCtx.astack = NewStack("alt")
|
|
// Going backwards to naturally push things onto the new stack.
|
|
for i := pcount; i > 0; i-- {
|
|
elem := v.estack.RemoveAt(i - 1)
|
|
newCtx.estack.Push(elem)
|
|
}
|
|
if tailCall {
|
|
_ = v.istack.Pop()
|
|
}
|
|
v.istack.PushVal(newCtx)
|
|
v.estack = newCtx.estack
|
|
v.astack = newCtx.astack
|
|
if op == opcode.CALLI {
|
|
err = v.execute(v.Context(), opcode.JMP, parameter[2:])
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
case opcode.THROW:
|
|
panic("THROW")
|
|
|
|
case opcode.THROWIFNOT:
|
|
if !v.estack.Pop().Bool() {
|
|
panic("THROWIFNOT")
|
|
}
|
|
|
|
default:
|
|
panic(fmt.Sprintf("unknown opcode %s", op.String()))
|
|
}
|
|
return
|
|
}
|
|
|
|
func cloneIfStruct(item StackItem) StackItem {
|
|
switch it := item.(type) {
|
|
case *StructItem:
|
|
return it.Clone()
|
|
default:
|
|
return it
|
|
}
|
|
}
|
|
|
|
func makeArrayOfFalses(n int) []StackItem {
|
|
items := make([]StackItem, n)
|
|
for i := range items {
|
|
items[i] = &BoolItem{false}
|
|
}
|
|
return items
|
|
}
|
|
|
|
func validateMapKey(key *Element) {
|
|
if key == nil {
|
|
panic("no key found")
|
|
}
|
|
switch key.value.(type) {
|
|
case *ArrayItem, *StructItem, *MapItem:
|
|
panic("key can't be a collection")
|
|
}
|
|
}
|
|
|
|
func (v *VM) checkInvocationStackSize() {
|
|
if v.istack.len >= MaxInvocationStackSize {
|
|
panic("invocation stack is too big")
|
|
}
|
|
}
|
|
|
|
func (v *VM) checkBigIntSize(a *big.Int) {
|
|
if a.BitLen() > MaxBigIntegerSizeBits {
|
|
panic("big integer is too big")
|
|
}
|
|
}
|
|
|
|
// bytesToPublicKey is a helper deserializing keys using cache and panicing on
|
|
// error.
|
|
func (v *VM) bytesToPublicKey(b []byte) *keys.PublicKey {
|
|
var pkey *keys.PublicKey
|
|
s := string(b)
|
|
if v.keys[s] != nil {
|
|
pkey = v.keys[s]
|
|
} else {
|
|
pkey = &keys.PublicKey{}
|
|
err := pkey.DecodeBytes(b)
|
|
if err != nil {
|
|
panic(err.Error())
|
|
}
|
|
v.keys[s] = pkey
|
|
}
|
|
return pkey
|
|
}
|