github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/vm/context.go (about)

     1  package vm
     2  
     3  import (
     4  	"encoding/binary"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  
     9  	"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
    10  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
    11  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
    12  	"github.com/nspcc-dev/neo-go/pkg/util"
    13  	"github.com/nspcc-dev/neo-go/pkg/vm/invocations"
    14  	"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
    15  	"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
    16  )
    17  
    18  // scriptContext is a part of the Context that is shared between multiple Contexts,
    19  // it's created when a new script is loaded into the VM while regular
    20  // CALL/CALLL/CALLA internal invocations reuse it.
    21  type scriptContext struct {
    22  	// The raw program script.
    23  	prog []byte
    24  
    25  	// Breakpoints.
    26  	breakPoints []int
    27  
    28  	// Evaluation stack pointer.
    29  	estack *Stack
    30  
    31  	static slot
    32  
    33  	// Script hash of the prog.
    34  	scriptHash util.Uint160
    35  
    36  	// Caller's contract script hash.
    37  	callingScriptHash util.Uint160
    38  
    39  	// Caller's scriptContext, if not entry.
    40  	callingContext *scriptContext
    41  
    42  	// Call flags this context was created with.
    43  	callFlag callflag.CallFlag
    44  
    45  	// NEF represents a NEF file for the current contract.
    46  	NEF *nef.File
    47  	// invTree is an invocation tree (or a branch of it) for this context.
    48  	invTree *invocations.Tree
    49  	// onUnload is a callback that should be called after current context unloading
    50  	// if no exception occurs.
    51  	onUnload ContextUnloadCallback
    52  }
    53  
    54  // Context represents the current execution context of the VM.
    55  type Context struct {
    56  	// Instruction pointer.
    57  	ip int
    58  
    59  	// The next instruction pointer.
    60  	nextip int
    61  
    62  	sc *scriptContext
    63  
    64  	local     slot
    65  	arguments slot
    66  
    67  	// Exception context stack.
    68  	tryStack Stack
    69  
    70  	// retCount specifies the number of return values.
    71  	retCount int
    72  }
    73  
    74  type contextAux struct {
    75  	Script string
    76  	IP     int
    77  	NextIP int
    78  	Caller string
    79  }
    80  
    81  // ContextUnloadCallback is a callback method used on context unloading from istack.
    82  type ContextUnloadCallback func(v *VM, ctx *Context, commit bool) error
    83  
    84  var errNoInstParam = errors.New("failed to read instruction parameter")
    85  
    86  // ErrMultiRet is returned when caller does not expect multiple return values
    87  // from callee.
    88  var ErrMultiRet = errors.New("multiple return values in a cross-contract call")
    89  
    90  // NewContext returns a new Context object.
    91  func NewContext(b []byte) *Context {
    92  	return NewContextWithParams(b, -1, 0)
    93  }
    94  
    95  // NewContextWithParams creates new Context objects using script, parameter count,
    96  // return value count and initial position in script.
    97  func NewContextWithParams(b []byte, rvcount int, pos int) *Context {
    98  	return &Context{
    99  		sc: &scriptContext{
   100  			prog: b,
   101  		},
   102  		retCount: rvcount,
   103  		nextip:   pos,
   104  	}
   105  }
   106  
   107  // Estack returns the evaluation stack of c.
   108  func (c *Context) Estack() *Stack {
   109  	return c.sc.estack
   110  }
   111  
   112  // NextIP returns the next instruction pointer.
   113  func (c *Context) NextIP() int {
   114  	return c.nextip
   115  }
   116  
   117  // Jump unconditionally moves the next instruction pointer to the specified location.
   118  func (c *Context) Jump(pos int) {
   119  	if pos < 0 || pos >= len(c.sc.prog) {
   120  		panic("instruction offset is out of range")
   121  	}
   122  	c.nextip = pos
   123  }
   124  
   125  // Next returns the next instruction to execute with its parameter if any.
   126  // The parameter is not copied and shouldn't be written to. After its invocation,
   127  // the instruction pointer points to the instruction returned.
   128  func (c *Context) Next() (opcode.Opcode, []byte, error) {
   129  	var err error
   130  
   131  	c.ip = c.nextip
   132  	prog := c.sc.prog
   133  	if c.ip >= len(prog) {
   134  		return opcode.RET, nil, nil
   135  	}
   136  
   137  	var instrbyte = prog[c.ip]
   138  	instr := opcode.Opcode(instrbyte)
   139  	if !opcode.IsValid(instr) {
   140  		return instr, nil, fmt.Errorf("incorrect opcode %s", instr.String())
   141  	}
   142  	c.nextip++
   143  
   144  	var numtoread int
   145  	switch instr {
   146  	case opcode.PUSHDATA1:
   147  		if c.nextip >= len(prog) {
   148  			err = errNoInstParam
   149  		} else {
   150  			numtoread = int(prog[c.nextip])
   151  			c.nextip++
   152  		}
   153  	case opcode.PUSHDATA2:
   154  		if c.nextip+1 >= len(prog) {
   155  			err = errNoInstParam
   156  		} else {
   157  			numtoread = int(binary.LittleEndian.Uint16(prog[c.nextip : c.nextip+2]))
   158  			c.nextip += 2
   159  		}
   160  	case opcode.PUSHDATA4:
   161  		if c.nextip+3 >= len(prog) {
   162  			err = errNoInstParam
   163  		} else {
   164  			var n = binary.LittleEndian.Uint32(prog[c.nextip : c.nextip+4])
   165  			if n > stackitem.MaxSize {
   166  				return instr, nil, errors.New("parameter is too big")
   167  			}
   168  			numtoread = int(n)
   169  			c.nextip += 4
   170  		}
   171  	case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.JMPEQ, opcode.JMPNE,
   172  		opcode.JMPGT, opcode.JMPGE, opcode.JMPLT, opcode.JMPLE,
   173  		opcode.CALL, opcode.ISTYPE, opcode.CONVERT, opcode.NEWARRAYT,
   174  		opcode.ENDTRY,
   175  		opcode.INITSSLOT, opcode.LDSFLD, opcode.STSFLD, opcode.LDARG, opcode.STARG, opcode.LDLOC, opcode.STLOC:
   176  		numtoread = 1
   177  	case opcode.INITSLOT, opcode.TRY, opcode.CALLT:
   178  		numtoread = 2
   179  	case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL, opcode.JMPEQL, opcode.JMPNEL,
   180  		opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLTL, opcode.JMPLEL,
   181  		opcode.ENDTRYL,
   182  		opcode.CALLL, opcode.SYSCALL, opcode.PUSHA:
   183  		numtoread = 4
   184  	case opcode.TRYL:
   185  		numtoread = 8
   186  	default:
   187  		if instr <= opcode.PUSHINT256 {
   188  			numtoread = 1 << instr
   189  		} else {
   190  			// No parameters, can just return.
   191  			return instr, nil, nil
   192  		}
   193  	}
   194  	if c.nextip+numtoread-1 >= len(prog) {
   195  		err = errNoInstParam
   196  	}
   197  	if err != nil {
   198  		return instr, nil, err
   199  	}
   200  	parameter := prog[c.nextip : c.nextip+numtoread]
   201  	c.nextip += numtoread
   202  	return instr, parameter, nil
   203  }
   204  
   205  // IP returns the current instruction offset in the context script.
   206  func (c *Context) IP() int {
   207  	return c.ip
   208  }
   209  
   210  // LenInstr returns the number of instructions loaded.
   211  func (c *Context) LenInstr() int {
   212  	return len(c.sc.prog)
   213  }
   214  
   215  // CurrInstr returns the current instruction and opcode.
   216  func (c *Context) CurrInstr() (int, opcode.Opcode) {
   217  	return c.ip, opcode.Opcode(c.sc.prog[c.ip])
   218  }
   219  
   220  // NextInstr returns the next instruction and opcode.
   221  func (c *Context) NextInstr() (int, opcode.Opcode) {
   222  	op := opcode.RET
   223  	if c.nextip < len(c.sc.prog) {
   224  		op = opcode.Opcode(c.sc.prog[c.nextip])
   225  	}
   226  	return c.nextip, op
   227  }
   228  
   229  // GetCallFlags returns the calling flags which the context was created with.
   230  func (c *Context) GetCallFlags() callflag.CallFlag {
   231  	return c.sc.callFlag
   232  }
   233  
   234  // Program returns the loaded program.
   235  func (c *Context) Program() []byte {
   236  	return c.sc.prog
   237  }
   238  
   239  // ScriptHash returns a hash of the script in the current context.
   240  func (c *Context) ScriptHash() util.Uint160 {
   241  	if c.sc.scriptHash.Equals(util.Uint160{}) {
   242  		c.sc.scriptHash = hash.Hash160(c.sc.prog)
   243  	}
   244  	return c.sc.scriptHash
   245  }
   246  
   247  // GetNEF returns NEF structure used by this context if it's present.
   248  func (c *Context) GetNEF() *nef.File {
   249  	return c.sc.NEF
   250  }
   251  
   252  // NumOfReturnVals returns the number of return values expected from this context.
   253  func (c *Context) NumOfReturnVals() int {
   254  	return c.retCount
   255  }
   256  
   257  func (c *Context) atBreakPoint() bool {
   258  	for _, n := range c.sc.breakPoints {
   259  		if n == c.nextip {
   260  			return true
   261  		}
   262  	}
   263  	return false
   264  }
   265  
   266  // IsDeployed returns whether this context contains a deployed contract.
   267  func (c *Context) IsDeployed() bool {
   268  	return c.sc.NEF != nil
   269  }
   270  
   271  // DumpStaticSlot returns json formatted representation of the given slot.
   272  func (c *Context) DumpStaticSlot() string {
   273  	return dumpSlot(&c.sc.static)
   274  }
   275  
   276  // DumpLocalSlot returns json formatted representation of the given slot.
   277  func (c *Context) DumpLocalSlot() string {
   278  	return dumpSlot(&c.local)
   279  }
   280  
   281  // DumpArgumentsSlot returns json formatted representation of the given slot.
   282  func (c *Context) DumpArgumentsSlot() string {
   283  	return dumpSlot(&c.arguments)
   284  }
   285  
   286  // dumpSlot returns json formatted representation of the given slot.
   287  func dumpSlot(s *slot) string {
   288  	if s == nil || *s == nil {
   289  		return "[]"
   290  	}
   291  	b, _ := json.MarshalIndent(s, "", "    ")
   292  	return string(b)
   293  }
   294  
   295  // getContextScriptHash returns script hash of the invocation stack element
   296  // number n.
   297  func (v *VM) getContextScriptHash(n int) util.Uint160 {
   298  	if len(v.istack) <= n {
   299  		return util.Uint160{}
   300  	}
   301  	return v.istack[len(v.istack)-1-n].ScriptHash()
   302  }
   303  
   304  // IsCalledByEntry checks parent script contexts and return true if the current one
   305  // is an entry script (the first loaded into the VM) or one called by it.
   306  func (c *Context) IsCalledByEntry() bool {
   307  	return c.sc.callingContext == nil || c.sc.callingContext.callingContext == nil
   308  }
   309  
   310  // PushContextScriptHash pushes the script hash of the
   311  // invocation stack element number n to the evaluation stack.
   312  func (v *VM) PushContextScriptHash(n int) error {
   313  	h := v.getContextScriptHash(n)
   314  	v.Estack().PushItem(stackitem.NewByteArray(h.BytesBE()))
   315  	return nil
   316  }
   317  
   318  // MarshalJSON implements the JSON marshalling interface.
   319  func (c *Context) MarshalJSON() ([]byte, error) {
   320  	var aux = contextAux{
   321  		Script: c.ScriptHash().StringLE(),
   322  		IP:     c.ip,
   323  		NextIP: c.nextip,
   324  		Caller: c.sc.callingScriptHash.StringLE(),
   325  	}
   326  	return json.Marshal(aux)
   327  }
   328  
   329  // DynamicOnUnload implements OnUnload script for dynamic calls, if no exception
   330  // has occurred it checks that the context has exactly 0 (in which case a `Null`
   331  // is pushed) or 1 returned value.
   332  func DynamicOnUnload(v *VM, ctx *Context, commit bool) error {
   333  	if commit {
   334  		eLen := ctx.Estack().Len()
   335  		if eLen == 0 { // No return value, add one.
   336  			v.Context().Estack().PushItem(stackitem.Null{}) // Must use current context stack.
   337  		} else if eLen > 1 { // Only one can be returned.
   338  			return ErrMultiRet
   339  		} // One value returned, it's OK.
   340  	}
   341  	return nil
   342  }
   343  
   344  // BreakPoints returns the current set of Context's breakpoints.
   345  func (c *Context) BreakPoints() []int {
   346  	res := make([]int, len(c.sc.breakPoints))
   347  	copy(res, c.sc.breakPoints)
   348  	return res
   349  }