-
Notifications
You must be signed in to change notification settings - Fork 79
/
context.go
318 lines (270 loc) · 7.98 KB
/
context.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
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/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 number of return values.
retCount int
// NEF represents NEF file for the current contract.
NEF *nef.File
// invTree is an invocation tree (or branch of it) for this context.
invTree *InvocationTree
}
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 next instruction pointer.
func (c *Context) NextIP() int {
return c.nextip
}
// Jump unconditionally moves the next instruction pointer to specified location.
func (c *Context) Jump(pos int) {
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 being returned.
func (c *Context) Next() (opcode.Opcode, []byte, error) {
var err error
c.ip = c.nextip
if c.ip < 0 {
return 0, nil, errors.New("invalid instruction offset")
}
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 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 calling flags 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 stackitem.Item interface.
func (c *Context) Value() interface{} {
return c
}
// Dup implements stackitem.Item interface.
func (c *Context) Dup() stackitem.Item {
return c
}
// TryBool implements stackitem.Item interface.
func (c *Context) TryBool() (bool, error) { panic("can't convert Context to Bool") }
// TryBytes implements stackitem.Item interface.
func (c *Context) TryBytes() ([]byte, error) {
return nil, errors.New("can't convert Context to ByteArray")
}
// TryInteger implements stackitem.Item interface.
func (c *Context) TryInteger() (*big.Int, error) {
return nil, errors.New("can't convert Context to Integer")
}
// Type implements stackitem.Item interface.
func (c *Context) Type() stackitem.Type { panic("Context cannot appear on evaluation stack") }
// Convert implements stackitem.Item interface.
func (c *Context) Convert(_ stackitem.Type) (stackitem.Item, error) {
panic("Context cannot be converted to anything")
}
// Equals implements 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 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 to evaluation stack the script hash of the
// invocation stack element number n.
func (v *VM) PushContextScriptHash(n int) error {
h := v.getContextScriptHash(n)
v.Estack().PushItem(stackitem.NewByteArray(h.BytesBE()))
return nil
}