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 }