2018-03-30 16:15:06 +00:00
package vm
import (
2020-07-13 09:59:41 +00:00
"crypto/elliptic"
2019-10-03 13:54:14 +00:00
"encoding/binary"
2020-03-03 10:05:57 +00:00
"encoding/json"
2020-08-06 14:44:08 +00:00
"errors"
2018-03-30 16:15:06 +00:00
"fmt"
2020-12-01 13:52:23 +00:00
"io"
2020-05-12 11:47:33 +00:00
"math"
2018-03-30 16:15:06 +00:00
"math/big"
2018-04-02 15:04:42 +00:00
"os"
2019-09-09 08:23:27 +00:00
"text/tabwriter"
2019-10-03 13:56:48 +00:00
"unicode/utf8"
2018-03-30 16:15:06 +00:00
2020-08-13 07:53:48 +00:00
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
2020-03-03 14:21:42 +00:00
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
2020-06-04 18:11:27 +00:00
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
2020-12-29 10:45:49 +00:00
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
2020-06-25 07:36:21 +00:00
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
2020-06-10 14:57:10 +00:00
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
2020-03-03 14:21:42 +00:00
"github.com/nspcc-dev/neo-go/pkg/util"
2021-07-18 13:08:23 +00:00
"github.com/nspcc-dev/neo-go/pkg/util/slice"
2020-03-03 14:21:42 +00:00
"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-03-30 16:15:06 +00:00
)
2019-10-22 10:44:14 +00:00
type errorAtInstruct struct {
ip int
2019-12-03 14:05:06 +00:00
op opcode . Opcode
2019-10-22 10:44:14 +00:00
err interface { }
}
2018-04-02 15:04:42 +00:00
2019-10-22 10:44:14 +00:00
func ( e * errorAtInstruct ) Error ( ) string {
2021-07-23 07:08:09 +00:00
return fmt . Sprintf ( "at instruction %d (%s): %s" , e . ip , e . op , e . err )
2019-10-22 10:44:14 +00:00
}
2019-12-03 14:05:06 +00:00
func newError ( ip int , op opcode . Opcode , err interface { } ) * errorAtInstruct {
2019-10-22 10:44:14 +00:00
return & errorAtInstruct { ip : ip , op : op , err : err }
}
2022-04-20 18:30:09 +00:00
// StateMessage is a vm state message which could be used as an additional info, for example by cli.
2019-10-22 10:44:14 +00:00
type StateMessage string
2018-04-02 15:04:42 +00:00
2019-09-18 11:35:29 +00:00
const (
2019-10-29 08:01:06 +00:00
// MaxInvocationStackSize is the maximum size of an invocation stack.
MaxInvocationStackSize = 1024
2020-08-22 21:19:28 +00:00
// MaxTryNestingDepth is the maximum level of TRY nesting allowed,
// that is you can't have more exception handling contexts than this.
MaxTryNestingDepth = 16
2019-10-29 10:26:34 +00:00
// MaxStackSize is the maximum number of items allowed to be
// on all stacks at once.
MaxStackSize = 2 * 1024
2020-06-03 12:55:06 +00:00
maxSHLArg = stackitem . MaxBigIntegerSizeBits
2019-09-18 11:35:29 +00:00
)
2020-07-28 13:38:00 +00:00
// SyscallHandler is a type for syscall handler.
type SyscallHandler = func ( * VM , uint32 ) error
2018-03-30 16:15:06 +00:00
// VM represents the virtual machine.
type VM struct {
state State
2020-01-20 11:42:36 +00:00
// callback to get interop price
2021-02-05 08:25:22 +00:00
getPrice func ( opcode . Opcode , [ ] byte ) int64
2020-01-20 11:42:36 +00:00
2022-05-19 08:35:04 +00:00
// wraps DAO with private MemCachedStore
wrapDao func ( )
// commits DAO changes and unwraps DAO.
commitChanges func ( ) error
// unwraps DAO and removes last notificationsCount notifications from the context
revertChanges func ( notificationsCount int )
2021-08-11 11:40:41 +00:00
istack Stack // invocation stack.
2018-03-30 16:15:06 +00:00
estack * Stack // execution stack.
2020-07-22 09:05:46 +00:00
uncaughtException stackitem . Item // exception being handled
2021-08-11 10:25:58 +00:00
refs refCounter
2019-12-10 16:13:29 +00:00
2020-06-23 14:15:35 +00:00
gasConsumed int64
GasLimit int64
2020-01-20 11:42:36 +00:00
2020-07-28 13:38:00 +00:00
// SyscallHandler handles SYSCALL opcode.
SyscallHandler func ( v * VM , id uint32 ) error
2020-12-29 09:04:53 +00:00
// LoadToken handles CALLT opcode.
LoadToken func ( id int32 ) error
2020-06-10 14:57:10 +00:00
trigger trigger . Type
2021-11-19 20:50:12 +00:00
// invTree is a top-level invocation tree (if enabled).
invTree * InvocationTree
2018-03-30 16:15:06 +00:00
}
2022-05-11 06:33:19 +00:00
var (
bigMinusOne = big . NewInt ( - 1 )
bigZero = big . NewInt ( 0 )
bigOne = big . NewInt ( 1 )
bigTwo = big . NewInt ( 2 )
)
2021-11-30 18:10:48 +00:00
2020-06-25 07:36:21 +00:00
// New returns a new VM object ready to load AVM bytecode scripts.
2019-10-22 10:44:14 +00:00
func New ( ) * VM {
2020-10-29 16:11:47 +00:00
return NewWithTrigger ( trigger . Application )
2020-06-10 14:57:10 +00:00
}
// NewWithTrigger returns a new VM for executions triggered by t.
func NewWithTrigger ( t trigger . Type ) * VM {
2018-04-02 15:04:42 +00:00
vm := & VM {
2020-08-18 08:13:09 +00:00
state : NoneState ,
2020-07-28 13:38:00 +00:00
trigger : t ,
SyscallHandler : defaultSyscallHandler ,
2018-03-30 16:15:06 +00:00
}
2018-04-10 09:45:31 +00:00
2021-08-11 11:40:41 +00:00
initStack ( & vm . istack , "invocation" , nil )
2021-11-30 20:07:27 +00:00
vm . istack . elems = make ( [ ] Element , 0 , 8 ) // Most of invocations use one-two contracts, but they're likely to have internal calls.
2021-08-11 10:25:58 +00:00
vm . estack = newStack ( "evaluation" , & vm . refs )
2018-04-02 15:04:42 +00:00
return vm
}
2022-05-19 08:35:04 +00:00
func ( v * VM ) EmitNotification ( ) {
currCtx := v . Context ( )
if currCtx == nil {
return
}
* currCtx . notificationsCount ++
}
// SetIsolationCallbacks registers given callbacks to perform DAO and interop context
// isolation between contract calls.
// wrapper performs DAO cloning;
// committer persists changes made in the upper snapshot to the underlying DAO;
// reverter rolls back the whole set of changes made in the current snapshot.
func ( v * VM ) SetIsolationCallbacks ( wrapper func ( ) , committer func ( ) error , reverter func ( ntfToRemove int ) ) {
v . wrapDao = wrapper
v . commitChanges = committer
v . revertChanges = reverter
}
2020-01-20 11:42:36 +00:00
// SetPriceGetter registers the given PriceGetterFunc in v.
// f accepts vm's Context, current instruction and instruction parameter.
2021-02-05 08:25:22 +00:00
func ( v * VM ) SetPriceGetter ( f func ( opcode . Opcode , [ ] byte ) int64 ) {
2020-01-20 11:42:36 +00:00
v . getPrice = f
}
// GasConsumed returns the amount of GAS consumed during execution.
2020-06-23 14:15:35 +00:00
func ( v * VM ) GasConsumed ( ) int64 {
2020-01-20 11:42:36 +00:00
return v . gasConsumed
}
2022-04-20 18:30:09 +00:00
// AddGas consumes the specified amount of gas. It returns true if gas limit wasn't exceeded.
2020-06-23 14:15:35 +00:00
func ( v * VM ) AddGas ( gas int64 ) bool {
2020-06-09 09:23:14 +00:00
v . gasConsumed += gas
2020-07-13 17:12:13 +00:00
return v . GasLimit < 0 || v . gasConsumed <= v . GasLimit
2020-06-09 09:23:14 +00:00
}
2022-04-20 18:30:09 +00:00
// Estack returns the evaluation stack, so interop hooks can utilize this.
2018-04-10 09:45:31 +00:00
func ( v * VM ) Estack ( ) * Stack {
return v . estack
}
2022-04-20 18:30:09 +00:00
// Istack returns the invocation stack, so interop hooks can utilize this.
2018-04-10 09:45:31 +00:00
func ( v * VM ) Istack ( ) * Stack {
2021-08-11 11:40:41 +00:00
return & v . istack
2018-04-10 09:45:31 +00:00
}
2019-10-18 15:28:08 +00:00
// PrintOps prints the opcodes of the current loaded program to stdout.
2020-12-01 13:52:23 +00:00
func ( v * VM ) PrintOps ( out io . Writer ) {
if out == nil {
out = os . Stdout
}
w := tabwriter . NewWriter ( out , 0 , 0 , 4 , ' ' , 0 )
2021-12-27 11:40:42 +00:00
fmt . Fprintln ( w , "INDEX\tOPCODE\tPARAMETER" )
2019-10-03 13:56:48 +00:00
realctx := v . Context ( )
ctx := realctx . Copy ( )
ctx . ip = 0
ctx . nextip = 0
for {
cursor := ""
instr , parameter , err := ctx . Next ( )
if ctx . ip == realctx . ip {
2021-12-27 11:40:42 +00:00
cursor = "\t<<"
2018-04-02 15:04:42 +00:00
}
2019-10-03 13:56:48 +00:00
if err != nil {
2021-12-27 11:40:42 +00:00
fmt . Fprintf ( w , "%d\t%s\tERROR: %s%s\n" , ctx . ip , instr , err , cursor )
2019-10-03 13:56:48 +00:00
break
}
var desc = ""
if parameter != nil {
switch instr {
2020-06-26 16:27:14 +00:00
case opcode . JMP , opcode . JMPIF , opcode . JMPIFNOT , opcode . CALL ,
opcode . JMPEQ , opcode . JMPNE ,
opcode . JMPGT , opcode . JMPGE , opcode . JMPLE , opcode . JMPLT ,
opcode . JMPL , opcode . JMPIFL , opcode . JMPIFNOTL , opcode . CALLL ,
opcode . JMPEQL , opcode . JMPNEL ,
2020-07-23 10:07:21 +00:00
opcode . JMPGTL , opcode . JMPGEL , opcode . JMPLEL , opcode . JMPLTL ,
2020-07-22 09:05:46 +00:00
opcode . PUSHA , opcode . ENDTRY , opcode . ENDTRYL :
2021-02-09 18:42:39 +00:00
desc = getOffsetDesc ( ctx , parameter )
2020-07-22 09:05:46 +00:00
case opcode . TRY , opcode . TRYL :
catchP , finallyP := getTryParams ( instr , parameter )
desc = fmt . Sprintf ( "catch %s, finally %s" ,
2021-02-09 18:42:39 +00:00
getOffsetDesc ( ctx , catchP ) , getOffsetDesc ( ctx , finallyP ) )
2020-06-26 16:34:49 +00:00
case opcode . INITSSLOT :
desc = fmt . Sprint ( parameter [ 0 ] )
2020-07-24 14:15:05 +00:00
case opcode . CONVERT , opcode . ISTYPE :
typ := stackitem . Type ( parameter [ 0 ] )
desc = fmt . Sprintf ( "%s (%x)" , typ , parameter [ 0 ] )
2020-06-26 16:34:49 +00:00
case opcode . INITSLOT :
desc = fmt . Sprintf ( "%d local, %d arg" , parameter [ 0 ] , parameter [ 1 ] )
2019-12-03 14:05:06 +00:00
case opcode . SYSCALL :
2020-08-13 07:53:48 +00:00
name , err := interopnames . FromID ( GetInteropID ( parameter ) )
if err != nil {
name = "not found"
}
desc = fmt . Sprintf ( "%s (%x)" , name , parameter )
2020-07-24 14:21:46 +00:00
case opcode . PUSHINT8 , opcode . PUSHINT16 , opcode . PUSHINT32 ,
opcode . PUSHINT64 , opcode . PUSHINT128 , opcode . PUSHINT256 :
val := bigint . FromBytes ( parameter )
desc = fmt . Sprintf ( "%d (%x)" , val , parameter )
2020-07-25 06:50:09 +00:00
case opcode . LDLOC , opcode . STLOC , opcode . LDARG , opcode . STARG , opcode . LDSFLD , opcode . STSFLD :
desc = fmt . Sprintf ( "%d (%x)" , parameter [ 0 ] , parameter )
2019-10-03 13:56:48 +00:00
default :
if utf8 . Valid ( parameter ) {
desc = fmt . Sprintf ( "%x (%q)" , parameter , parameter )
} else {
desc = fmt . Sprintf ( "%x" , parameter )
}
}
}
2018-04-02 15:04:42 +00:00
2021-12-27 11:40:42 +00:00
fmt . Fprintf ( w , "%d\t%s\t%s%s\n" , ctx . ip , instr , desc , cursor )
2019-10-03 13:56:48 +00:00
if ctx . nextip >= len ( ctx . prog ) {
break
}
2018-04-02 15:04:42 +00:00
}
w . Flush ( )
2018-03-30 16:15:06 +00:00
}
2021-02-09 18:42:39 +00:00
func getOffsetDesc ( ctx * Context , parameter [ ] byte ) string {
offset , rOffset , err := calcJumpOffset ( ctx , parameter )
2020-07-22 09:05:46 +00:00
if err != nil {
return fmt . Sprintf ( "ERROR: %v" , err )
}
return fmt . Sprintf ( "%d (%d/%x)" , offset , rOffset , parameter )
}
2018-03-30 16:15:06 +00:00
// 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 ( )
2020-12-01 13:53:38 +00:00
v . AddBreakPoint ( ctx . nextip + n )
2018-03-30 16:15:06 +00:00
}
2021-05-28 09:07:03 +00:00
// LoadFileWithFlags loads a program in NEF format from the given path, ready to execute it.
func ( v * VM ) LoadFileWithFlags ( path string , f callflag . CallFlag ) error {
2022-02-22 16:27:32 +00:00
b , err := os . ReadFile ( path )
2018-03-30 16:15:06 +00:00
if err != nil {
return err
}
2021-05-28 09:07:03 +00:00
nef , err := nef . FileFromBytes ( b )
2020-06-25 07:36:21 +00:00
if err != nil {
return err
}
2021-05-28 09:07:03 +00:00
v . Load ( nef . Script )
2018-03-30 16:15:06 +00:00
return nil
}
2021-11-19 20:50:12 +00:00
// CollectInvocationTree enables collecting invocation tree data.
func ( v * VM ) EnableInvocationTree ( ) {
v . invTree = & InvocationTree { }
}
2022-04-20 18:30:09 +00:00
// GetInvocationTree returns the current invocation tree structure.
2021-11-19 20:50:12 +00:00
func ( v * VM ) GetInvocationTree ( ) * InvocationTree {
return v . invTree
}
2019-09-03 14:51:37 +00:00
// Load initializes the VM with the program given.
2018-04-02 15:04:42 +00:00
func ( v * VM ) Load ( prog [ ] byte ) {
2021-05-28 09:07:03 +00:00
v . LoadWithFlags ( prog , callflag . NoneFlag )
}
// LoadWithFlags initializes the VM with the program and flags given.
func ( v * VM ) LoadWithFlags ( prog [ ] byte , f callflag . CallFlag ) {
2019-10-29 09:53:09 +00:00
// Clear all stacks and state, it could be a reload.
2018-04-02 15:04:42 +00:00
v . istack . Clear ( )
v . estack . Clear ( )
2020-07-27 14:57:53 +00:00
v . state = NoneState
2020-01-20 11:42:36 +00:00
v . gasConsumed = 0
2021-11-19 20:50:12 +00:00
v . invTree = nil
2021-05-28 09:07:03 +00:00
v . LoadScriptWithFlags ( prog , f )
2018-04-02 15:04:42 +00:00
}
2019-10-18 15:28:08 +00:00
// LoadScript loads a script from the internal script table. It
2019-02-09 15:53:58 +00:00
// will immediately push a new context created from this script to
2018-03-30 16:15:06 +00:00
// the invocation stack and starts executing it.
func ( v * VM ) LoadScript ( b [ ] byte ) {
2020-12-29 10:45:49 +00:00
v . LoadScriptWithFlags ( b , callflag . NoneFlag )
2020-06-10 12:51:28 +00:00
}
// LoadScriptWithFlags loads script and sets call flag to f.
2020-12-29 10:45:49 +00:00
func ( v * VM ) LoadScriptWithFlags ( b [ ] byte , f callflag . CallFlag ) {
2022-05-23 08:35:01 +00:00
v . loadScriptWithCallingHash ( b , nil , v . GetCurrentScriptHash ( ) , util . Uint160 { } , f , - 1 , 0 , nil )
2018-03-30 16:15:06 +00:00
}
2022-04-20 18:30:09 +00:00
// LoadScriptWithHash is similar to the LoadScriptWithFlags method, but it also loads
// the given script hash directly into the Context to avoid its recalculations and to make
// it possible to override it for deployed contracts with special hashes (the function
2020-11-26 20:02:00 +00:00
// assumes that it is used for deployed contracts setting context's parameters
2022-04-20 18:30:09 +00:00
// accordingly). It's up to the user of this function to make sure the script and hash match
2020-11-26 20:02:00 +00:00
// each other.
2020-12-29 10:45:49 +00:00
func ( v * VM ) LoadScriptWithHash ( b [ ] byte , hash util . Uint160 , f callflag . CallFlag ) {
2022-05-23 08:35:01 +00:00
v . loadScriptWithCallingHash ( b , nil , v . GetCurrentScriptHash ( ) , hash , f , 1 , 0 , nil )
2020-12-09 12:16:49 +00:00
}
2021-11-19 17:02:32 +00:00
// LoadNEFMethod allows to create a context to execute a method from the NEF
2022-04-20 18:30:09 +00:00
// file with the specified caller and executing hash, call flags, return value,
2021-11-19 17:02:32 +00:00
// method and _initialize offsets.
func ( v * VM ) LoadNEFMethod ( exe * nef . File , caller util . Uint160 , hash util . Uint160 , f callflag . CallFlag ,
2022-05-23 08:35:01 +00:00
hasReturn bool , methodOff int , initOff int , onContextUnload ContextUnloadCallback ) {
2021-11-19 19:17:05 +00:00
var rvcount int
if hasReturn {
rvcount = 1
}
2022-05-23 08:35:01 +00:00
v . loadScriptWithCallingHash ( exe . Script , exe , caller , hash , f , rvcount , methodOff , onContextUnload )
2021-11-19 17:02:32 +00:00
if initOff >= 0 {
v . Call ( initOff )
}
}
// loadScriptWithCallingHash is similar to LoadScriptWithHash but sets calling hash explicitly.
2020-12-09 12:16:49 +00:00
// It should be used for calling from native contracts.
2021-11-19 19:17:05 +00:00
func ( v * VM ) loadScriptWithCallingHash ( b [ ] byte , exe * nef . File , caller util . Uint160 ,
2022-05-23 08:35:01 +00:00
hash util . Uint160 , f callflag . CallFlag , rvcount int , offset int , onContextUnload ContextUnloadCallback ) {
2021-11-30 12:03:01 +00:00
var sl slot
2021-11-19 19:17:05 +00:00
v . checkInvocationStackSize ( )
2022-05-19 08:35:04 +00:00
ctx := NewContextWithParams ( b , rvcount , offset , nil )
2021-12-02 12:59:51 +00:00
if rvcount != - 1 || v . estack . Len ( ) != 0 {
v . estack = newStack ( "evaluation" , & v . refs )
}
2021-11-19 19:17:05 +00:00
ctx . estack = v . estack
initStack ( & ctx . tryStack , "exception" , nil )
ctx . callFlag = f
2021-11-30 12:03:01 +00:00
ctx . static = & sl
2020-06-11 07:32:55 +00:00
ctx . scriptHash = hash
2020-12-09 12:16:49 +00:00
ctx . callingScriptHash = caller
2021-11-19 19:17:05 +00:00
ctx . NEF = exe
2022-05-23 05:42:10 +00:00
parent := v . Context ( )
2021-11-19 20:50:12 +00:00
if v . invTree != nil {
curTree := v . invTree
if parent != nil {
curTree = parent . invTree
}
newTree := & InvocationTree { Current : ctx . ScriptHash ( ) }
curTree . Calls = append ( curTree . Calls , newTree )
ctx . invTree = newTree
}
2022-05-19 08:35:04 +00:00
if v . wrapDao != nil {
2022-05-23 05:42:10 +00:00
needWrap := f & ( callflag . All ^ callflag . ReadOnly ) != 0 // If the method is safe, then it's read-only and doesn't perform storage changes or emit notifications.
if ! needWrap && parent != nil { // If the method is not wrapped into try-catch block, then changes should be discarded anyway if exception occurs.
for i := 0 ; i < parent . tryStack . Len ( ) ; i ++ {
eCtx := parent . tryStack . Peek ( i ) . Value ( ) . ( * exceptionHandlingContext )
if eCtx . State == eTry {
needWrap = true // TODO: is it correct to wrap it only once and break after the first occurrence?
break
}
}
}
if needWrap {
v . wrapDao ( )
ctx . isWrapped = true
}
2022-05-19 08:35:04 +00:00
}
2022-05-23 05:42:10 +00:00
ctx . persistNotificationsCountOnUnloading = true
2022-05-23 08:35:01 +00:00
ctx . onUnload = onContextUnload
2021-11-19 19:17:05 +00:00
v . istack . PushItem ( ctx )
2020-06-11 07:32:55 +00:00
}
2018-03-30 16:15:06 +00:00
// 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
}
2021-08-28 19:31:08 +00:00
return v . istack . Peek ( 0 ) . value . ( * Context )
2018-03-30 16:15:06 +00:00
}
2018-04-02 15:04:42 +00:00
// PopResult is used to pop the first item of the evaluation stack. This allows
2022-04-20 18:30:09 +00:00
// us to test the compiler and the vm in a bi-directional way.
2018-04-02 15:04:42 +00:00
func ( v * VM ) PopResult ( ) interface { } {
2021-08-21 16:09:44 +00:00
if v . estack . Len ( ) == 0 {
return nil
2019-10-03 13:02:15 +00:00
}
2021-08-21 16:09:44 +00:00
return v . estack . Pop ( ) . Value ( )
2018-04-02 15:04:42 +00:00
}
2021-09-08 15:51:34 +00:00
// DumpIStack returns json formatted representation of the invocation stack.
func ( v * VM ) DumpIStack ( ) string {
return dumpStack ( & v . istack )
}
// DumpEStack returns json formatted representation of the execution stack.
func ( v * VM ) DumpEStack ( ) string {
return dumpStack ( v . estack )
}
// dumpStack returns json formatted representation of the given stack.
func dumpStack ( s * Stack ) string {
2020-07-31 12:57:02 +00:00
b , _ := json . MarshalIndent ( s , "" , " " )
2020-03-03 10:05:57 +00:00
return string ( b )
2018-03-30 16:15:06 +00:00
}
2020-07-27 14:57:53 +00:00
// State returns the state for the VM.
func ( v * VM ) State ( ) State {
return v . state
2019-10-29 15:26:04 +00:00
}
2022-04-20 18:30:09 +00:00
// Ready returns true if the VM is ready to execute the loaded program.
// It will return false if no program is loaded.
2018-03-30 16:15:06 +00:00
func ( v * VM ) Ready ( ) bool {
return v . istack . Len ( ) > 0
}
2022-04-20 18:30:09 +00:00
// Run starts execution of the loaded program.
2019-10-22 10:44:14 +00:00
func ( v * VM ) Run ( ) error {
2021-08-28 19:31:08 +00:00
var ctx * Context
2018-03-30 16:15:06 +00:00
if ! v . Ready ( ) {
2020-07-27 14:57:53 +00:00
v . state = FaultState
2019-10-22 10:44:14 +00:00
return errors . New ( "no program loaded" )
2018-03-30 16:15:06 +00:00
}
2020-07-27 14:57:53 +00:00
if v . state . HasFlag ( FaultState ) {
2019-10-22 12:16:29 +00:00
// 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" )
}
2020-07-27 14:57:53 +00:00
// HaltState (the default) or BreakState are safe to continue.
v . state = NoneState
2021-08-28 19:31:08 +00:00
ctx = v . Context ( )
2018-03-30 16:15:06 +00:00
for {
2019-02-19 11:47:25 +00:00
switch {
2020-07-27 14:57:53 +00:00
case v . state . HasFlag ( FaultState ) :
2019-10-22 12:16:29 +00:00
// 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" )
2020-07-27 14:57:53 +00:00
case v . state . HasFlag ( HaltState ) , v . state . HasFlag ( BreakState ) :
2019-10-22 12:16:29 +00:00
// Normal exit from this loop.
return nil
2020-07-27 14:57:53 +00:00
case v . state == NoneState :
2021-08-28 19:31:08 +00:00
if err := v . step ( ctx ) ; err != nil {
2019-10-22 10:44:14 +00:00
return err
}
default :
2020-07-27 14:57:53 +00:00
v . state = FaultState
2019-10-22 10:44:14 +00:00
return errors . New ( "unknown state" )
2018-03-30 16:15:06 +00:00
}
2020-08-18 08:13:09 +00:00
// check for breakpoint before executing the next instruction
2021-08-28 19:31:08 +00:00
ctx = v . Context ( )
2020-08-18 08:13:09 +00:00
if ctx != nil && ctx . atBreakPoint ( ) {
v . state = BreakState
}
2018-03-30 16:15:06 +00:00
}
}
// Step 1 instruction in the program.
2019-10-22 10:44:14 +00:00
func ( v * VM ) Step ( ) error {
2018-03-30 16:15:06 +00:00
ctx := v . Context ( )
2021-08-28 19:31:08 +00:00
return v . step ( ctx )
}
2022-04-20 18:30:09 +00:00
// step executes one instruction in the given context.
2021-08-28 19:31:08 +00:00
func ( v * VM ) step ( ctx * Context ) error {
2019-10-03 13:54:14 +00:00
op , param , err := ctx . Next ( )
if err != nil {
2020-07-27 14:57:53 +00:00
v . state = FaultState
2019-10-22 10:44:14 +00:00
return newError ( ctx . ip , op , err )
2018-03-30 16:15:06 +00:00
}
2019-10-22 10:44:14 +00:00
return v . execute ( ctx , op , param )
2018-03-30 16:15:06 +00:00
}
2022-04-20 18:30:09 +00:00
// StepInto behaves the same as “step over” in case the line does not contain a function. Otherwise,
2019-10-14 15:37:11 +00:00
// the debugger will enter the called function and continue line-by-line debugging there.
2019-10-22 10:44:14 +00:00
func ( v * VM ) StepInto ( ) error {
2019-10-14 15:37:11 +00:00
ctx := v . Context ( )
if ctx == nil {
2020-07-27 14:57:53 +00:00
v . state = HaltState
2019-10-14 15:37:11 +00:00
}
if v . HasStopped ( ) {
2019-10-22 10:44:14 +00:00
return nil
2019-10-14 15:37:11 +00:00
}
if ctx != nil && ctx . prog != nil {
op , param , err := ctx . Next ( )
if err != nil {
2020-07-27 14:57:53 +00:00
v . state = FaultState
2019-10-22 10:44:14 +00:00
return newError ( ctx . ip , op , err )
}
vErr := v . execute ( ctx , op , param )
if vErr != nil {
return vErr
2019-10-14 15:37:11 +00:00
}
}
cctx := v . Context ( )
if cctx != nil && cctx . atBreakPoint ( ) {
2020-07-27 14:57:53 +00:00
v . state = BreakState
2019-10-14 15:37:11 +00:00
}
2019-10-22 10:44:14 +00:00
return nil
2019-10-14 15:37:11 +00:00
}
// StepOut takes the debugger to the line where the current function was called.
2019-10-22 10:44:14 +00:00
func ( v * VM ) StepOut ( ) error {
var err error
2020-07-27 14:57:53 +00:00
if v . state == BreakState {
v . state = NoneState
2019-10-14 15:37:11 +00:00
}
2021-08-21 16:09:44 +00:00
expSize := v . istack . Len ( )
for v . state == NoneState && v . istack . Len ( ) >= expSize {
2019-10-22 10:44:14 +00:00
err = v . StepInto ( )
2019-10-14 15:37:11 +00:00
}
2020-08-18 08:13:09 +00:00
if v . state == NoneState {
v . state = BreakState
}
2019-10-22 10:44:14 +00:00
return err
2019-10-14 15:37:11 +00:00
}
2022-04-20 18:30:09 +00:00
// StepOver takes the debugger to the line that will step over the given line.
// If the line contains a function, the function will be executed and the result is returned without debugging each line.
2019-10-22 10:44:14 +00:00
func ( v * VM ) StepOver ( ) error {
var err error
2019-10-14 15:37:11 +00:00
if v . HasStopped ( ) {
2019-10-22 10:44:14 +00:00
return err
2019-10-14 15:37:11 +00:00
}
2020-07-27 14:57:53 +00:00
if v . state == BreakState {
v . state = NoneState
2019-10-14 15:37:11 +00:00
}
2021-08-21 16:09:44 +00:00
expSize := v . istack . Len ( )
2019-10-14 15:37:11 +00:00
for {
2019-10-22 10:44:14 +00:00
err = v . StepInto ( )
2021-08-21 16:09:44 +00:00
if ! ( v . state == NoneState && v . istack . Len ( ) > expSize ) {
2019-10-14 15:37:11 +00:00
break
}
}
2019-11-06 09:24:02 +00:00
2020-07-27 14:57:53 +00:00
if v . state == NoneState {
v . state = BreakState
2019-11-06 09:24:02 +00:00
}
2019-10-22 10:44:14 +00:00
return err
2019-10-14 15:37:11 +00:00
}
2022-04-20 18:30:09 +00:00
// HasFailed returns whether the VM is in the failed state now. Usually, it's used to
2019-09-23 15:46:47 +00:00
// check status after Run.
func ( v * VM ) HasFailed ( ) bool {
2020-07-27 14:57:53 +00:00
return v . state . HasFlag ( FaultState )
2019-09-23 15:46:47 +00:00
}
2022-04-20 18:30:09 +00:00
// HasStopped returns whether the VM is in the Halt or Failed state.
2019-10-14 15:37:11 +00:00
func ( v * VM ) HasStopped ( ) bool {
2020-07-27 14:57:53 +00:00
return v . state . HasFlag ( HaltState ) || v . state . HasFlag ( FaultState )
2019-10-14 15:37:11 +00:00
}
2022-04-20 18:30:09 +00:00
// HasHalted returns whether the VM is in the Halt state.
2019-10-22 10:44:14 +00:00
func ( v * VM ) HasHalted ( ) bool {
2020-07-27 14:57:53 +00:00
return v . state . HasFlag ( HaltState )
2019-10-22 10:44:14 +00:00
}
2022-04-20 18:30:09 +00:00
// AtBreakpoint returns whether the VM is at breakpoint.
2019-10-22 10:44:14 +00:00
func ( v * VM ) AtBreakpoint ( ) bool {
2020-07-27 14:57:53 +00:00
return v . state . HasFlag ( BreakState )
2019-10-22 10:44:14 +00:00
}
2020-01-20 08:41:40 +00:00
// GetInteropID converts instruction parameter to an interop ID.
func GetInteropID ( parameter [ ] byte ) uint32 {
2020-04-15 14:40:05 +00:00
return binary . LittleEndian . Uint32 ( parameter )
2020-01-20 08:41:40 +00:00
}
2018-03-30 16:15:06 +00:00
// execute performs an instruction cycle in the VM. Acting on the instruction (opcode).
2019-12-03 14:05:06 +00:00
func ( v * VM ) execute ( ctx * Context , op opcode . Opcode , parameter [ ] byte ) ( err error ) {
2019-02-09 15:53:58 +00:00
// Instead of polluting the whole VM logic with error handling, we will recover
2019-10-22 10:44:14 +00:00
// each panic at a central point, putting the VM in a fault state and setting error.
2018-03-30 16:15:06 +00:00
defer func ( ) {
2019-10-22 10:44:14 +00:00
if errRecover := recover ( ) ; errRecover != nil {
2020-07-27 14:57:53 +00:00
v . state = FaultState
2019-10-22 10:44:14 +00:00
err = newError ( ctx . ip , op , errRecover )
2021-08-11 12:16:29 +00:00
} else if v . refs > MaxStackSize {
2020-07-27 14:57:53 +00:00
v . state = FaultState
2019-10-29 10:26:34 +00:00
err = newError ( ctx . ip , op , "stack is too big" )
2018-03-30 16:15:06 +00:00
}
} ( )
2020-01-20 11:42:36 +00:00
if v . getPrice != nil && ctx . ip < len ( ctx . prog ) {
2021-02-05 08:25:22 +00:00
v . gasConsumed += v . getPrice ( op , parameter )
2020-07-13 17:12:13 +00:00
if v . GasLimit >= 0 && v . gasConsumed > v . GasLimit {
2020-01-20 11:49:07 +00:00
panic ( "gas limit is exceeded" )
}
2020-01-20 11:42:36 +00:00
}
2020-04-21 13:45:48 +00:00
if op <= opcode . PUSHINT256 {
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewBigInteger ( bigint . FromBytes ( parameter ) ) )
2018-03-30 16:15:06 +00:00
return
}
switch op {
2020-05-20 13:31:10 +00:00
case opcode . PUSHM1 , opcode . PUSH0 , opcode . PUSH1 , opcode . PUSH2 , opcode . PUSH3 ,
2019-12-03 14:05:06 +00:00
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 :
2020-05-20 13:31:10 +00:00
val := int ( op ) - int ( opcode . PUSH0 )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewBigInteger ( big . NewInt ( int64 ( val ) ) ) )
2018-03-30 16:15:06 +00:00
2019-12-03 14:05:06 +00:00
case opcode . PUSHDATA1 , opcode . PUSHDATA2 , opcode . PUSHDATA4 :
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewByteArray ( parameter ) )
2018-03-30 16:15:06 +00:00
2020-05-06 13:55:30 +00:00
case opcode . PUSHA :
2021-02-09 18:42:39 +00:00
n := getJumpOffset ( ctx , parameter )
2020-08-22 19:19:44 +00:00
ptr := stackitem . NewPointerWithHash ( n , ctx . prog , ctx . ScriptHash ( ) )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( ptr )
2020-05-06 13:55:30 +00:00
2020-03-23 08:58:41 +00:00
case opcode . PUSHNULL :
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . Null { } )
2020-03-23 08:58:41 +00:00
2020-03-23 09:26:26 +00:00
case opcode . ISNULL :
2020-09-29 09:21:51 +00:00
_ , ok := v . estack . Pop ( ) . value . ( stackitem . Null )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . Bool ( ok ) )
2020-03-23 09:26:26 +00:00
2020-04-24 10:46:46 +00:00
case opcode . ISTYPE :
res := v . estack . Pop ( ) . Item ( )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . Bool ( res . Type ( ) == stackitem . Type ( parameter [ 0 ] ) ) )
2020-04-24 10:46:46 +00:00
2020-04-28 08:24:02 +00:00
case opcode . CONVERT :
2020-06-03 12:55:06 +00:00
typ := stackitem . Type ( parameter [ 0 ] )
2020-04-28 08:24:02 +00:00
item := v . estack . Pop ( ) . Item ( )
result , err := item . Convert ( typ )
if err != nil {
panic ( err )
}
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( result )
2020-04-28 08:24:02 +00:00
2020-05-07 08:54:35 +00:00
case opcode . INITSSLOT :
if parameter [ 0 ] == 0 {
panic ( "zero argument" )
}
2022-05-16 14:42:57 +00:00
ctx . static . init ( int ( parameter [ 0 ] ) , & v . refs )
2020-05-07 08:54:35 +00:00
case opcode . INITSLOT :
if ctx . local != nil || ctx . arguments != nil {
panic ( "already initialized" )
}
if parameter [ 0 ] == 0 && parameter [ 1 ] == 0 {
panic ( "zero argument" )
}
if parameter [ 0 ] > 0 {
2022-05-16 14:42:57 +00:00
ctx . local . init ( int ( parameter [ 0 ] ) , & v . refs )
2020-05-07 08:54:35 +00:00
}
if parameter [ 1 ] > 0 {
sz := int ( parameter [ 1 ] )
2022-05-16 14:42:57 +00:00
ctx . arguments . init ( sz , & v . refs )
2020-05-07 08:54:35 +00:00
for i := 0 ; i < sz ; i ++ {
2021-11-30 12:03:01 +00:00
ctx . arguments . Set ( i , v . estack . Pop ( ) . Item ( ) , & v . refs )
2020-05-07 08:54:35 +00:00
}
}
case opcode . LDSFLD0 , opcode . LDSFLD1 , opcode . LDSFLD2 , opcode . LDSFLD3 , opcode . LDSFLD4 , opcode . LDSFLD5 , opcode . LDSFLD6 :
2020-07-09 12:00:49 +00:00
item := ctx . static . Get ( int ( op - opcode . LDSFLD0 ) )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( item )
2020-05-07 08:54:35 +00:00
case opcode . LDSFLD :
2020-07-09 12:00:49 +00:00
item := ctx . static . Get ( int ( parameter [ 0 ] ) )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( item )
2020-05-07 08:54:35 +00:00
case opcode . STSFLD0 , opcode . STSFLD1 , opcode . STSFLD2 , opcode . STSFLD3 , opcode . STSFLD4 , opcode . STSFLD5 , opcode . STSFLD6 :
item := v . estack . Pop ( ) . Item ( )
2021-11-30 12:03:01 +00:00
ctx . static . Set ( int ( op - opcode . STSFLD0 ) , item , & v . refs )
2020-05-07 08:54:35 +00:00
case opcode . STSFLD :
item := v . estack . Pop ( ) . Item ( )
2021-11-30 12:03:01 +00:00
ctx . static . Set ( int ( parameter [ 0 ] ) , item , & v . refs )
2020-05-07 08:54:35 +00:00
case opcode . LDLOC0 , opcode . LDLOC1 , opcode . LDLOC2 , opcode . LDLOC3 , opcode . LDLOC4 , opcode . LDLOC5 , opcode . LDLOC6 :
item := ctx . local . Get ( int ( op - opcode . LDLOC0 ) )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( item )
2020-05-07 08:54:35 +00:00
case opcode . LDLOC :
item := ctx . local . Get ( int ( parameter [ 0 ] ) )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( item )
2018-03-30 16:15:06 +00:00
2020-05-07 08:54:35 +00:00
case opcode . STLOC0 , opcode . STLOC1 , opcode . STLOC2 , opcode . STLOC3 , opcode . STLOC4 , opcode . STLOC5 , opcode . STLOC6 :
item := v . estack . Pop ( ) . Item ( )
2021-11-30 12:03:01 +00:00
ctx . local . Set ( int ( op - opcode . STLOC0 ) , item , & v . refs )
2020-05-07 08:54:35 +00:00
case opcode . STLOC :
item := v . estack . Pop ( ) . Item ( )
2021-11-30 12:03:01 +00:00
ctx . local . Set ( int ( parameter [ 0 ] ) , item , & v . refs )
2018-03-30 16:15:06 +00:00
2020-05-07 08:54:35 +00:00
case opcode . LDARG0 , opcode . LDARG1 , opcode . LDARG2 , opcode . LDARG3 , opcode . LDARG4 , opcode . LDARG5 , opcode . LDARG6 :
item := ctx . arguments . Get ( int ( op - opcode . LDARG0 ) )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( item )
2020-05-07 08:54:35 +00:00
case opcode . LDARG :
item := ctx . arguments . Get ( int ( parameter [ 0 ] ) )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( item )
2020-05-07 08:54:35 +00:00
case opcode . STARG0 , opcode . STARG1 , opcode . STARG2 , opcode . STARG3 , opcode . STARG4 , opcode . STARG5 , opcode . STARG6 :
item := v . estack . Pop ( ) . Item ( )
2021-11-30 12:03:01 +00:00
ctx . arguments . Set ( int ( op - opcode . STARG0 ) , item , & v . refs )
2020-05-07 08:54:35 +00:00
case opcode . STARG :
item := v . estack . Pop ( ) . Item ( )
2021-11-30 12:03:01 +00:00
ctx . arguments . Set ( int ( parameter [ 0 ] ) , item , & v . refs )
2018-03-30 16:15:06 +00:00
2020-05-12 11:53:17 +00:00
case opcode . NEWBUFFER :
n := toInt ( v . estack . Pop ( ) . BigInt ( ) )
2020-06-11 13:31:31 +00:00
if n < 0 || n > stackitem . MaxSize {
2020-05-12 11:53:17 +00:00
panic ( "invalid size" )
}
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewBuffer ( make ( [ ] byte , n ) ) )
2020-05-12 11:53:17 +00:00
2020-05-12 11:55:31 +00:00
case opcode . MEMCPY :
n := toInt ( v . estack . Pop ( ) . BigInt ( ) )
if n < 0 {
panic ( "invalid size" )
}
si := toInt ( v . estack . Pop ( ) . BigInt ( ) )
if si < 0 {
panic ( "invalid source index" )
}
src := v . estack . Pop ( ) . Bytes ( )
if sum := si + n ; sum < 0 || sum > len ( src ) {
panic ( "size is too big" )
}
di := toInt ( v . estack . Pop ( ) . BigInt ( ) )
if di < 0 {
panic ( "invalid destination index" )
}
2020-06-03 12:55:06 +00:00
dst := v . estack . Pop ( ) . value . ( * stackitem . Buffer ) . Value ( ) . ( [ ] byte )
2020-08-24 09:52:02 +00:00
if sum := di + n ; sum < 0 || sum > len ( dst ) {
2020-05-12 11:55:31 +00:00
panic ( "size is too big" )
}
copy ( dst [ di : ] , src [ si : si + n ] )
2019-12-03 14:05:06 +00:00
case opcode . CAT :
2019-09-06 06:12:19 +00:00
b := v . estack . Pop ( ) . Bytes ( )
a := v . estack . Pop ( ) . Bytes ( )
2020-08-22 20:36:38 +00:00
l := len ( a ) + len ( b )
if l > stackitem . MaxSize {
2019-10-17 14:01:03 +00:00
panic ( fmt . Sprintf ( "too big item: %d" , l ) )
}
2020-08-22 20:36:38 +00:00
ab := make ( [ ] byte , l )
copy ( ab , a )
copy ( ab [ len ( a ) : ] , b )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewBuffer ( ab ) )
2019-10-18 15:28:08 +00:00
2019-12-03 14:05:06 +00:00
case opcode . SUBSTR :
2022-03-09 14:34:36 +00:00
l := toInt ( v . estack . Pop ( ) . BigInt ( ) )
2019-09-11 09:00:11 +00:00
if l < 0 {
panic ( "negative length" )
}
2022-03-09 14:34:36 +00:00
o := toInt ( v . estack . Pop ( ) . BigInt ( ) )
2019-09-11 09:00:11 +00:00
if o < 0 {
panic ( "negative index" )
}
2019-09-18 11:19:58 +00:00
s := v . estack . Pop ( ) . Bytes ( )
last := l + o
if last > len ( s ) {
2020-04-15 13:16:49 +00:00
panic ( "invalid offset" )
2019-09-11 09:00:11 +00:00
}
2020-08-22 20:36:38 +00:00
res := make ( [ ] byte , l )
copy ( res , s [ o : last ] )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewBuffer ( res ) )
2019-10-18 15:28:08 +00:00
2019-12-03 14:05:06 +00:00
case opcode . LEFT :
2022-03-09 14:34:36 +00:00
l := toInt ( v . estack . Pop ( ) . BigInt ( ) )
2019-09-11 09:03:43 +00:00
if l < 0 {
panic ( "negative length" )
}
2019-09-06 06:12:19 +00:00
s := v . estack . Pop ( ) . Bytes ( )
2019-09-09 14:05:40 +00:00
if t := len ( s ) ; l > t {
2020-05-21 06:43:35 +00:00
panic ( "size is too big" )
2019-09-09 14:05:40 +00:00
}
2020-08-22 20:36:38 +00:00
res := make ( [ ] byte , l )
copy ( res , s [ : l ] )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewBuffer ( res ) )
2019-10-18 15:28:08 +00:00
2019-12-03 14:05:06 +00:00
case opcode . RIGHT :
2022-03-09 14:34:36 +00:00
l := toInt ( v . estack . Pop ( ) . BigInt ( ) )
2019-09-11 09:03:43 +00:00
if l < 0 {
panic ( "negative length" )
}
2019-09-06 06:12:19 +00:00
s := v . estack . Pop ( ) . Bytes ( )
2020-08-22 20:36:38 +00:00
res := make ( [ ] byte , l )
copy ( res , s [ len ( s ) - l : ] )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewBuffer ( res ) )
2019-10-18 15:28:08 +00:00
2019-12-03 14:05:06 +00:00
case opcode . DEPTH :
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewBigInteger ( big . NewInt ( int64 ( v . estack . Len ( ) ) ) ) )
2018-03-30 16:15:06 +00:00
2020-05-06 08:19:58 +00:00
case opcode . DROP :
if v . estack . Len ( ) < 1 {
panic ( "stack is too small" )
}
v . estack . Pop ( )
2019-12-03 14:05:06 +00:00
case opcode . NIP :
2021-08-21 16:09:44 +00:00
if v . estack . Len ( ) < 2 {
2019-09-12 08:09:23 +00:00
panic ( "no second element found" )
}
2021-08-21 16:09:44 +00:00
_ = v . estack . RemoveAt ( 1 )
2018-03-30 16:15:06 +00:00
2020-05-06 08:19:58 +00:00
case opcode . XDROP :
2022-03-09 14:34:36 +00:00
n := toInt ( v . estack . Pop ( ) . BigInt ( ) )
2020-05-06 08:19:58 +00:00
if n < 0 {
panic ( "invalid length" )
}
2021-08-21 16:09:44 +00:00
if v . estack . Len ( ) < n + 1 {
2020-05-06 08:19:58 +00:00
panic ( "bad index" )
}
2021-08-21 16:09:44 +00:00
_ = v . estack . RemoveAt ( n )
2020-05-06 08:19:58 +00:00
2020-05-06 08:32:22 +00:00
case opcode . CLEAR :
v . estack . Clear ( )
2020-05-06 08:19:58 +00:00
case opcode . DUP :
v . estack . Push ( v . estack . Dup ( 0 ) )
2019-12-03 14:05:06 +00:00
case opcode . OVER :
2021-08-21 16:09:44 +00:00
if v . estack . Len ( ) < 2 {
2019-09-05 12:57:44 +00:00
panic ( "no second element found" )
}
2021-08-21 16:09:44 +00:00
a := v . estack . Dup ( 1 )
2018-03-30 16:15:06 +00:00
v . estack . Push ( a )
2019-12-03 14:05:06 +00:00
case opcode . PICK :
2022-03-09 14:34:36 +00:00
n := toInt ( v . estack . Pop ( ) . BigInt ( ) )
2019-09-05 12:18:04 +00:00
if n < 0 {
panic ( "negative stack item returned" )
}
2021-08-21 16:09:44 +00:00
if v . estack . Len ( ) < n + 1 {
2019-09-05 12:57:44 +00:00
panic ( "no nth element found" )
}
2021-08-21 16:09:44 +00:00
a := v . estack . Dup ( n )
2019-09-05 12:18:04 +00:00
v . estack . Push ( a )
2020-05-06 08:19:58 +00:00
case opcode . TUCK :
if v . estack . Len ( ) < 2 {
2021-08-21 16:09:44 +00:00
panic ( "too short stack to TUCK" )
2020-05-06 08:19:58 +00:00
}
2021-08-21 16:09:44 +00:00
a := v . estack . Dup ( 0 )
2020-05-06 08:19:58 +00:00
v . estack . InsertAt ( a , 2 )
case opcode . SWAP :
err := v . estack . Swap ( 1 , 0 )
if err != nil {
panic ( err . Error ( ) )
}
case opcode . ROT :
err := v . estack . Roll ( 2 )
if err != nil {
panic ( err . Error ( ) )
}
2019-12-03 14:05:06 +00:00
case opcode . ROLL :
2022-03-09 14:34:36 +00:00
n := toInt ( v . estack . Pop ( ) . BigInt ( ) )
2019-12-16 16:53:21 +00:00
err := v . estack . Roll ( n )
if err != nil {
panic ( err . Error ( ) )
2018-03-30 16:15:06 +00:00
}
2020-05-06 09:12:29 +00:00
case opcode . REVERSE3 , opcode . REVERSE4 , opcode . REVERSEN :
n := 3
switch op {
case opcode . REVERSE4 :
n = 4
case opcode . REVERSEN :
2022-03-09 14:34:36 +00:00
n = toInt ( v . estack . Pop ( ) . BigInt ( ) )
2020-05-06 09:12:29 +00:00
}
if err := v . estack . ReverseTop ( n ) ; err != nil {
panic ( err . Error ( ) )
}
2018-03-30 16:15:06 +00:00
// Bit operations.
2019-12-03 14:05:06 +00:00
case opcode . INVERT :
2021-08-21 16:09:44 +00:00
i := v . estack . Pop ( ) . BigInt ( )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewBigInteger ( new ( big . Int ) . Not ( i ) ) )
2019-09-05 14:20:53 +00:00
2019-12-03 14:05:06 +00:00
case opcode . AND :
2018-03-30 16:15:06 +00:00
b := v . estack . Pop ( ) . BigInt ( )
a := v . estack . Pop ( ) . BigInt ( )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewBigInteger ( new ( big . Int ) . And ( b , a ) ) )
2018-03-30 16:15:06 +00:00
2019-12-03 14:05:06 +00:00
case opcode . OR :
2018-03-30 16:15:06 +00:00
b := v . estack . Pop ( ) . BigInt ( )
a := v . estack . Pop ( ) . BigInt ( )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewBigInteger ( new ( big . Int ) . Or ( b , a ) ) )
2018-03-30 16:15:06 +00:00
2019-12-03 14:05:06 +00:00
case opcode . XOR :
2018-03-30 16:15:06 +00:00
b := v . estack . Pop ( ) . BigInt ( )
a := v . estack . Pop ( ) . BigInt ( )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewBigInteger ( new ( big . Int ) . Xor ( b , a ) ) )
2018-03-30 16:15:06 +00:00
2020-04-30 07:52:29 +00:00
case opcode . EQUAL , opcode . NOTEQUAL :
2021-08-21 16:09:44 +00:00
if v . estack . Len ( ) < 2 {
panic ( "need a pair of elements on the stack" )
2020-04-28 13:05:11 +00:00
}
2021-08-21 16:09:44 +00:00
b := v . estack . Pop ( )
2020-04-28 13:05:11 +00:00
a := v . estack . Pop ( )
2021-08-28 19:35:43 +00:00
res := stackitem . Bool ( a . value . Equals ( b . value ) == ( op == opcode . EQUAL ) )
v . estack . PushItem ( res )
2020-04-28 13:05:11 +00:00
2018-03-30 16:15:06 +00:00
// Numeric operations.
2020-04-28 13:05:11 +00:00
case opcode . SIGN :
x := v . estack . Pop ( ) . BigInt ( )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewBigInteger ( big . NewInt ( int64 ( x . Sign ( ) ) ) ) )
2020-04-28 13:05:11 +00:00
case opcode . ABS :
x := v . estack . Pop ( ) . BigInt ( )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewBigInteger ( new ( big . Int ) . Abs ( x ) ) )
2020-04-28 13:05:11 +00:00
case opcode . NEGATE :
x := v . estack . Pop ( ) . BigInt ( )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewBigInteger ( new ( big . Int ) . Neg ( x ) ) )
2020-04-28 13:05:11 +00:00
case opcode . INC :
x := v . estack . Pop ( ) . BigInt ( )
2021-11-30 18:10:48 +00:00
a := new ( big . Int ) . Add ( x , bigOne )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewBigInteger ( a ) )
2020-04-28 13:05:11 +00:00
case opcode . DEC :
x := v . estack . Pop ( ) . BigInt ( )
2021-11-30 18:10:48 +00:00
a := new ( big . Int ) . Sub ( x , bigOne )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewBigInteger ( a ) )
2020-04-28 13:05:11 +00:00
2019-12-03 14:05:06 +00:00
case opcode . ADD :
2018-03-30 16:15:06 +00:00
a := v . estack . Pop ( ) . BigInt ( )
b := v . estack . Pop ( ) . BigInt ( )
2019-11-07 09:14:36 +00:00
c := new ( big . Int ) . Add ( a , b )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewBigInteger ( c ) )
2018-03-30 16:15:06 +00:00
2019-12-03 14:05:06 +00:00
case opcode . SUB :
2018-03-30 16:15:06 +00:00
b := v . estack . Pop ( ) . BigInt ( )
a := v . estack . Pop ( ) . BigInt ( )
2019-11-07 09:14:36 +00:00
c := new ( big . Int ) . Sub ( a , b )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewBigInteger ( c ) )
2018-03-30 16:15:06 +00:00
2019-12-03 14:05:06 +00:00
case opcode . MUL :
2018-03-30 16:15:06 +00:00
a := v . estack . Pop ( ) . BigInt ( )
b := v . estack . Pop ( ) . BigInt ( )
2019-11-07 09:14:36 +00:00
c := new ( big . Int ) . Mul ( a , b )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewBigInteger ( c ) )
2018-03-30 16:15:06 +00:00
2020-04-28 13:05:11 +00:00
case opcode . DIV :
b := v . estack . Pop ( ) . BigInt ( )
a := v . estack . Pop ( ) . BigInt ( )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewBigInteger ( new ( big . Int ) . Quo ( a , b ) ) )
2020-04-28 13:05:11 +00:00
2019-12-03 14:05:06 +00:00
case opcode . MOD :
2018-03-30 16:15:06 +00:00
b := v . estack . Pop ( ) . BigInt ( )
a := v . estack . Pop ( ) . BigInt ( )
2019-11-07 09:14:36 +00:00
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewBigInteger ( new ( big . Int ) . Rem ( a , b ) ) )
2018-03-30 16:15:06 +00:00
2021-03-01 09:22:22 +00:00
case opcode . POW :
exp := v . estack . Pop ( ) . BigInt ( )
a := v . estack . Pop ( ) . BigInt ( )
2021-07-19 12:15:14 +00:00
if ei := exp . Uint64 ( ) ; ! exp . IsUint64 ( ) || ei > maxSHLArg {
2021-03-01 09:22:22 +00:00
panic ( "invalid exponent" )
}
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewBigInteger ( new ( big . Int ) . Exp ( a , exp , nil ) ) )
2021-03-01 09:22:22 +00:00
2021-03-01 09:26:15 +00:00
case opcode . SQRT :
a := v . estack . Pop ( ) . BigInt ( )
if a . Sign ( ) == - 1 {
panic ( "negative value" )
}
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewBigInteger ( new ( big . Int ) . Sqrt ( a ) ) )
2021-03-01 09:26:15 +00:00
2022-05-11 06:33:19 +00:00
case opcode . MODMUL :
modulus := v . estack . Pop ( ) . BigInt ( )
if modulus . Sign ( ) == 0 {
panic ( "zero modulus" )
}
x2 := v . estack . Pop ( ) . BigInt ( )
x1 := v . estack . Pop ( ) . BigInt ( )
res := new ( big . Int ) . Mul ( x1 , x2 )
v . estack . PushItem ( stackitem . NewBigInteger ( res . Mod ( res , modulus ) ) )
case opcode . MODPOW :
modulus := v . estack . Pop ( ) . BigInt ( )
exponent := v . estack . Pop ( ) . BigInt ( )
base := v . estack . Pop ( ) . BigInt ( )
res := new ( big . Int )
switch exponent . Cmp ( bigMinusOne ) {
case - 1 :
panic ( "exponent should be >= -1" )
case 0 :
if base . Cmp ( bigZero ) <= 0 {
panic ( "invalid base" )
}
if modulus . Cmp ( bigTwo ) < 0 {
panic ( "invalid modulus" )
}
if res . ModInverse ( base , modulus ) == nil {
panic ( "base and modulus are not relatively prime" )
}
case 1 :
if modulus . Sign ( ) == 0 {
panic ( "zero modulus" ) // https://docs.microsoft.com/en-us/dotnet/api/system.numerics.biginteger.modpow?view=net-6.0#exceptions
}
res . Exp ( base , exponent , modulus )
}
v . estack . PushItem ( stackitem . NewBigInteger ( res ) )
2019-12-03 14:05:06 +00:00
case opcode . SHL , opcode . SHR :
2022-03-09 14:34:36 +00:00
b := toInt ( v . estack . Pop ( ) . BigInt ( ) )
2019-09-18 11:35:29 +00:00
if b == 0 {
2019-09-12 09:02:38 +00:00
return
2020-04-19 16:15:00 +00:00
} else if b < 0 || b > maxSHLArg {
panic ( fmt . Sprintf ( "operand must be between %d and %d" , 0 , maxSHLArg ) )
2019-09-12 09:02:38 +00:00
}
2018-03-30 16:15:06 +00:00
a := v . estack . Pop ( ) . BigInt ( )
2019-11-07 09:14:36 +00:00
var item big . Int
2020-04-19 16:15:00 +00:00
if op == opcode . SHL {
2019-11-07 09:14:36 +00:00
item . Lsh ( a , uint ( b ) )
2019-09-18 11:35:29 +00:00
} else {
2019-11-07 09:14:36 +00:00
item . Rsh ( a , uint ( b ) )
2019-09-12 09:02:38 +00:00
}
2018-03-30 16:15:06 +00:00
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewBigInteger ( & item ) )
2019-11-07 09:14:36 +00:00
2020-04-28 13:05:11 +00:00
case opcode . NOT :
x := v . estack . Pop ( ) . Bool ( )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . Bool ( ! x ) )
2020-04-28 13:05:11 +00:00
2019-12-03 14:05:06 +00:00
case opcode . BOOLAND :
2018-03-30 16:15:06 +00:00
b := v . estack . Pop ( ) . Bool ( )
a := v . estack . Pop ( ) . Bool ( )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . Bool ( a && b ) )
2018-03-30 16:15:06 +00:00
2019-12-03 14:05:06 +00:00
case opcode . BOOLOR :
2018-03-30 16:15:06 +00:00
b := v . estack . Pop ( ) . Bool ( )
a := v . estack . Pop ( ) . Bool ( )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . Bool ( a || b ) )
2018-03-30 16:15:06 +00:00
2020-04-28 13:05:11 +00:00
case opcode . NZ :
x := v . estack . Pop ( ) . BigInt ( )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . Bool ( x . Sign ( ) != 0 ) )
2020-04-28 13:05:11 +00:00
2019-12-03 14:05:06 +00:00
case opcode . NUMEQUAL :
2018-03-30 16:15:06 +00:00
b := v . estack . Pop ( ) . BigInt ( )
a := v . estack . Pop ( ) . BigInt ( )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . Bool ( a . Cmp ( b ) == 0 ) )
2018-03-30 16:15:06 +00:00
2019-12-03 14:05:06 +00:00
case opcode . NUMNOTEQUAL :
2018-03-30 16:15:06 +00:00
b := v . estack . Pop ( ) . BigInt ( )
a := v . estack . Pop ( ) . BigInt ( )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . Bool ( a . Cmp ( b ) != 0 ) )
2018-03-30 16:15:06 +00:00
2021-04-29 11:34:52 +00:00
case opcode . LT , opcode . LE , opcode . GT , opcode . GE :
eb := v . estack . Pop ( )
ea := v . estack . Pop ( )
_ , aNil := ea . Item ( ) . ( stackitem . Null )
_ , bNil := eb . Item ( ) . ( stackitem . Null )
res := ! aNil && ! bNil
if res {
cmp := ea . BigInt ( ) . Cmp ( eb . BigInt ( ) )
switch op {
case opcode . LT :
res = cmp == - 1
case opcode . LE :
res = cmp <= 0
case opcode . GT :
res = cmp == 1
case opcode . GE :
res = cmp >= 0
}
}
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . Bool ( res ) )
2018-03-30 16:15:06 +00:00
2019-12-03 14:05:06 +00:00
case opcode . MIN :
2018-03-30 16:15:06 +00:00
b := v . estack . Pop ( ) . BigInt ( )
a := v . estack . Pop ( ) . BigInt ( )
val := a
if a . Cmp ( b ) == 1 {
val = b
}
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewBigInteger ( val ) )
2018-03-30 16:15:06 +00:00
2019-12-03 14:05:06 +00:00
case opcode . MAX :
2018-03-30 16:15:06 +00:00
b := v . estack . Pop ( ) . BigInt ( )
a := v . estack . Pop ( ) . BigInt ( )
val := a
if a . Cmp ( b ) == - 1 {
val = b
}
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewBigInteger ( val ) )
2018-03-30 16:15:06 +00:00
2019-12-03 14:05:06 +00:00
case opcode . WITHIN :
2018-03-30 16:15:06 +00:00
b := v . estack . Pop ( ) . BigInt ( )
a := v . estack . Pop ( ) . BigInt ( )
x := v . estack . Pop ( ) . BigInt ( )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . Bool ( a . Cmp ( x ) <= 0 && x . Cmp ( b ) == - 1 ) )
2018-03-30 16:15:06 +00:00
2020-04-24 09:31:59 +00:00
// Object operations
case opcode . NEWARRAY0 :
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewArray ( [ ] stackitem . Item { } ) )
2020-04-24 09:31:59 +00:00
2021-07-19 10:35:48 +00:00
case opcode . NEWARRAY , opcode . NEWARRAYT , opcode . NEWSTRUCT :
n := toInt ( v . estack . Pop ( ) . BigInt ( ) )
if n < 0 || n > MaxStackSize {
panic ( "wrong number of elements" )
2019-09-06 16:00:04 +00:00
}
2020-06-03 12:55:06 +00:00
typ := stackitem . AnyT
2020-05-20 14:09:57 +00:00
if op == opcode . NEWARRAYT {
2020-06-03 12:55:06 +00:00
typ = stackitem . Type ( parameter [ 0 ] )
2020-05-20 14:09:57 +00:00
}
items := makeArrayOfType ( int ( n ) , typ )
2021-07-19 10:35:48 +00:00
var res stackitem . Item
if op == opcode . NEWSTRUCT {
res = stackitem . NewStruct ( items )
} else {
res = stackitem . NewArray ( items )
}
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( res )
2018-03-30 16:15:06 +00:00
2020-04-24 09:31:59 +00:00
case opcode . NEWSTRUCT0 :
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewStruct ( [ ] stackitem . Item { } ) )
2020-04-24 09:31:59 +00:00
2019-12-03 14:05:06 +00:00
case opcode . APPEND :
2018-03-30 16:15:06 +00:00
itemElem := v . estack . Pop ( )
arrElem := v . estack . Pop ( )
2021-07-19 12:39:54 +00:00
val := cloneIfStruct ( itemElem . value )
2019-09-18 11:03:15 +00:00
2018-03-30 16:15:06 +00:00
switch t := arrElem . value . ( type ) {
2020-06-03 12:55:06 +00:00
case * stackitem . Array :
t . Append ( val )
case * stackitem . Struct :
t . Append ( val )
2018-03-30 16:15:06 +00:00
default :
panic ( "APPEND: not of underlying type Array" )
}
2020-05-12 13:05:10 +00:00
v . refs . Add ( val )
2019-10-29 10:26:34 +00:00
2021-11-12 11:10:41 +00:00
case opcode . PACKMAP :
n := toInt ( v . estack . Pop ( ) . BigInt ( ) )
if n < 0 || n * 2 > v . estack . Len ( ) {
panic ( "invalid length" )
}
items := make ( [ ] stackitem . MapElement , n )
for i := 0 ; i < n ; i ++ {
key := v . estack . Pop ( )
validateMapKey ( key )
val := v . estack . Pop ( ) . value
items [ i ] . Key = key . value
items [ i ] . Value = val
}
v . estack . PushItem ( stackitem . NewMapWithValue ( items ) )
case opcode . PACKSTRUCT , opcode . PACK :
2021-07-19 10:35:48 +00:00
n := toInt ( v . estack . Pop ( ) . BigInt ( ) )
if n < 0 || n > v . estack . Len ( ) {
2018-03-30 16:15:06 +00:00
panic ( "OPACK: invalid length" )
}
2020-06-03 12:55:06 +00:00
items := make ( [ ] stackitem . Item , n )
2018-03-30 16:15:06 +00:00
for i := 0 ; i < n ; i ++ {
items [ i ] = v . estack . Pop ( ) . value
}
2021-11-12 11:10:41 +00:00
var res stackitem . Item
if op == opcode . PACK {
res = stackitem . NewArray ( items )
} else {
res = stackitem . NewStruct ( items )
}
v . estack . PushItem ( res )
2018-03-30 16:15:06 +00:00
2019-12-03 14:05:06 +00:00
case opcode . UNPACK :
2021-11-12 11:10:41 +00:00
e := v . estack . Pop ( )
var arr [ ] stackitem . Item
var l int
switch t := e . value . ( type ) {
case * stackitem . Array :
arr = t . Value ( ) . ( [ ] stackitem . Item )
case * stackitem . Struct :
arr = t . Value ( ) . ( [ ] stackitem . Item )
case * stackitem . Map :
m := t . Value ( ) . ( [ ] stackitem . MapElement )
l = len ( m )
for i := l - 1 ; i >= 0 ; i -- {
v . estack . PushItem ( m [ i ] . Value )
v . estack . PushItem ( m [ i ] . Key )
}
default :
panic ( "element is not an array/struct/map" )
}
if arr != nil {
l = len ( arr )
for i := l - 1 ; i >= 0 ; i -- {
v . estack . PushItem ( arr [ i ] )
}
2019-09-06 08:32:20 +00:00
}
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewBigInteger ( big . NewInt ( int64 ( l ) ) ) )
2019-09-06 08:32:20 +00:00
2019-12-03 14:05:06 +00:00
case opcode . PICKITEM :
2019-09-24 12:58:31 +00:00
key := v . estack . Pop ( )
validateMapKey ( key )
obj := v . estack . Pop ( )
2018-03-30 16:15:06 +00:00
switch t := obj . value . ( type ) {
2020-06-03 12:55:06 +00:00
// Struct and Array items have their underlying value as []Item.
case * stackitem . Array , * stackitem . Struct :
2021-10-08 13:02:24 +00:00
index := toInt ( key . BigInt ( ) )
2020-06-03 12:55:06 +00:00
arr := t . Value ( ) . ( [ ] stackitem . Item )
2018-03-30 16:15:06 +00:00
if index < 0 || index >= len ( arr ) {
2021-10-08 13:02:24 +00:00
msg := fmt . Sprintf ( "The value %d is out of range." , index )
v . throw ( stackitem . NewByteArray ( [ ] byte ( msg ) ) )
return
2018-03-30 16:15:06 +00:00
}
2019-12-17 14:20:44 +00:00
item := arr [ index ] . Dup ( )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( item )
2020-06-03 12:55:06 +00:00
case * stackitem . Map :
2020-03-31 10:30:38 +00:00
index := t . Index ( key . Item ( ) )
if index < 0 {
2021-10-08 13:02:24 +00:00
v . throw ( stackitem . NewByteArray ( [ ] byte ( "Key not found in Map" ) ) )
return
2019-09-24 12:58:31 +00:00
}
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( t . Value ( ) . ( [ ] stackitem . MapElement ) [ index ] . Value . Dup ( ) )
2018-03-30 16:15:06 +00:00
default :
2021-10-08 13:02:24 +00:00
index := toInt ( key . BigInt ( ) )
2019-09-12 08:19:25 +00:00
arr := obj . Bytes ( )
if index < 0 || index >= len ( arr ) {
2021-10-08 13:02:24 +00:00
msg := fmt . Sprintf ( "The value %d is out of range." , index )
v . throw ( stackitem . NewByteArray ( [ ] byte ( msg ) ) )
return
2019-09-12 08:19:25 +00:00
}
item := arr [ index ]
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewBigInteger ( big . NewInt ( int64 ( item ) ) ) )
2018-03-30 16:15:06 +00:00
}
2019-12-03 14:05:06 +00:00
case opcode . SETITEM :
2019-09-24 12:58:31 +00:00
item := v . estack . Pop ( ) . value
key := v . estack . Pop ( )
validateMapKey ( key )
obj := v . estack . Pop ( )
2018-03-30 16:15:06 +00:00
switch t := obj . value . ( type ) {
2020-06-03 12:55:06 +00:00
// Struct and Array items have their underlying value as []Item.
case * stackitem . Array , * stackitem . Struct :
arr := t . Value ( ) . ( [ ] stackitem . Item )
2021-10-08 13:02:24 +00:00
index := toInt ( key . BigInt ( ) )
2018-03-30 16:15:06 +00:00
if index < 0 || index >= len ( arr ) {
2021-10-08 13:02:24 +00:00
msg := fmt . Sprintf ( "The value %d is out of range." , index )
v . throw ( stackitem . NewByteArray ( [ ] byte ( msg ) ) )
return
2018-03-30 16:15:06 +00:00
}
2020-05-12 13:05:10 +00:00
v . refs . Remove ( arr [ index ] )
2018-03-30 16:15:06 +00:00
arr [ index ] = item
2020-05-12 13:05:10 +00:00
v . refs . Add ( arr [ index ] )
2020-06-03 12:55:06 +00:00
case * stackitem . Map :
2020-05-21 11:57:16 +00:00
if i := t . Index ( key . value ) ; i >= 0 {
2020-06-03 12:55:06 +00:00
v . refs . Remove ( t . Value ( ) . ( [ ] stackitem . MapElement ) [ i ] . Value )
2022-05-16 13:07:25 +00:00
} else {
v . refs . Add ( key . value )
2019-10-17 08:18:15 +00:00
}
2019-09-24 12:58:31 +00:00
t . Add ( key . value , item )
2020-05-12 13:05:10 +00:00
v . refs . Add ( item )
2019-09-24 12:58:31 +00:00
2020-06-03 12:55:06 +00:00
case * stackitem . Buffer :
2020-05-12 11:47:33 +00:00
index := toInt ( key . BigInt ( ) )
2020-06-03 12:55:06 +00:00
if index < 0 || index >= t . Len ( ) {
2021-10-08 13:02:24 +00:00
msg := fmt . Sprintf ( "The value %d is out of range." , index )
v . throw ( stackitem . NewByteArray ( [ ] byte ( msg ) ) )
return
2020-05-12 11:47:33 +00:00
}
bi , err := item . TryInteger ( )
b := toInt ( bi )
if err != nil || b < math . MinInt8 || b > math . MaxUint8 {
panic ( "invalid value" )
}
2020-06-03 12:55:06 +00:00
t . Value ( ) . ( [ ] byte ) [ index ] = byte ( b )
2020-05-12 11:47:33 +00:00
2018-03-30 16:15:06 +00:00
default :
2018-04-02 15:04:42 +00:00
panic ( fmt . Sprintf ( "SETITEM: invalid item type %s" , t ) )
2018-03-30 16:15:06 +00:00
}
2020-04-24 09:24:08 +00:00
case opcode . REVERSEITEMS :
2020-05-12 11:47:33 +00:00
item := v . estack . Pop ( )
switch t := item . value . ( type ) {
2020-06-03 12:55:06 +00:00
case * stackitem . Array , * stackitem . Struct :
a := t . Value ( ) . ( [ ] stackitem . Item )
2020-05-12 11:47:33 +00:00
for i , j := 0 , len ( a ) - 1 ; i < j ; i , j = i + 1 , j - 1 {
2019-09-06 08:32:20 +00:00
a [ i ] , a [ j ] = a [ j ] , a [ i ]
}
2020-06-03 12:55:06 +00:00
case * stackitem . Buffer :
2021-07-18 13:08:23 +00:00
b := t . Value ( ) . ( [ ] byte )
slice . Reverse ( b )
2020-05-12 11:47:33 +00:00
default :
panic ( fmt . Sprintf ( "invalid item type %s" , t ) )
2019-09-06 08:32:20 +00:00
}
2019-12-03 14:05:06 +00:00
case opcode . REMOVE :
2019-09-24 13:16:24 +00:00
key := v . estack . Pop ( )
validateMapKey ( key )
2019-09-12 08:32:09 +00:00
elem := v . estack . Pop ( )
switch t := elem . value . ( type ) {
2020-06-03 12:55:06 +00:00
case * stackitem . Array :
a := t . Value ( ) . ( [ ] stackitem . Item )
2022-03-09 14:34:36 +00:00
k := toInt ( key . BigInt ( ) )
2019-09-24 13:16:24 +00:00
if k < 0 || k >= len ( a ) {
2019-09-12 08:32:09 +00:00
panic ( "REMOVE: invalid index" )
}
2020-05-12 13:05:10 +00:00
v . refs . Remove ( a [ k ] )
2020-06-03 12:55:06 +00:00
t . Remove ( k )
case * stackitem . Struct :
a := t . Value ( ) . ( [ ] stackitem . Item )
2022-03-09 14:34:36 +00:00
k := toInt ( key . BigInt ( ) )
2019-09-24 13:16:24 +00:00
if k < 0 || k >= len ( a ) {
2019-09-12 08:32:09 +00:00
panic ( "REMOVE: invalid index" )
}
2020-05-12 13:05:10 +00:00
v . refs . Remove ( a [ k ] )
2020-06-03 12:55:06 +00:00
t . Remove ( k )
case * stackitem . Map :
2020-03-31 10:30:38 +00:00
index := t . Index ( key . Item ( ) )
// NEO 2.0 doesn't error on missing key.
if index >= 0 {
2022-05-16 13:07:25 +00:00
elems := t . Value ( ) . ( [ ] stackitem . MapElement )
v . refs . Remove ( elems [ index ] . Key )
v . refs . Remove ( elems [ index ] . Value )
2020-03-31 10:30:38 +00:00
t . Drop ( index )
}
2019-09-12 08:32:09 +00:00
default :
panic ( "REMOVE: invalid type" )
}
2019-09-06 08:32:20 +00:00
2020-04-24 10:02:38 +00:00
case opcode . CLEARITEMS :
elem := v . estack . Pop ( )
switch t := elem . value . ( type ) {
2020-06-03 12:55:06 +00:00
case * stackitem . Array :
for _ , item := range t . Value ( ) . ( [ ] stackitem . Item ) {
2020-05-12 13:05:10 +00:00
v . refs . Remove ( item )
2020-04-24 10:02:38 +00:00
}
2020-06-03 12:55:06 +00:00
t . Clear ( )
case * stackitem . Struct :
for _ , item := range t . Value ( ) . ( [ ] stackitem . Item ) {
2020-05-12 13:05:10 +00:00
v . refs . Remove ( item )
2020-04-24 10:02:38 +00:00
}
2020-06-03 12:55:06 +00:00
t . Clear ( )
case * stackitem . Map :
2022-05-16 13:07:25 +00:00
elems := t . Value ( ) . ( [ ] stackitem . MapElement )
for i := range elems {
v . refs . Remove ( elems [ i ] . Key )
v . refs . Remove ( elems [ i ] . Value )
2020-04-24 10:02:38 +00:00
}
2020-06-03 12:55:06 +00:00
t . Clear ( )
2020-04-24 10:02:38 +00:00
default :
panic ( "CLEARITEMS: invalid type" )
}
2021-01-18 13:15:04 +00:00
case opcode . POPITEM :
arr := v . estack . Pop ( ) . Item ( )
elems := arr . Value ( ) . ( [ ] stackitem . Item )
index := len ( elems ) - 1
elem := elems [ index ]
switch item := arr . ( type ) {
case * stackitem . Array :
item . Remove ( index )
case * stackitem . Struct :
item . Remove ( index )
}
v . refs . Remove ( elem )
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( elem )
2021-01-18 13:15:04 +00:00
2020-04-24 09:24:08 +00:00
case opcode . SIZE :
2018-03-30 16:15:06 +00:00
elem := v . estack . Pop ( )
2021-08-28 19:35:43 +00:00
var res int
2022-04-20 18:30:09 +00:00
// Cause there is no native (byte) item type here, we need to check
2018-04-22 18:11:37 +00:00
// the type of the item for array size operations.
2019-10-03 13:12:24 +00:00
switch t := elem . Value ( ) . ( type ) {
2020-06-03 12:55:06 +00:00
case [ ] stackitem . Item :
2021-08-28 19:35:43 +00:00
res = len ( t )
2020-06-03 12:55:06 +00:00
case [ ] stackitem . MapElement :
2021-08-28 19:35:43 +00:00
res = len ( t )
2018-04-22 18:11:37 +00:00
default :
2021-08-28 19:35:43 +00:00
res = len ( elem . Bytes ( ) )
2018-03-30 16:15:06 +00:00
}
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewBigInteger ( big . NewInt ( int64 ( res ) ) ) )
2018-03-30 16:15:06 +00:00
2020-04-23 08:56:36 +00:00
case opcode . JMP , opcode . JMPL , opcode . JMPIF , opcode . JMPIFL , opcode . JMPIFNOT , opcode . JMPIFNOTL ,
opcode . JMPEQ , opcode . JMPEQL , opcode . JMPNE , opcode . JMPNEL ,
opcode . JMPGT , opcode . JMPGTL , opcode . JMPGE , opcode . JMPGEL ,
opcode . JMPLT , opcode . JMPLTL , opcode . JMPLE , opcode . JMPLEL :
2021-02-09 18:42:39 +00:00
offset := getJumpOffset ( ctx , parameter )
2018-03-30 16:15:06 +00:00
cond := true
2020-04-23 08:56:36 +00:00
switch op {
case opcode . JMP , opcode . JMPL :
case opcode . JMPIF , opcode . JMPIFL , opcode . JMPIFNOT , opcode . JMPIFNOTL :
cond = v . estack . Pop ( ) . Bool ( ) == ( op == opcode . JMPIF || op == opcode . JMPIFL )
default :
b := v . estack . Pop ( ) . BigInt ( )
a := v . estack . Pop ( ) . BigInt ( )
cond = getJumpCondition ( op , a , b )
2018-03-30 16:15:06 +00:00
}
2020-07-20 11:51:15 +00:00
if cond {
2021-11-19 13:46:29 +00:00
ctx . Jump ( offset )
2020-07-20 11:51:15 +00:00
}
2020-01-21 10:48:59 +00:00
2020-04-23 08:56:36 +00:00
case opcode . CALL , opcode . CALLL :
2022-04-20 18:30:09 +00:00
// Note: jump offset must be calculated regarding the new context,
2020-07-24 08:56:35 +00:00
// but it is cloned and thus has the same script and instruction pointer.
2021-02-09 18:42:39 +00:00
v . call ( ctx , getJumpOffset ( ctx , parameter ) )
2018-03-30 16:15:06 +00:00
2020-05-06 13:55:30 +00:00
case opcode . CALLA :
2020-06-03 12:55:06 +00:00
ptr := v . estack . Pop ( ) . Item ( ) . ( * stackitem . Pointer )
if ptr . ScriptHash ( ) != ctx . ScriptHash ( ) {
2020-05-06 13:55:30 +00:00
panic ( "invalid script in pointer" )
}
2020-10-05 10:35:53 +00:00
v . call ( ctx , ptr . Position ( ) )
2020-05-06 13:55:30 +00:00
2020-12-29 09:04:53 +00:00
case opcode . CALLT :
id := int32 ( binary . LittleEndian . Uint16 ( parameter ) )
if err := v . LoadToken ( id ) ; err != nil {
panic ( err )
}
2019-12-03 14:05:06 +00:00
case opcode . SYSCALL :
2020-01-20 08:41:40 +00:00
interopID := GetInteropID ( parameter )
2020-07-28 13:38:00 +00:00
err := v . SyscallHandler ( v , interopID )
if err != nil {
2021-03-31 10:52:53 +00:00
panic ( fmt . Sprintf ( "failed to invoke syscall %d: %s" , interopID , err ) )
2018-03-30 16:15:06 +00:00
}
2019-12-03 14:05:06 +00:00
case opcode . RET :
2021-08-28 19:31:08 +00:00
oldCtx := v . istack . Pop ( ) . value . ( * Context )
2019-10-25 14:25:46 +00:00
oldEstack := v . estack
2020-07-22 08:11:28 +00:00
v . unloadContext ( oldCtx )
2018-03-30 16:15:06 +00:00
if v . istack . Len ( ) == 0 {
2020-07-27 14:57:53 +00:00
v . state = HaltState
2019-10-25 14:25:46 +00:00
break
}
newEstack := v . Context ( ) . estack
if oldEstack != newEstack {
2021-11-19 16:36:42 +00:00
if oldCtx . retCount >= 0 && oldEstack . Len ( ) != oldCtx . retCount {
2020-12-29 10:44:07 +00:00
panic ( fmt . Errorf ( "invalid return values count: expected %d, got %d" ,
2021-11-19 16:36:42 +00:00
oldCtx . retCount , oldEstack . Len ( ) ) )
2020-12-29 10:44:07 +00:00
}
2020-07-22 10:25:50 +00:00
rvcount := oldEstack . Len ( )
2019-10-25 14:25:46 +00:00
for i := rvcount ; i > 0 ; i -- {
elem := oldEstack . RemoveAt ( i - 1 )
newEstack . Push ( elem )
}
v . estack = newEstack
2018-03-30 16:15:06 +00:00
}
2019-12-03 14:05:06 +00:00
case opcode . NEWMAP :
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewMap ( ) )
2019-09-24 12:06:23 +00:00
2019-12-03 14:05:06 +00:00
case opcode . KEYS :
2021-08-21 16:09:44 +00:00
if v . estack . Len ( ) == 0 {
2019-09-24 12:41:39 +00:00
panic ( "no argument" )
}
2021-08-21 16:09:44 +00:00
item := v . estack . Pop ( )
2019-09-24 12:41:39 +00:00
2020-06-03 12:55:06 +00:00
m , ok := item . value . ( * stackitem . Map )
2019-09-24 12:41:39 +00:00
if ! ok {
panic ( "not a Map" )
}
2020-06-03 12:55:06 +00:00
arr := make ( [ ] stackitem . Item , 0 , m . Len ( ) )
for k := range m . Value ( ) . ( [ ] stackitem . MapElement ) {
arr = append ( arr , m . Value ( ) . ( [ ] stackitem . MapElement ) [ k ] . Key . Dup ( ) )
2019-09-24 12:41:39 +00:00
}
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewArray ( arr ) )
2019-09-24 12:41:39 +00:00
2019-12-03 14:05:06 +00:00
case opcode . VALUES :
2021-08-21 16:09:44 +00:00
if v . estack . Len ( ) == 0 {
2019-09-24 12:53:55 +00:00
panic ( "no argument" )
}
2021-08-21 16:09:44 +00:00
item := v . estack . Pop ( )
2019-09-24 12:53:55 +00:00
2020-06-03 12:55:06 +00:00
var arr [ ] stackitem . Item
2019-09-24 12:53:55 +00:00
switch t := item . value . ( type ) {
2020-06-03 12:55:06 +00:00
case * stackitem . Array , * stackitem . Struct :
src := t . Value ( ) . ( [ ] stackitem . Item )
arr = make ( [ ] stackitem . Item , len ( src ) )
2019-09-24 12:53:55 +00:00
for i := range src {
2021-07-19 12:39:54 +00:00
arr [ i ] = cloneIfStruct ( src [ i ] )
2019-09-24 12:53:55 +00:00
}
2020-06-03 12:55:06 +00:00
case * stackitem . Map :
arr = make ( [ ] stackitem . Item , 0 , t . Len ( ) )
for k := range t . Value ( ) . ( [ ] stackitem . MapElement ) {
2021-07-19 12:39:54 +00:00
arr = append ( arr , cloneIfStruct ( t . Value ( ) . ( [ ] stackitem . MapElement ) [ k ] . Value ) )
2019-09-24 12:53:55 +00:00
}
default :
panic ( "not a Map, Array or Struct" )
}
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . NewArray ( arr ) )
2019-09-24 12:53:55 +00:00
2019-12-03 14:05:06 +00:00
case opcode . HASKEY :
2021-08-21 16:09:44 +00:00
if v . estack . Len ( ) < 2 {
panic ( "not enough arguments" )
}
2019-09-24 12:25:57 +00:00
key := v . estack . Pop ( )
validateMapKey ( key )
c := v . estack . Pop ( )
2021-08-28 19:35:43 +00:00
var res bool
2019-09-24 12:25:57 +00:00
switch t := c . value . ( type ) {
2020-06-03 12:55:06 +00:00
case * stackitem . Array , * stackitem . Struct :
2022-03-09 14:34:36 +00:00
index := toInt ( key . BigInt ( ) )
2019-09-24 12:25:57 +00:00
if index < 0 {
panic ( "negative index" )
}
2022-03-09 14:34:36 +00:00
res = index < len ( c . Array ( ) )
2020-06-03 12:55:06 +00:00
case * stackitem . Map :
2021-08-28 19:35:43 +00:00
res = t . Has ( key . Item ( ) )
2022-03-10 07:26:07 +00:00
case * stackitem . Buffer , * stackitem . ByteArray :
2022-03-09 14:34:36 +00:00
index := toInt ( key . BigInt ( ) )
2020-05-12 11:47:33 +00:00
if index < 0 {
panic ( "negative index" )
}
2022-03-10 07:26:07 +00:00
res = index < len ( t . Value ( ) . ( [ ] byte ) )
2019-09-24 12:25:57 +00:00
default :
panic ( "wrong collection type" )
}
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( stackitem . Bool ( res ) )
2019-09-24 12:25:57 +00:00
2019-12-03 14:05:06 +00:00
case opcode . NOP :
2018-03-30 16:15:06 +00:00
// unlucky ^^
2019-12-03 14:05:06 +00:00
case opcode . THROW :
2020-07-22 09:05:46 +00:00
v . throw ( v . estack . Pop ( ) . Item ( ) )
2018-03-30 16:15:06 +00:00
2020-05-06 12:54:18 +00:00
case opcode . ABORT :
panic ( "ABORT" )
case opcode . ASSERT :
2018-03-30 16:15:06 +00:00
if ! v . estack . Pop ( ) . Bool ( ) {
2020-05-06 12:54:18 +00:00
panic ( "ASSERT failed" )
2018-03-30 16:15:06 +00:00
}
2020-07-22 09:05:46 +00:00
case opcode . TRY , opcode . TRYL :
catchP , finallyP := getTryParams ( op , parameter )
2020-08-22 21:19:28 +00:00
if ctx . tryStack . Len ( ) >= MaxTryNestingDepth {
panic ( "maximum TRY depth exceeded" )
}
2021-02-09 18:42:39 +00:00
cOffset := getJumpOffset ( ctx , catchP )
fOffset := getJumpOffset ( ctx , finallyP )
2020-08-24 13:20:57 +00:00
if cOffset == ctx . ip && fOffset == ctx . ip {
2020-07-22 09:05:46 +00:00
panic ( "invalid offset for TRY*" )
} else if cOffset == ctx . ip {
cOffset = - 1
} else if fOffset == ctx . ip {
fOffset = - 1
}
eCtx := newExceptionHandlingContext ( cOffset , fOffset )
2021-08-28 19:35:43 +00:00
ctx . tryStack . PushItem ( eCtx )
2020-07-22 09:05:46 +00:00
case opcode . ENDTRY , opcode . ENDTRYL :
eCtx := ctx . tryStack . Peek ( 0 ) . Value ( ) . ( * exceptionHandlingContext )
if eCtx . State == eFinally {
panic ( "invalid exception handling state during ENDTRY*" )
}
2021-02-09 18:42:39 +00:00
eOffset := getJumpOffset ( ctx , parameter )
2020-07-22 09:05:46 +00:00
if eCtx . HasFinally ( ) {
eCtx . State = eFinally
eCtx . EndOffset = eOffset
eOffset = eCtx . FinallyOffset
} else {
ctx . tryStack . Pop ( )
}
2021-11-19 13:46:29 +00:00
ctx . Jump ( eOffset )
2020-07-22 09:05:46 +00:00
case opcode . ENDFINALLY :
if v . uncaughtException != nil {
v . handleException ( )
return
}
eCtx := ctx . tryStack . Pop ( ) . Value ( ) . ( * exceptionHandlingContext )
2021-11-19 13:46:29 +00:00
ctx . Jump ( eCtx . EndOffset )
2020-07-22 09:05:46 +00:00
2018-03-30 16:15:06 +00:00
default :
2019-02-09 15:53:58 +00:00
panic ( fmt . Sprintf ( "unknown opcode %s" , op . String ( ) ) )
2018-03-30 16:15:06 +00:00
}
2019-10-22 10:44:14 +00:00
return
2018-03-30 16:15:06 +00:00
}
2020-07-22 08:11:28 +00:00
func ( v * VM ) unloadContext ( ctx * Context ) {
if ctx . local != nil {
2022-05-16 14:42:57 +00:00
ctx . local . ClearRefs ( & v . refs )
2020-07-22 08:11:28 +00:00
}
if ctx . arguments != nil {
2022-05-16 14:42:57 +00:00
ctx . arguments . ClearRefs ( & v . refs )
2020-07-22 08:11:28 +00:00
}
currCtx := v . Context ( )
2022-05-17 11:41:07 +00:00
if ctx . static != nil && ( currCtx == nil || ctx . static != currCtx . static ) {
2022-05-16 14:42:57 +00:00
ctx . static . ClearRefs ( & v . refs )
2020-07-22 08:11:28 +00:00
}
2022-05-23 05:42:10 +00:00
if ctx . isWrapped { // In case of CALL, CALLA, CALLL we don't need to commit/discard changes, unwrap DAO and change notificationsCount.
2022-05-19 08:35:04 +00:00
if v . uncaughtException == nil {
if v . commitChanges != nil {
if err := v . commitChanges ( ) ; err != nil {
// TODO: return an error instead?
panic ( fmt . Errorf ( "failed to commit changes: %w" , err ) )
}
}
} else {
if v . revertChanges != nil {
v . revertChanges ( * ctx . notificationsCount )
}
}
}
2022-05-23 05:42:10 +00:00
if currCtx != nil && ctx . persistNotificationsCountOnUnloading && ! ( ctx . isWrapped && v . uncaughtException != nil ) {
* currCtx . notificationsCount += * ctx . notificationsCount
}
2022-05-23 08:35:01 +00:00
if currCtx != nil && ctx . onUnload != nil {
if v . uncaughtException == nil {
ctx . onUnload ( currCtx . Estack ( ) ) // Use the estack of current context.
}
ctx . onUnload = nil
}
2020-07-22 08:11:28 +00:00
}
2020-07-22 09:05:46 +00:00
// getTryParams splits TRY(L) instruction parameter into offsets for catch and finally blocks.
func getTryParams ( op opcode . Opcode , p [ ] byte ) ( [ ] byte , [ ] byte ) {
i := 1
if op == opcode . TRYL {
i = 4
}
return p [ : i ] , p [ i : ]
}
2021-05-12 20:17:03 +00:00
// getJumpCondition performs opcode specific comparison of a and b.
2020-04-23 08:56:36 +00:00
func getJumpCondition ( op opcode . Opcode , a , b * big . Int ) bool {
cmp := a . Cmp ( b )
switch op {
case opcode . JMPEQ , opcode . JMPEQL :
return cmp == 0
case opcode . JMPNE , opcode . JMPNEL :
return cmp != 0
case opcode . JMPGT , opcode . JMPGTL :
return cmp > 0
case opcode . JMPGE , opcode . JMPGEL :
return cmp >= 0
case opcode . JMPLT , opcode . JMPLTL :
return cmp < 0
case opcode . JMPLE , opcode . JMPLEL :
return cmp <= 0
default :
panic ( fmt . Sprintf ( "invalid JMP* opcode: %s" , op ) )
}
}
2020-07-22 09:05:46 +00:00
func ( v * VM ) throw ( item stackitem . Item ) {
v . uncaughtException = item
v . handleException ( )
}
2022-04-20 18:30:09 +00:00
// Call calls a method by offset using the new execution context.
2021-11-19 14:15:30 +00:00
func ( v * VM ) Call ( offset int ) {
v . call ( v . Context ( ) , offset )
2020-10-05 10:35:53 +00:00
}
// call is an internal representation of Call, which does not
2022-04-20 18:30:09 +00:00
// affect the invocation counter and is only used by vm
2020-10-05 10:35:53 +00:00
// package.
func ( v * VM ) call ( ctx * Context , offset int ) {
2020-10-15 13:11:26 +00:00
v . checkInvocationStackSize ( )
2020-07-24 08:56:35 +00:00
newCtx := ctx . Copy ( )
2021-11-19 16:36:42 +00:00
newCtx . retCount = - 1
2020-07-24 08:56:35 +00:00
newCtx . local = nil
newCtx . arguments = nil
2022-02-04 07:39:38 +00:00
// If memory for `elems` is reused, we can end up
2022-04-20 18:30:09 +00:00
// with an incorrect exception context state in the caller.
2022-02-04 07:39:38 +00:00
newCtx . tryStack . elems = nil
2021-08-20 20:11:59 +00:00
initStack ( & newCtx . tryStack , "exception" , nil )
2021-01-19 08:23:39 +00:00
newCtx . NEF = ctx . NEF
2022-05-19 08:35:04 +00:00
// Use exactly the same counter and don't use v.wrapDao() for this context.
// Unloading of such unwrapped context will be properly handled inside
// unloadContext without unnecessary DAO unwrapping and notificationsCount changes.
newCtx . notificationsCount = ctx . notificationsCount
newCtx . isWrapped = false
2022-05-23 05:42:10 +00:00
newCtx . persistNotificationsCountOnUnloading = false
2022-05-23 08:35:01 +00:00
newCtx . onUnload = nil
2021-08-28 19:35:43 +00:00
v . istack . PushItem ( newCtx )
2021-11-19 13:46:29 +00:00
newCtx . Jump ( offset )
2020-07-24 08:56:35 +00:00
}
2022-04-20 18:30:09 +00:00
// getJumpOffset returns an instruction number in the current context
// to which JMP should be performed.
2020-04-23 08:56:36 +00:00
// parameter should have length either 1 or 4 and
// is interpreted as little-endian.
2021-02-09 18:42:39 +00:00
func getJumpOffset ( ctx * Context , parameter [ ] byte ) int {
offset , _ , err := calcJumpOffset ( ctx , parameter )
2020-06-26 16:27:14 +00:00
if err != nil {
panic ( err )
}
return offset
}
2022-04-20 18:30:09 +00:00
// calcJumpOffset returns an absolute and a relative offset of JMP/CALL/TRY instructions
// either in a short (1-byte) or a long (4-byte) form.
2021-02-09 18:42:39 +00:00
func calcJumpOffset ( ctx * Context , parameter [ ] byte ) ( int , int , error ) {
2020-04-23 08:56:36 +00:00
var rOffset int32
switch l := len ( parameter ) ; l {
case 1 :
rOffset = int32 ( int8 ( parameter [ 0 ] ) )
case 4 :
rOffset = int32 ( binary . LittleEndian . Uint32 ( parameter ) )
default :
2020-07-22 09:05:46 +00:00
_ , curr := ctx . CurrInstr ( )
return 0 , 0 , fmt . Errorf ( "invalid %s parameter length: %d" , curr , l )
2020-04-23 08:56:36 +00:00
}
2020-06-26 16:17:17 +00:00
offset := ctx . ip + int ( rOffset )
2020-01-21 10:48:59 +00:00
if offset < 0 || offset > len ( ctx . prog ) {
2020-06-26 16:27:14 +00:00
return 0 , 0 , fmt . Errorf ( "invalid offset %d ip at %d" , offset , ctx . ip )
2020-01-21 10:48:59 +00:00
}
2020-06-26 16:27:14 +00:00
return offset , int ( rOffset ) , nil
2020-01-21 10:48:59 +00:00
}
2020-07-22 09:05:46 +00:00
func ( v * VM ) handleException ( ) {
2021-08-21 16:09:44 +00:00
for pop := 0 ; pop < v . istack . Len ( ) ; pop ++ {
ictxv := v . istack . Peek ( pop )
2021-08-28 19:31:08 +00:00
ictx := ictxv . value . ( * Context )
2021-08-21 16:09:44 +00:00
for j := 0 ; j < ictx . tryStack . Len ( ) ; j ++ {
e := ictx . tryStack . Peek ( j )
2020-07-22 09:05:46 +00:00
ectx := e . Value ( ) . ( * exceptionHandlingContext )
if ectx . State == eFinally || ( ectx . State == eCatch && ! ectx . HasFinally ( ) ) {
ictx . tryStack . Pop ( )
2021-08-21 16:09:44 +00:00
j = - 1
2020-07-22 09:05:46 +00:00
continue
}
for i := 0 ; i < pop ; i ++ {
2021-08-28 19:31:08 +00:00
ctx := v . istack . Pop ( ) . value . ( * Context )
2020-07-22 09:05:46 +00:00
v . unloadContext ( ctx )
}
if ectx . State == eTry && ectx . HasCatch ( ) {
ectx . State = eCatch
2021-08-28 19:35:43 +00:00
v . estack . PushItem ( v . uncaughtException )
2020-07-22 09:05:46 +00:00
v . uncaughtException = nil
2021-11-19 13:46:29 +00:00
ictx . Jump ( ectx . CatchOffset )
2020-07-22 09:05:46 +00:00
} else {
ectx . State = eFinally
2021-11-19 13:46:29 +00:00
ictx . Jump ( ectx . FinallyOffset )
2020-07-22 09:05:46 +00:00
}
return
}
}
2021-03-31 14:17:54 +00:00
throwUnhandledException ( v . uncaughtException )
}
2022-04-20 18:30:09 +00:00
// throwUnhandledException gets an exception message from the provided stackitem and panics.
2021-03-31 14:17:54 +00:00
func throwUnhandledException ( item stackitem . Item ) {
msg := "unhandled exception"
switch item . Type ( ) {
case stackitem . ArrayT :
if arr := item . Value ( ) . ( [ ] stackitem . Item ) ; len ( arr ) > 0 {
data , err := arr [ 0 ] . TryBytes ( )
if err == nil {
msg = fmt . Sprintf ( "%s: %q" , msg , string ( data ) )
}
}
default :
data , err := item . TryBytes ( )
if err == nil {
msg = fmt . Sprintf ( "%s: %q" , msg , string ( data ) )
}
}
panic ( msg )
2020-07-22 09:05:46 +00:00
}
2022-04-20 18:30:09 +00:00
// CheckMultisigPar checks if the sigs contains sufficient valid signatures.
2020-07-13 09:59:41 +00:00
func CheckMultisigPar ( v * VM , curve elliptic . Curve , h [ ] byte , pkeys [ ] [ ] byte , sigs [ ] [ ] byte ) bool {
2020-02-05 14:07:03 +00:00
if len ( sigs ) == 1 {
2020-07-13 09:59:41 +00:00
return checkMultisig1 ( v , curve , h , pkeys , sigs [ 0 ] )
2020-02-05 14:07:03 +00:00
}
k1 , k2 := 0 , len ( pkeys ) - 1
s1 , s2 := 0 , len ( sigs ) - 1
type task struct {
pub * keys . PublicKey
signum int
}
type verify struct {
ok bool
signum int
}
worker := func ( ch <- chan task , result chan verify ) {
for {
t , ok := <- ch
if ! ok {
return
}
result <- verify {
signum : t . signum ,
2020-04-17 14:03:07 +00:00
ok : t . pub . Verify ( sigs [ t . signum ] , h ) ,
2020-02-05 14:07:03 +00:00
}
}
}
const workerCount = 3
tasks := make ( chan task , 2 )
results := make ( chan verify , len ( sigs ) )
for i := 0 ; i < workerCount ; i ++ {
go worker ( tasks , results )
}
2020-09-10 11:43:24 +00:00
tasks <- task { pub : bytesToPublicKey ( pkeys [ k1 ] , curve ) , signum : s1 }
tasks <- task { pub : bytesToPublicKey ( pkeys [ k2 ] , curve ) , signum : s2 }
2020-02-05 14:07:03 +00:00
sigok := true
taskCount := 2
loop :
for r := range results {
2020-01-08 10:03:55 +00:00
goingForward := true
2020-02-05 14:07:03 +00:00
taskCount --
2020-01-08 10:03:55 +00:00
if r . signum == s2 {
goingForward = false
}
if k1 + 1 == k2 {
sigok = r . ok && s1 + 1 == s2
if taskCount != 0 && sigok {
continue
}
break loop
} else if r . ok {
if s1 + 1 == s2 {
2020-02-05 14:07:03 +00:00
if taskCount != 0 && sigok {
continue
}
break loop
2020-01-08 10:03:55 +00:00
}
if goingForward {
2020-02-05 14:07:03 +00:00
s1 ++
2020-01-08 10:03:55 +00:00
} else {
s2 --
2020-02-05 14:07:03 +00:00
}
2020-01-08 10:03:55 +00:00
}
var nextSig , nextKey int
if goingForward {
2020-02-05 14:07:03 +00:00
k1 ++
2020-01-08 10:03:55 +00:00
nextSig = s1
nextKey = k1
2020-02-05 14:07:03 +00:00
} else {
k2 --
2020-01-08 10:03:55 +00:00
nextSig = s2
nextKey = k2
2020-02-05 14:07:03 +00:00
}
2020-01-08 10:03:55 +00:00
taskCount ++
2020-09-10 11:43:24 +00:00
tasks <- task { pub : bytesToPublicKey ( pkeys [ nextKey ] , curve ) , signum : nextSig }
2020-02-05 14:07:03 +00:00
}
close ( tasks )
return sigok
}
2020-07-13 09:59:41 +00:00
func checkMultisig1 ( v * VM , curve elliptic . Curve , h [ ] byte , pkeys [ ] [ ] byte , sig [ ] byte ) bool {
2020-02-05 14:07:03 +00:00
for i := range pkeys {
2020-09-10 11:43:24 +00:00
pkey := bytesToPublicKey ( pkeys [ i ] , curve )
2020-04-17 14:03:07 +00:00
if pkey . Verify ( sig , h ) {
2020-02-05 14:07:03 +00:00
return true
}
}
return false
}
2021-07-19 12:39:54 +00:00
func cloneIfStruct ( item stackitem . Item ) stackitem . Item {
2019-09-24 12:53:55 +00:00
switch it := item . ( type ) {
2020-06-03 12:55:06 +00:00
case * stackitem . Struct :
2021-07-19 12:39:54 +00:00
ret , err := it . Clone ( )
2021-07-12 08:51:49 +00:00
if err != nil {
panic ( err )
}
return ret
2019-09-24 12:53:55 +00:00
default :
2021-07-12 08:51:49 +00:00
return it
2019-09-24 12:53:55 +00:00
}
}
2020-06-03 12:55:06 +00:00
func makeArrayOfType ( n int , typ stackitem . Type ) [ ] stackitem . Item {
2020-04-24 10:48:19 +00:00
if ! typ . IsValid ( ) {
panic ( fmt . Sprintf ( "invalid stack item type: %d" , typ ) )
}
2020-06-03 12:55:06 +00:00
items := make ( [ ] stackitem . Item , n )
2019-09-12 08:24:10 +00:00
for i := range items {
2020-04-24 10:48:19 +00:00
switch typ {
2020-06-03 12:55:06 +00:00
case stackitem . BooleanT :
items [ i ] = stackitem . NewBool ( false )
case stackitem . IntegerT :
items [ i ] = stackitem . NewBigInteger ( big . NewInt ( 0 ) )
case stackitem . ByteArrayT :
items [ i ] = stackitem . NewByteArray ( [ ] byte { } )
2020-04-24 10:48:19 +00:00
default :
2020-06-03 12:55:06 +00:00
items [ i ] = stackitem . Null { }
2020-04-24 10:48:19 +00:00
}
2019-09-12 08:24:10 +00:00
}
return items
}
2021-08-21 16:09:44 +00:00
func validateMapKey ( key Element ) {
item := key . Item ( )
if item == nil {
2019-09-24 12:25:57 +00:00
panic ( "no key found" )
}
2021-08-21 16:09:44 +00:00
if err := stackitem . IsValidMapKey ( item ) ; err != nil {
2020-09-29 07:56:57 +00:00
panic ( err )
2019-09-24 12:25:57 +00:00
}
}
2019-10-29 08:01:06 +00:00
func ( v * VM ) checkInvocationStackSize ( ) {
2021-08-21 16:09:44 +00:00
if v . istack . Len ( ) >= MaxInvocationStackSize {
2019-10-29 08:01:06 +00:00
panic ( "invocation stack is too big" )
}
}
2019-11-07 09:14:36 +00:00
2019-12-10 16:13:29 +00:00
// bytesToPublicKey is a helper deserializing keys using cache and panicing on
// error.
2020-09-10 11:43:24 +00:00
func bytesToPublicKey ( b [ ] byte , curve elliptic . Curve ) * keys . PublicKey {
pkey , err := keys . NewPublicKeyFromBytes ( b , curve )
if err != nil {
panic ( err . Error ( ) )
2019-12-10 16:13:29 +00:00
}
return pkey
}
2020-04-30 13:00:33 +00:00
2022-04-20 18:30:09 +00:00
// GetCallingScriptHash implements the ScriptHashGetter interface.
2020-04-30 13:00:33 +00:00
func ( v * VM ) GetCallingScriptHash ( ) util . Uint160 {
2020-06-23 18:39:26 +00:00
return v . Context ( ) . callingScriptHash
2020-04-30 13:00:33 +00:00
}
2022-04-20 18:30:09 +00:00
// GetEntryScriptHash implements the ScriptHashGetter interface.
2020-04-30 13:00:33 +00:00
func ( v * VM ) GetEntryScriptHash ( ) util . Uint160 {
2021-08-21 16:09:44 +00:00
return v . getContextScriptHash ( v . istack . Len ( ) - 1 )
2020-04-30 13:00:33 +00:00
}
2022-04-20 18:30:09 +00:00
// GetCurrentScriptHash implements the ScriptHashGetter interface.
2020-04-30 13:00:33 +00:00
func ( v * VM ) GetCurrentScriptHash ( ) util . Uint160 {
2020-05-04 08:41:41 +00:00
return v . getContextScriptHash ( 0 )
2020-04-30 13:00:33 +00:00
}
2020-05-12 11:47:33 +00:00
// toInt converts an item to a 32-bit int.
func toInt ( i * big . Int ) int {
if ! i . IsInt64 ( ) {
panic ( "not an int32" )
}
n := i . Int64 ( )
if n < math . MinInt32 || n > math . MaxInt32 {
panic ( "not an int32" )
}
return int ( n )
}