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