forked from TrueCloudLab/neoneo-go
Merge pull request #901 from nspcc-dev/feature/flowopcodes
vm: implement new flow-control opcodes
This commit is contained in:
commit
f940d6e5ad
10 changed files with 329 additions and 379 deletions
|
@ -435,13 +435,13 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
|
|
||||||
if n.Cond != nil {
|
if n.Cond != nil {
|
||||||
ast.Walk(c, n.Cond)
|
ast.Walk(c, n.Cond)
|
||||||
emit.Jmp(c.prog.BinWriter, opcode.JMPIFNOT, lElse)
|
emit.Jmp(c.prog.BinWriter, opcode.JMPIFNOTL, lElse)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.setLabel(lIf)
|
c.setLabel(lIf)
|
||||||
ast.Walk(c, n.Body)
|
ast.Walk(c, n.Body)
|
||||||
if n.Else != nil {
|
if n.Else != nil {
|
||||||
emit.Jmp(c.prog.BinWriter, opcode.JMP, lElseEnd)
|
emit.Jmp(c.prog.BinWriter, opcode.JMPL, lElseEnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.setLabel(lElse)
|
c.setLabel(lElse)
|
||||||
|
@ -476,9 +476,9 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
ast.Walk(c, cc.List[j])
|
ast.Walk(c, cc.List[j])
|
||||||
emit.Opcode(c.prog.BinWriter, eqOpcode)
|
emit.Opcode(c.prog.BinWriter, eqOpcode)
|
||||||
if j == l-1 {
|
if j == l-1 {
|
||||||
emit.Jmp(c.prog.BinWriter, opcode.JMPIFNOT, lEnd)
|
emit.Jmp(c.prog.BinWriter, opcode.JMPIFNOTL, lEnd)
|
||||||
} else {
|
} else {
|
||||||
emit.Jmp(c.prog.BinWriter, opcode.JMPIF, lStart)
|
emit.Jmp(c.prog.BinWriter, opcode.JMPIFL, lStart)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -487,12 +487,12 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
last := len(cc.Body) - 1
|
last := len(cc.Body) - 1
|
||||||
for j, stmt := range cc.Body {
|
for j, stmt := range cc.Body {
|
||||||
if j == last && isFallthroughStmt(stmt) {
|
if j == last && isFallthroughStmt(stmt) {
|
||||||
emit.Jmp(c.prog.BinWriter, opcode.JMP, startLabels[i+1])
|
emit.Jmp(c.prog.BinWriter, opcode.JMPL, startLabels[i+1])
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
ast.Walk(c, stmt)
|
ast.Walk(c, stmt)
|
||||||
}
|
}
|
||||||
emit.Jmp(c.prog.BinWriter, opcode.JMP, switchEnd)
|
emit.Jmp(c.prog.BinWriter, opcode.JMPL, switchEnd)
|
||||||
c.setLabel(lEnd)
|
c.setLabel(lEnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -562,9 +562,9 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
next := c.newLabel()
|
next := c.newLabel()
|
||||||
end := c.newLabel()
|
end := c.newLabel()
|
||||||
ast.Walk(c, n.X)
|
ast.Walk(c, n.X)
|
||||||
emit.Jmp(c.prog.BinWriter, opcode.JMPIF, next)
|
emit.Jmp(c.prog.BinWriter, opcode.JMPIFL, next)
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.PUSHF)
|
emit.Opcode(c.prog.BinWriter, opcode.PUSHF)
|
||||||
emit.Jmp(c.prog.BinWriter, opcode.JMP, end)
|
emit.Jmp(c.prog.BinWriter, opcode.JMPL, end)
|
||||||
c.setLabel(next)
|
c.setLabel(next)
|
||||||
ast.Walk(c, n.Y)
|
ast.Walk(c, n.Y)
|
||||||
c.setLabel(end)
|
c.setLabel(end)
|
||||||
|
@ -574,9 +574,9 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
next := c.newLabel()
|
next := c.newLabel()
|
||||||
end := c.newLabel()
|
end := c.newLabel()
|
||||||
ast.Walk(c, n.X)
|
ast.Walk(c, n.X)
|
||||||
emit.Jmp(c.prog.BinWriter, opcode.JMPIFNOT, next)
|
emit.Jmp(c.prog.BinWriter, opcode.JMPIFNOTL, next)
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.PUSHT)
|
emit.Opcode(c.prog.BinWriter, opcode.PUSHT)
|
||||||
emit.Jmp(c.prog.BinWriter, opcode.JMP, end)
|
emit.Jmp(c.prog.BinWriter, opcode.JMPL, end)
|
||||||
c.setLabel(next)
|
c.setLabel(next)
|
||||||
ast.Walk(c, n.Y)
|
ast.Walk(c, n.Y)
|
||||||
c.setLabel(end)
|
c.setLabel(end)
|
||||||
|
@ -686,7 +686,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
case isSyscall(f):
|
case isSyscall(f):
|
||||||
c.convertSyscall(n, f.selector.Name, f.name)
|
c.convertSyscall(n, f.selector.Name, f.name)
|
||||||
default:
|
default:
|
||||||
emit.Call(c.prog.BinWriter, opcode.CALL, f.label)
|
emit.Call(c.prog.BinWriter, opcode.CALLL, f.label)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -782,10 +782,10 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
switch n.Tok {
|
switch n.Tok {
|
||||||
case token.BREAK:
|
case token.BREAK:
|
||||||
end := c.getLabelOffset(labelEnd, label)
|
end := c.getLabelOffset(labelEnd, label)
|
||||||
emit.Jmp(c.prog.BinWriter, opcode.JMP, end)
|
emit.Jmp(c.prog.BinWriter, opcode.JMPL, end)
|
||||||
case token.CONTINUE:
|
case token.CONTINUE:
|
||||||
post := c.getLabelOffset(labelPost, label)
|
post := c.getLabelOffset(labelPost, label)
|
||||||
emit.Jmp(c.prog.BinWriter, opcode.JMP, post)
|
emit.Jmp(c.prog.BinWriter, opcode.JMPL, post)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -819,7 +819,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
ast.Walk(c, n.Cond)
|
ast.Walk(c, n.Cond)
|
||||||
|
|
||||||
// Jump if the condition is false
|
// Jump if the condition is false
|
||||||
emit.Jmp(c.prog.BinWriter, opcode.JMPIFNOT, fend)
|
emit.Jmp(c.prog.BinWriter, opcode.JMPIFNOTL, fend)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk body followed by the iterator (post stmt).
|
// Walk body followed by the iterator (post stmt).
|
||||||
|
@ -830,7 +830,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Jump back to condition.
|
// Jump back to condition.
|
||||||
emit.Jmp(c.prog.BinWriter, opcode.JMP, fstart)
|
emit.Jmp(c.prog.BinWriter, opcode.JMPL, fstart)
|
||||||
c.setLabel(fend)
|
c.setLabel(fend)
|
||||||
c.dropStackLabel()
|
c.dropStackLabel()
|
||||||
|
|
||||||
|
@ -867,7 +867,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.OVER)
|
emit.Opcode(c.prog.BinWriter, opcode.OVER)
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.OVER)
|
emit.Opcode(c.prog.BinWriter, opcode.OVER)
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.LTE) // finish if len <= i
|
emit.Opcode(c.prog.BinWriter, opcode.LTE) // finish if len <= i
|
||||||
emit.Jmp(c.prog.BinWriter, opcode.JMPIF, end)
|
emit.Jmp(c.prog.BinWriter, opcode.JMPIFL, end)
|
||||||
|
|
||||||
if n.Key != nil {
|
if n.Key != nil {
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.DUP)
|
emit.Opcode(c.prog.BinWriter, opcode.DUP)
|
||||||
|
@ -881,7 +881,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
c.setLabel(post)
|
c.setLabel(post)
|
||||||
|
|
||||||
emit.Opcode(c.prog.BinWriter, opcode.INC)
|
emit.Opcode(c.prog.BinWriter, opcode.INC)
|
||||||
emit.Jmp(c.prog.BinWriter, opcode.JMP, start)
|
emit.Jmp(c.prog.BinWriter, opcode.JMPL, start)
|
||||||
|
|
||||||
c.setLabel(end)
|
c.setLabel(end)
|
||||||
c.dropStackLabel()
|
c.dropStackLabel()
|
||||||
|
@ -1346,21 +1346,28 @@ func (c *codegen) writeJumps(b []byte) error {
|
||||||
ctx := vm.NewContext(b)
|
ctx := vm.NewContext(b)
|
||||||
for op, _, err := ctx.Next(); err == nil && ctx.NextIP() < len(b); op, _, err = ctx.Next() {
|
for op, _, err := ctx.Next(); err == nil && ctx.NextIP() < len(b); op, _, err = ctx.Next() {
|
||||||
switch op {
|
switch op {
|
||||||
case opcode.JMP, opcode.JMPIFNOT, opcode.JMPIF, opcode.CALL:
|
case opcode.JMP, opcode.JMPIFNOT, opcode.JMPIF, opcode.CALL,
|
||||||
|
opcode.JMPEQ, opcode.JMPNE,
|
||||||
|
opcode.JMPGT, opcode.JMPGE, opcode.JMPLE, opcode.JMPLT:
|
||||||
|
panic("short jumps are not yet supported")
|
||||||
|
case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL,
|
||||||
|
opcode.JMPEQL, opcode.JMPNEL,
|
||||||
|
opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLEL, opcode.JMPLTL,
|
||||||
|
opcode.CALLL:
|
||||||
// we can't use arg returned by ctx.Next() because it is copied
|
// we can't use arg returned by ctx.Next() because it is copied
|
||||||
nextIP := ctx.NextIP()
|
nextIP := ctx.NextIP()
|
||||||
arg := b[nextIP-2:]
|
arg := b[nextIP-4:]
|
||||||
|
|
||||||
index := binary.LittleEndian.Uint16(arg)
|
index := binary.LittleEndian.Uint16(arg)
|
||||||
if int(index) > len(c.l) {
|
if int(index) > len(c.l) {
|
||||||
return fmt.Errorf("unexpected label number: %d (max %d)", index, len(c.l))
|
return fmt.Errorf("unexpected label number: %d (max %d)", index, len(c.l))
|
||||||
}
|
}
|
||||||
offset := c.l[index] - nextIP + 3
|
offset := c.l[index] - nextIP + 5
|
||||||
if offset > math.MaxUint16 {
|
if offset > math.MaxInt32 || offset < math.MinInt32 {
|
||||||
return fmt.Errorf("label offset is too big at the instruction %d: %d (max %d)",
|
return fmt.Errorf("label offset is too big at the instruction %d: %d (max %d, min %d)",
|
||||||
nextIP-3, offset, math.MaxUint16)
|
nextIP-5, offset, math.MaxInt32, math.MinInt32)
|
||||||
}
|
}
|
||||||
binary.LittleEndian.PutUint16(arg, uint16(offset))
|
binary.LittleEndian.PutUint32(arg, uint32(offset))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -48,18 +48,18 @@ type rpcTestCase struct {
|
||||||
check func(t *testing.T, e *executor, result interface{})
|
check func(t *testing.T, e *executor, result interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
const testContractHash = "8bb068bca226bf013e7d19400b9d85c4eb865607"
|
const testContractHash = "6b9e88be61028590ebbb1296cbee09beed4ae75d"
|
||||||
|
|
||||||
var rpcTestCases = map[string][]rpcTestCase{
|
var rpcTestCases = map[string][]rpcTestCase{
|
||||||
"getapplicationlog": {
|
"getapplicationlog": {
|
||||||
{
|
{
|
||||||
name: "positive",
|
name: "positive",
|
||||||
params: `["66238fd4ac778326b0c151c025ee8f1c6d738e7e191820537564d2b887f3ecde"]`,
|
params: `["4459d8050e0abb0a42a9ec5a84dee1c5f8c449b11859265ef45a47c5ea369bc5"]`,
|
||||||
result: func(e *executor) interface{} { return &result.ApplicationLog{} },
|
result: func(e *executor) interface{} { return &result.ApplicationLog{} },
|
||||||
check: func(t *testing.T, e *executor, acc interface{}) {
|
check: func(t *testing.T, e *executor, acc interface{}) {
|
||||||
res, ok := acc.(*result.ApplicationLog)
|
res, ok := acc.(*result.ApplicationLog)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
expectedTxHash, err := util.Uint256DecodeStringLE("66238fd4ac778326b0c151c025ee8f1c6d738e7e191820537564d2b887f3ecde")
|
expectedTxHash, err := util.Uint256DecodeStringLE("4459d8050e0abb0a42a9ec5a84dee1c5f8c449b11859265ef45a47c5ea369bc5")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, expectedTxHash, res.TxHash)
|
assert.Equal(t, expectedTxHash, res.TxHash)
|
||||||
assert.Equal(t, 1, len(res.Executions))
|
assert.Equal(t, 1, len(res.Executions))
|
||||||
|
|
BIN
pkg/rpc/server/testdata/test_contract.avm
vendored
BIN
pkg/rpc/server/testdata/test_contract.avm
vendored
Binary file not shown.
BIN
pkg/rpc/server/testdata/testblocks.acc
vendored
BIN
pkg/rpc/server/testdata/testblocks.acc
vendored
Binary file not shown.
|
@ -99,14 +99,16 @@ func (c *Context) Next() (opcode.Opcode, []byte, error) {
|
||||||
numtoread = int(n)
|
numtoread = int(n)
|
||||||
c.nextip += 4
|
c.nextip += 4
|
||||||
}
|
}
|
||||||
case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.CALL, opcode.CALLED, opcode.CALLEDT:
|
case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.JMPEQ, opcode.JMPNE,
|
||||||
numtoread = 2
|
opcode.JMPGT, opcode.JMPGE, opcode.JMPLT, opcode.JMPLE,
|
||||||
case opcode.CALLI, opcode.SYSCALL:
|
opcode.CALL:
|
||||||
|
numtoread = 1
|
||||||
|
case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL, opcode.JMPEQL, opcode.JMPNEL,
|
||||||
|
opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLTL, opcode.JMPLEL,
|
||||||
|
opcode.CALLL, opcode.SYSCALL:
|
||||||
numtoread = 4
|
numtoread = 4
|
||||||
case opcode.APPCALL, opcode.TAILCALL:
|
case opcode.APPCALL, opcode.TAILCALL:
|
||||||
numtoread = 20
|
numtoread = 20
|
||||||
case opcode.CALLE, opcode.CALLET:
|
|
||||||
numtoread = 22
|
|
||||||
default:
|
default:
|
||||||
if instr <= opcode.PUSHINT256 {
|
if instr <= opcode.PUSHINT256 {
|
||||||
numtoread = 1 << instr
|
numtoread = 1 << instr
|
||||||
|
|
|
@ -135,7 +135,7 @@ func Jmp(w *io.BinWriter, op opcode.Opcode, label uint16) {
|
||||||
w.Err = fmt.Errorf("opcode %s is not a jump or call type", op.String())
|
w.Err = fmt.Errorf("opcode %s is not a jump or call type", op.String())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
buf := make([]byte, 2)
|
buf := make([]byte, 4)
|
||||||
binary.LittleEndian.PutUint16(buf, label)
|
binary.LittleEndian.PutUint16(buf, label)
|
||||||
Instruction(w, op, buf)
|
Instruction(w, op, buf)
|
||||||
}
|
}
|
||||||
|
@ -172,10 +172,7 @@ func AppCallWithOperation(w *io.BinWriter, scriptHash util.Uint160, operation st
|
||||||
}
|
}
|
||||||
|
|
||||||
func isInstructionJmp(op opcode.Opcode) bool {
|
func isInstructionJmp(op opcode.Opcode) bool {
|
||||||
if op == opcode.JMP || op == opcode.JMPIFNOT || op == opcode.JMPIF || op == opcode.CALL {
|
return opcode.JMP <= op && op <= opcode.CALLL
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// InteropNameToID returns an identificator of the method based on its name.
|
// InteropNameToID returns an identificator of the method based on its name.
|
||||||
|
|
|
@ -42,14 +42,32 @@ const (
|
||||||
PUSH15 Opcode = 0x1F
|
PUSH15 Opcode = 0x1F
|
||||||
PUSH16 Opcode = 0x20
|
PUSH16 Opcode = 0x20
|
||||||
|
|
||||||
|
// Flow control
|
||||||
|
NOP Opcode = 0x21
|
||||||
|
JMP Opcode = 0x22
|
||||||
|
JMPL Opcode = 0x23
|
||||||
|
JMPIF Opcode = 0x24
|
||||||
|
JMPIFL Opcode = 0x25
|
||||||
|
JMPIFNOT Opcode = 0x26
|
||||||
|
JMPIFNOTL Opcode = 0x27
|
||||||
|
JMPEQ Opcode = 0x28
|
||||||
|
JMPEQL Opcode = 0x29
|
||||||
|
JMPNE Opcode = 0x2A
|
||||||
|
JMPNEL Opcode = 0x2B
|
||||||
|
JMPGT Opcode = 0x2C
|
||||||
|
JMPGTL Opcode = 0x2D
|
||||||
|
JMPGE Opcode = 0x2E
|
||||||
|
JMPGEL Opcode = 0x2F
|
||||||
|
JMPLT Opcode = 0x30
|
||||||
|
JMPLTL Opcode = 0x31
|
||||||
|
JMPLE Opcode = 0x32
|
||||||
|
JMPLEL Opcode = 0x33
|
||||||
|
CALL Opcode = 0x34
|
||||||
|
CALLL Opcode = 0x35
|
||||||
|
|
||||||
|
// Legacy
|
||||||
OLDPUSH1 Opcode = 0x51
|
OLDPUSH1 Opcode = 0x51
|
||||||
|
|
||||||
// Flow control
|
|
||||||
NOP Opcode = 0x61
|
|
||||||
JMP Opcode = 0x62
|
|
||||||
JMPIF Opcode = 0x63
|
|
||||||
JMPIFNOT Opcode = 0x64
|
|
||||||
CALL Opcode = 0x65
|
|
||||||
RET Opcode = 0x66
|
RET Opcode = 0x66
|
||||||
APPCALL Opcode = 0x67
|
APPCALL Opcode = 0x67
|
||||||
SYSCALL Opcode = 0x68
|
SYSCALL Opcode = 0x68
|
||||||
|
@ -141,13 +159,6 @@ const (
|
||||||
KEYS Opcode = 0xCC
|
KEYS Opcode = 0xCC
|
||||||
VALUES Opcode = 0xCD
|
VALUES Opcode = 0xCD
|
||||||
|
|
||||||
// Stack isolation
|
|
||||||
CALLI Opcode = 0xE0
|
|
||||||
CALLE Opcode = 0xE1
|
|
||||||
CALLED Opcode = 0xE2
|
|
||||||
CALLET Opcode = 0xE3
|
|
||||||
CALLEDT Opcode = 0xE4
|
|
||||||
|
|
||||||
// Exceptions
|
// Exceptions
|
||||||
THROW Opcode = 0xF0
|
THROW Opcode = 0xF0
|
||||||
THROWIFNOT Opcode = 0xF1
|
THROWIFNOT Opcode = 0xF1
|
||||||
|
|
|
@ -38,11 +38,28 @@ func _() {
|
||||||
_ = x[PUSH14-30]
|
_ = x[PUSH14-30]
|
||||||
_ = x[PUSH15-31]
|
_ = x[PUSH15-31]
|
||||||
_ = x[PUSH16-32]
|
_ = x[PUSH16-32]
|
||||||
_ = x[NOP-97]
|
_ = x[NOP-33]
|
||||||
_ = x[JMP-98]
|
_ = x[JMP-34]
|
||||||
_ = x[JMPIF-99]
|
_ = x[JMPL-35]
|
||||||
_ = x[JMPIFNOT-100]
|
_ = x[JMPIF-36]
|
||||||
_ = x[CALL-101]
|
_ = x[JMPIFL-37]
|
||||||
|
_ = x[JMPIFNOT-38]
|
||||||
|
_ = x[JMPIFNOTL-39]
|
||||||
|
_ = x[JMPEQ-40]
|
||||||
|
_ = x[JMPEQL-41]
|
||||||
|
_ = x[JMPNE-42]
|
||||||
|
_ = x[JMPNEL-43]
|
||||||
|
_ = x[JMPGT-44]
|
||||||
|
_ = x[JMPGTL-45]
|
||||||
|
_ = x[JMPGE-46]
|
||||||
|
_ = x[JMPGEL-47]
|
||||||
|
_ = x[JMPLT-48]
|
||||||
|
_ = x[JMPLTL-49]
|
||||||
|
_ = x[JMPLE-50]
|
||||||
|
_ = x[JMPLEL-51]
|
||||||
|
_ = x[CALL-52]
|
||||||
|
_ = x[CALLL-53]
|
||||||
|
_ = x[OLDPUSH1-81]
|
||||||
_ = x[RET-102]
|
_ = x[RET-102]
|
||||||
_ = x[APPCALL-103]
|
_ = x[APPCALL-103]
|
||||||
_ = x[SYSCALL-104]
|
_ = x[SYSCALL-104]
|
||||||
|
@ -120,16 +137,11 @@ func _() {
|
||||||
_ = x[HASKEY-203]
|
_ = x[HASKEY-203]
|
||||||
_ = x[KEYS-204]
|
_ = x[KEYS-204]
|
||||||
_ = x[VALUES-205]
|
_ = x[VALUES-205]
|
||||||
_ = x[CALLI-224]
|
|
||||||
_ = x[CALLE-225]
|
|
||||||
_ = x[CALLED-226]
|
|
||||||
_ = x[CALLET-227]
|
|
||||||
_ = x[CALLEDT-228]
|
|
||||||
_ = x[THROW-240]
|
_ = x[THROW-240]
|
||||||
_ = x[THROWIFNOT-241]
|
_ = x[THROWIFNOT-241]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _Opcode_name = "PUSHINT8PUSHINT16PUSHINT32PUSHINT64PUSHINT128PUSHINT256PUSHNULLPUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16NOPJMPJMPIFJMPIFNOTCALLRETAPPCALLSYSCALLTAILCALLDUPFROMALTSTACKTOALTSTACKFROMALTSTACKXDROPISNULLXSWAPXTUCKDEPTHDROPDUPNIPOVERPICKROLLROTSWAPTUCKCATSUBSTRLEFTRIGHTSIZEINVERTANDORXOREQUALINCDECSIGNNEGATEABSNOTNZADDSUBMULDIVMODSHLSHRBOOLANDBOOLORNUMEQUALNUMNOTEQUALLTGTLTEGTEMINMAXWITHINSHA1SHA256HASH160HASH256CHECKSIGVERIFYCHECKMULTISIGARRAYSIZEPACKUNPACKPICKITEMSETITEMNEWARRAYNEWSTRUCTNEWMAPAPPENDREVERSEREMOVEHASKEYKEYSVALUESCALLICALLECALLEDCALLETCALLEDTTHROWTHROWIFNOT"
|
const _Opcode_name = "PUSHINT8PUSHINT16PUSHINT32PUSHINT64PUSHINT128PUSHINT256PUSHNULLPUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16NOPJMPJMP_LJMPIFJMPIF_LJMPIFNOTJMPIFNOT_LJMPEQJMPEQ_LJMPNEJMPNE_LJMPGTJMPGT_LJMPGEJMPGE_LJMPLTJMPLT_LJMPLEJMPLE_LCALLCALL_LOLDPUSH1RETAPPCALLSYSCALLTAILCALLDUPFROMALTSTACKTOALTSTACKFROMALTSTACKXDROPISNULLXSWAPXTUCKDEPTHDROPDUPNIPOVERPICKROLLROTSWAPTUCKCATSUBSTRLEFTRIGHTSIZEINVERTANDORXOREQUALINCDECSIGNNEGATEABSNOTNZADDSUBMULDIVMODSHLSHRBOOLANDBOOLORNUMEQUALNUMNOTEQUALLTGTLTEGTEMINMAXWITHINSHA1SHA256HASH160HASH256CHECKSIGVERIFYCHECKMULTISIGARRAYSIZEPACKUNPACKPICKITEMSETITEMNEWARRAYNEWSTRUCTNEWMAPAPPENDREVERSEREMOVEHASKEYKEYSVALUESTHROWTHROWIFNOT"
|
||||||
|
|
||||||
var _Opcode_map = map[Opcode]string{
|
var _Opcode_map = map[Opcode]string{
|
||||||
0: _Opcode_name[0:8],
|
0: _Opcode_name[0:8],
|
||||||
|
@ -160,95 +172,107 @@ var _Opcode_map = map[Opcode]string{
|
||||||
30: _Opcode_name[170:176],
|
30: _Opcode_name[170:176],
|
||||||
31: _Opcode_name[176:182],
|
31: _Opcode_name[176:182],
|
||||||
32: _Opcode_name[182:188],
|
32: _Opcode_name[182:188],
|
||||||
97: _Opcode_name[188:191],
|
33: _Opcode_name[188:191],
|
||||||
98: _Opcode_name[191:194],
|
34: _Opcode_name[191:194],
|
||||||
99: _Opcode_name[194:199],
|
35: _Opcode_name[194:199],
|
||||||
100: _Opcode_name[199:207],
|
36: _Opcode_name[199:204],
|
||||||
101: _Opcode_name[207:211],
|
37: _Opcode_name[204:211],
|
||||||
102: _Opcode_name[211:214],
|
38: _Opcode_name[211:219],
|
||||||
103: _Opcode_name[214:221],
|
39: _Opcode_name[219:229],
|
||||||
104: _Opcode_name[221:228],
|
40: _Opcode_name[229:234],
|
||||||
105: _Opcode_name[228:236],
|
41: _Opcode_name[234:241],
|
||||||
106: _Opcode_name[236:251],
|
42: _Opcode_name[241:246],
|
||||||
107: _Opcode_name[251:261],
|
43: _Opcode_name[246:253],
|
||||||
108: _Opcode_name[261:273],
|
44: _Opcode_name[253:258],
|
||||||
109: _Opcode_name[273:278],
|
45: _Opcode_name[258:265],
|
||||||
112: _Opcode_name[278:284],
|
46: _Opcode_name[265:270],
|
||||||
114: _Opcode_name[284:289],
|
47: _Opcode_name[270:277],
|
||||||
115: _Opcode_name[289:294],
|
48: _Opcode_name[277:282],
|
||||||
116: _Opcode_name[294:299],
|
49: _Opcode_name[282:289],
|
||||||
117: _Opcode_name[299:303],
|
50: _Opcode_name[289:294],
|
||||||
118: _Opcode_name[303:306],
|
51: _Opcode_name[294:301],
|
||||||
119: _Opcode_name[306:309],
|
52: _Opcode_name[301:305],
|
||||||
120: _Opcode_name[309:313],
|
53: _Opcode_name[305:311],
|
||||||
121: _Opcode_name[313:317],
|
81: _Opcode_name[311:319],
|
||||||
122: _Opcode_name[317:321],
|
102: _Opcode_name[319:322],
|
||||||
123: _Opcode_name[321:324],
|
103: _Opcode_name[322:329],
|
||||||
124: _Opcode_name[324:328],
|
104: _Opcode_name[329:336],
|
||||||
125: _Opcode_name[328:332],
|
105: _Opcode_name[336:344],
|
||||||
126: _Opcode_name[332:335],
|
106: _Opcode_name[344:359],
|
||||||
127: _Opcode_name[335:341],
|
107: _Opcode_name[359:369],
|
||||||
128: _Opcode_name[341:345],
|
108: _Opcode_name[369:381],
|
||||||
129: _Opcode_name[345:350],
|
109: _Opcode_name[381:386],
|
||||||
130: _Opcode_name[350:354],
|
112: _Opcode_name[386:392],
|
||||||
131: _Opcode_name[354:360],
|
114: _Opcode_name[392:397],
|
||||||
132: _Opcode_name[360:363],
|
115: _Opcode_name[397:402],
|
||||||
133: _Opcode_name[363:365],
|
116: _Opcode_name[402:407],
|
||||||
134: _Opcode_name[365:368],
|
117: _Opcode_name[407:411],
|
||||||
135: _Opcode_name[368:373],
|
118: _Opcode_name[411:414],
|
||||||
139: _Opcode_name[373:376],
|
119: _Opcode_name[414:417],
|
||||||
140: _Opcode_name[376:379],
|
120: _Opcode_name[417:421],
|
||||||
141: _Opcode_name[379:383],
|
121: _Opcode_name[421:425],
|
||||||
143: _Opcode_name[383:389],
|
122: _Opcode_name[425:429],
|
||||||
144: _Opcode_name[389:392],
|
123: _Opcode_name[429:432],
|
||||||
145: _Opcode_name[392:395],
|
124: _Opcode_name[432:436],
|
||||||
146: _Opcode_name[395:397],
|
125: _Opcode_name[436:440],
|
||||||
147: _Opcode_name[397:400],
|
126: _Opcode_name[440:443],
|
||||||
148: _Opcode_name[400:403],
|
127: _Opcode_name[443:449],
|
||||||
149: _Opcode_name[403:406],
|
128: _Opcode_name[449:453],
|
||||||
150: _Opcode_name[406:409],
|
129: _Opcode_name[453:458],
|
||||||
151: _Opcode_name[409:412],
|
130: _Opcode_name[458:462],
|
||||||
152: _Opcode_name[412:415],
|
131: _Opcode_name[462:468],
|
||||||
153: _Opcode_name[415:418],
|
132: _Opcode_name[468:471],
|
||||||
154: _Opcode_name[418:425],
|
133: _Opcode_name[471:473],
|
||||||
155: _Opcode_name[425:431],
|
134: _Opcode_name[473:476],
|
||||||
156: _Opcode_name[431:439],
|
135: _Opcode_name[476:481],
|
||||||
158: _Opcode_name[439:450],
|
139: _Opcode_name[481:484],
|
||||||
159: _Opcode_name[450:452],
|
140: _Opcode_name[484:487],
|
||||||
160: _Opcode_name[452:454],
|
141: _Opcode_name[487:491],
|
||||||
161: _Opcode_name[454:457],
|
143: _Opcode_name[491:497],
|
||||||
162: _Opcode_name[457:460],
|
144: _Opcode_name[497:500],
|
||||||
163: _Opcode_name[460:463],
|
145: _Opcode_name[500:503],
|
||||||
164: _Opcode_name[463:466],
|
146: _Opcode_name[503:505],
|
||||||
165: _Opcode_name[466:472],
|
147: _Opcode_name[505:508],
|
||||||
167: _Opcode_name[472:476],
|
148: _Opcode_name[508:511],
|
||||||
168: _Opcode_name[476:482],
|
149: _Opcode_name[511:514],
|
||||||
169: _Opcode_name[482:489],
|
150: _Opcode_name[514:517],
|
||||||
170: _Opcode_name[489:496],
|
151: _Opcode_name[517:520],
|
||||||
172: _Opcode_name[496:504],
|
152: _Opcode_name[520:523],
|
||||||
173: _Opcode_name[504:510],
|
153: _Opcode_name[523:526],
|
||||||
174: _Opcode_name[510:523],
|
154: _Opcode_name[526:533],
|
||||||
192: _Opcode_name[523:532],
|
155: _Opcode_name[533:539],
|
||||||
193: _Opcode_name[532:536],
|
156: _Opcode_name[539:547],
|
||||||
194: _Opcode_name[536:542],
|
158: _Opcode_name[547:558],
|
||||||
195: _Opcode_name[542:550],
|
159: _Opcode_name[558:560],
|
||||||
196: _Opcode_name[550:557],
|
160: _Opcode_name[560:562],
|
||||||
197: _Opcode_name[557:565],
|
161: _Opcode_name[562:565],
|
||||||
198: _Opcode_name[565:574],
|
162: _Opcode_name[565:568],
|
||||||
199: _Opcode_name[574:580],
|
163: _Opcode_name[568:571],
|
||||||
200: _Opcode_name[580:586],
|
164: _Opcode_name[571:574],
|
||||||
201: _Opcode_name[586:593],
|
165: _Opcode_name[574:580],
|
||||||
202: _Opcode_name[593:599],
|
167: _Opcode_name[580:584],
|
||||||
203: _Opcode_name[599:605],
|
168: _Opcode_name[584:590],
|
||||||
204: _Opcode_name[605:609],
|
169: _Opcode_name[590:597],
|
||||||
205: _Opcode_name[609:615],
|
170: _Opcode_name[597:604],
|
||||||
224: _Opcode_name[615:620],
|
172: _Opcode_name[604:612],
|
||||||
225: _Opcode_name[620:625],
|
173: _Opcode_name[612:618],
|
||||||
226: _Opcode_name[625:631],
|
174: _Opcode_name[618:631],
|
||||||
227: _Opcode_name[631:637],
|
192: _Opcode_name[631:640],
|
||||||
228: _Opcode_name[637:644],
|
193: _Opcode_name[640:644],
|
||||||
240: _Opcode_name[644:649],
|
194: _Opcode_name[644:650],
|
||||||
241: _Opcode_name[649:659],
|
195: _Opcode_name[650:658],
|
||||||
|
196: _Opcode_name[658:665],
|
||||||
|
197: _Opcode_name[665:673],
|
||||||
|
198: _Opcode_name[673:682],
|
||||||
|
199: _Opcode_name[682:688],
|
||||||
|
200: _Opcode_name[688:694],
|
||||||
|
201: _Opcode_name[694:701],
|
||||||
|
202: _Opcode_name[701:707],
|
||||||
|
203: _Opcode_name[707:713],
|
||||||
|
204: _Opcode_name[713:717],
|
||||||
|
205: _Opcode_name[717:723],
|
||||||
|
240: _Opcode_name[723:728],
|
||||||
|
241: _Opcode_name[728:738],
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i Opcode) String() string {
|
func (i Opcode) String() string {
|
||||||
|
|
122
pkg/vm/vm.go
122
pkg/vm/vm.go
|
@ -529,8 +529,6 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
||||||
}
|
}
|
||||||
|
|
||||||
parameter = v.estack.Pop().Bytes()
|
parameter = v.estack.Pop().Bytes()
|
||||||
fallthrough
|
|
||||||
case opcode.CALLED, opcode.CALLEDT:
|
|
||||||
if !ctx.hasDynamicInvoke {
|
if !ctx.hasDynamicInvoke {
|
||||||
panic("contract is not allowed to make dynamic invocations")
|
panic("contract is not allowed to make dynamic invocations")
|
||||||
}
|
}
|
||||||
|
@ -1120,16 +1118,25 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
||||||
arr := elem.Bytes()
|
arr := elem.Bytes()
|
||||||
v.estack.PushVal(len(arr))
|
v.estack.PushVal(len(arr))
|
||||||
|
|
||||||
case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT:
|
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:
|
||||||
offset := v.getJumpOffset(ctx, parameter, 0)
|
offset := v.getJumpOffset(ctx, parameter, 0)
|
||||||
cond := true
|
cond := true
|
||||||
if op != opcode.JMP {
|
switch op {
|
||||||
cond = v.estack.Pop().Bool() == (op == opcode.JMPIF)
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
v.jumpIf(ctx, offset, cond)
|
v.jumpIf(ctx, offset, cond)
|
||||||
|
|
||||||
case opcode.CALL:
|
case opcode.CALL, opcode.CALLL:
|
||||||
v.checkInvocationStackSize()
|
v.checkInvocationStackSize()
|
||||||
|
|
||||||
newCtx := ctx.Copy()
|
newCtx := ctx.Copy()
|
||||||
|
@ -1281,75 +1288,6 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
||||||
case opcode.NOP:
|
case opcode.NOP:
|
||||||
// unlucky ^^
|
// unlucky ^^
|
||||||
|
|
||||||
case opcode.CALLI, opcode.CALLE, opcode.CALLED, opcode.CALLET, opcode.CALLEDT:
|
|
||||||
var (
|
|
||||||
tailCall = (op == opcode.CALLET || op == opcode.CALLEDT)
|
|
||||||
hashOnStack = (op == opcode.CALLED || op == opcode.CALLEDT)
|
|
||||||
addElement int
|
|
||||||
newCtx *Context
|
|
||||||
)
|
|
||||||
|
|
||||||
if hashOnStack {
|
|
||||||
addElement = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
rvcount := int(parameter[0])
|
|
||||||
pcount := int(parameter[1])
|
|
||||||
if v.estack.Len() < pcount+addElement {
|
|
||||||
panic("missing some parameters")
|
|
||||||
}
|
|
||||||
if tailCall {
|
|
||||||
if ctx.rvcount != rvcount {
|
|
||||||
panic("context and parameter rvcount mismatch")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
v.checkInvocationStackSize()
|
|
||||||
}
|
|
||||||
|
|
||||||
if op == opcode.CALLI {
|
|
||||||
newCtx = ctx.Copy()
|
|
||||||
} else {
|
|
||||||
var hashBytes []byte
|
|
||||||
|
|
||||||
if hashOnStack {
|
|
||||||
hashBytes = v.estack.Pop().Bytes()
|
|
||||||
} else {
|
|
||||||
hashBytes = parameter[2:]
|
|
||||||
}
|
|
||||||
|
|
||||||
hash, err := util.Uint160DecodeBytesBE(hashBytes)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
script, hasDynamicInvoke := v.getScript(hash)
|
|
||||||
if script == nil {
|
|
||||||
panic(fmt.Sprintf("could not find script %s", hash))
|
|
||||||
}
|
|
||||||
newCtx = NewContext(script)
|
|
||||||
newCtx.scriptHash = hash
|
|
||||||
newCtx.hasDynamicInvoke = hasDynamicInvoke
|
|
||||||
}
|
|
||||||
newCtx.rvcount = rvcount
|
|
||||||
newCtx.estack = NewStack("evaluation")
|
|
||||||
newCtx.astack = NewStack("alt")
|
|
||||||
// Going backwards to naturally push things onto the new stack.
|
|
||||||
for i := pcount; i > 0; i-- {
|
|
||||||
elem := v.estack.RemoveAt(i - 1)
|
|
||||||
newCtx.estack.Push(elem)
|
|
||||||
}
|
|
||||||
if tailCall {
|
|
||||||
_ = v.istack.Pop()
|
|
||||||
}
|
|
||||||
v.istack.PushVal(newCtx)
|
|
||||||
v.estack = newCtx.estack
|
|
||||||
v.astack = newCtx.astack
|
|
||||||
if op == opcode.CALLI {
|
|
||||||
// CALLI is a bit different from other JMPs
|
|
||||||
// https://github.com/neo-project/neo-vm/blob/master-2.x/src/neo-vm/ExecutionEngine.cs#L1175
|
|
||||||
offset := v.getJumpOffset(newCtx, parameter[2:], 2)
|
|
||||||
v.jumpIf(newCtx, offset, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
case opcode.THROW:
|
case opcode.THROW:
|
||||||
panic("THROW")
|
panic("THROW")
|
||||||
|
|
||||||
|
@ -1364,6 +1302,27 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getJumpCondition performs opcode specific comparison of a and b
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// jumpIf performs jump to offset if cond is true.
|
// jumpIf performs jump to offset if cond is true.
|
||||||
func (v *VM) jumpIf(ctx *Context, offset int, cond bool) {
|
func (v *VM) jumpIf(ctx *Context, offset int, cond bool) {
|
||||||
if cond {
|
if cond {
|
||||||
|
@ -1373,9 +1332,18 @@ func (v *VM) jumpIf(ctx *Context, offset int, cond bool) {
|
||||||
|
|
||||||
// getJumpOffset returns instruction number in a current context
|
// getJumpOffset returns instruction number in a current context
|
||||||
// to a which JMP should be performed.
|
// to a which JMP should be performed.
|
||||||
// parameter is interpreted as little-endian int16.
|
// parameter should have length either 1 or 4 and
|
||||||
|
// is interpreted as little-endian.
|
||||||
func (v *VM) getJumpOffset(ctx *Context, parameter []byte, mod int) int {
|
func (v *VM) getJumpOffset(ctx *Context, parameter []byte, mod int) int {
|
||||||
rOffset := int16(binary.LittleEndian.Uint16(parameter))
|
var rOffset int32
|
||||||
|
switch l := len(parameter); l {
|
||||||
|
case 1:
|
||||||
|
rOffset = int32(int8(parameter[0]))
|
||||||
|
case 4:
|
||||||
|
rOffset = int32(binary.LittleEndian.Uint32(parameter))
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("invalid JMP* parameter length: %d", l))
|
||||||
|
}
|
||||||
offset := ctx.ip + int(rOffset) + mod
|
offset := ctx.ip + int(rOffset) + mod
|
||||||
if offset < 0 || offset > len(ctx.prog) {
|
if offset < 0 || offset > len(ctx.prog) {
|
||||||
panic(fmt.Sprintf("JMP: invalid offset %d ip at %d", offset, ctx.ip))
|
panic(fmt.Sprintf("JMP: invalid offset %d ip at %d", offset, ctx.ip))
|
||||||
|
|
|
@ -719,9 +719,9 @@ func callNTimes(n uint16) []byte {
|
||||||
return makeProgram(
|
return makeProgram(
|
||||||
opcode.PUSHINT16, opcode.Opcode(n), opcode.Opcode(n>>8), // little-endian
|
opcode.PUSHINT16, opcode.Opcode(n), opcode.Opcode(n>>8), // little-endian
|
||||||
opcode.TOALTSTACK, opcode.DUPFROMALTSTACK,
|
opcode.TOALTSTACK, opcode.DUPFROMALTSTACK,
|
||||||
opcode.JMPIF, 0x4, 0, opcode.RET,
|
opcode.JMPIF, 0x3, opcode.RET,
|
||||||
opcode.FROMALTSTACK, opcode.DEC,
|
opcode.FROMALTSTACK, opcode.DEC,
|
||||||
opcode.CALL, 0xF8, 0xFF) // -8 -> JMP to TOALTSTACK)
|
opcode.CALL, 0xF9) // -7 -> JMP to TOALTSTACK)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInvocationLimitGood(t *testing.T) {
|
func TestInvocationLimitGood(t *testing.T) {
|
||||||
|
@ -736,6 +736,96 @@ func TestInvocationLimitBad(t *testing.T) {
|
||||||
checkVMFailed(t, v)
|
checkVMFailed(t, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isLongJMP(op opcode.Opcode) bool {
|
||||||
|
return op == opcode.JMPL || op == opcode.JMPIFL || op == opcode.JMPIFNOTL ||
|
||||||
|
op == opcode.JMPEQL || op == opcode.JMPNEL ||
|
||||||
|
op == opcode.JMPGEL || op == opcode.JMPGTL ||
|
||||||
|
op == opcode.JMPLEL || op == opcode.JMPLTL
|
||||||
|
}
|
||||||
|
|
||||||
|
func getJMPProgram(op opcode.Opcode) []byte {
|
||||||
|
prog := []byte{byte(op)}
|
||||||
|
if isLongJMP(op) {
|
||||||
|
prog = append(prog, 0x07, 0x00, 0x00, 0x00)
|
||||||
|
} else {
|
||||||
|
prog = append(prog, 0x04)
|
||||||
|
}
|
||||||
|
return append(prog, byte(opcode.PUSH1), byte(opcode.RET), byte(opcode.PUSH2), byte(opcode.RET))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testJMP(t *testing.T, op opcode.Opcode, res interface{}, items ...interface{}) {
|
||||||
|
prog := getJMPProgram(op)
|
||||||
|
v := load(prog)
|
||||||
|
for i := range items {
|
||||||
|
v.estack.PushVal(items[i])
|
||||||
|
}
|
||||||
|
if res == nil {
|
||||||
|
checkVMFailed(t, v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
runVM(t, v)
|
||||||
|
require.EqualValues(t, res, v.estack.Pop().BigInt().Int64())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJMPs(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
items []interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no condition",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single item (true)",
|
||||||
|
items: []interface{}{true},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single item (false)",
|
||||||
|
items: []interface{}{false},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "24 and 42",
|
||||||
|
items: []interface{}{24, 42},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "42 and 24",
|
||||||
|
items: []interface{}{42, 24},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "42 and 42",
|
||||||
|
items: []interface{}{42, 42},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2 is true, 1 is false
|
||||||
|
results := map[opcode.Opcode][]interface{}{
|
||||||
|
opcode.JMP: {2, 2, 2, 2, 2, 2},
|
||||||
|
opcode.JMPIF: {nil, 2, 1, 2, 2, 2},
|
||||||
|
opcode.JMPIFNOT: {nil, 1, 2, 1, 1, 1},
|
||||||
|
opcode.JMPEQ: {nil, nil, nil, 1, 1, 2},
|
||||||
|
opcode.JMPNE: {nil, nil, nil, 2, 2, 1},
|
||||||
|
opcode.JMPGE: {nil, nil, nil, 1, 2, 2},
|
||||||
|
opcode.JMPGT: {nil, nil, nil, 1, 2, 1},
|
||||||
|
opcode.JMPLE: {nil, nil, nil, 2, 1, 2},
|
||||||
|
opcode.JMPLT: {nil, nil, nil, 2, 1, 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
i := i
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
for op := opcode.JMP; op < opcode.JMPLEL; op++ {
|
||||||
|
resOp := op
|
||||||
|
if isLongJMP(op) {
|
||||||
|
resOp--
|
||||||
|
}
|
||||||
|
t.Run(op.String(), func(t *testing.T) {
|
||||||
|
testJMP(t, op, results[resOp][i], tc.items...)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNOTNoArgument(t *testing.T) {
|
func TestNOTNoArgument(t *testing.T) {
|
||||||
prog := makeProgram(opcode.NOT)
|
prog := makeProgram(opcode.NOT)
|
||||||
vm := load(prog)
|
vm := load(prog)
|
||||||
|
@ -1798,7 +1888,7 @@ func TestSimpleCall(t *testing.T) {
|
||||||
buf := io.NewBufBinWriter()
|
buf := io.NewBufBinWriter()
|
||||||
w := buf.BinWriter
|
w := buf.BinWriter
|
||||||
emit.Opcode(w, opcode.PUSH2)
|
emit.Opcode(w, opcode.PUSH2)
|
||||||
emit.Instruction(w, opcode.CALL, []byte{04, 00})
|
emit.Instruction(w, opcode.CALL, []byte{03})
|
||||||
emit.Opcode(w, opcode.RET)
|
emit.Opcode(w, opcode.RET)
|
||||||
emit.Opcode(w, opcode.PUSH10)
|
emit.Opcode(w, opcode.PUSH10)
|
||||||
emit.Opcode(w, opcode.ADD)
|
emit.Opcode(w, opcode.ADD)
|
||||||
|
@ -3026,155 +3116,6 @@ func TestBitAndNumericOpcodes(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var stackIsolationTestCases = map[opcode.Opcode][]struct {
|
|
||||||
name string
|
|
||||||
params []interface{}
|
|
||||||
}{
|
|
||||||
opcode.CALLE: {
|
|
||||||
{
|
|
||||||
name: "no_params",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "with_params",
|
|
||||||
params: []interface{}{big.NewInt(5), true, []byte{1, 2, 3}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
opcode.CALLED: {
|
|
||||||
{
|
|
||||||
name: "no_paranms",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "with_params",
|
|
||||||
params: []interface{}{big.NewInt(5), true, []byte{1, 2, 3}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
opcode.CALLET: {
|
|
||||||
{
|
|
||||||
name: "no_params",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "with_params",
|
|
||||||
params: []interface{}{big.NewInt(5), true, []byte{1, 2, 3}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
opcode.CALLEDT: {
|
|
||||||
{
|
|
||||||
name: "no_params",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "with_params",
|
|
||||||
params: []interface{}{big.NewInt(5), true, []byte{1, 2, 3}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStackIsolationOpcodes(t *testing.T) {
|
|
||||||
scriptHash := util.Uint160{1, 2, 3}
|
|
||||||
for code, codeTestCases := range stackIsolationTestCases {
|
|
||||||
t.Run(code.String(), func(t *testing.T) {
|
|
||||||
for _, testCase := range codeTestCases {
|
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
|
||||||
rvcount := len(testCase.params) + 1 // parameters + DEPTH
|
|
||||||
pcount := len(testCase.params)
|
|
||||||
prog := []byte{byte(code), byte(rvcount), byte(pcount)}
|
|
||||||
if code == opcode.CALLE || code == opcode.CALLET {
|
|
||||||
prog = append(prog, scriptHash.BytesBE()...)
|
|
||||||
}
|
|
||||||
prog = append(prog, byte(opcode.RET))
|
|
||||||
|
|
||||||
vm := load(prog)
|
|
||||||
vm.SetScriptGetter(func(in util.Uint160) ([]byte, bool) {
|
|
||||||
if in.Equals(scriptHash) {
|
|
||||||
return makeProgram(opcode.DEPTH), true
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
})
|
|
||||||
if code == opcode.CALLED || code == opcode.CALLEDT {
|
|
||||||
vm.Context().hasDynamicInvoke = true
|
|
||||||
}
|
|
||||||
if code == opcode.CALLET || code == opcode.CALLEDT {
|
|
||||||
vm.Context().rvcount = rvcount
|
|
||||||
}
|
|
||||||
// add extra parameter just to check that it won't appear on new estack
|
|
||||||
vm.estack.PushVal(7)
|
|
||||||
for _, param := range testCase.params {
|
|
||||||
vm.estack.PushVal(param)
|
|
||||||
}
|
|
||||||
if code == opcode.CALLED || code == opcode.CALLEDT {
|
|
||||||
vm.estack.PushVal(scriptHash.BytesBE())
|
|
||||||
}
|
|
||||||
|
|
||||||
runVM(t, vm)
|
|
||||||
elem := vm.estack.Pop() // depth should be equal to params count, as it's a separate execution context
|
|
||||||
assert.Equal(t, int64(pcount), elem.BigInt().Int64())
|
|
||||||
for i := pcount; i > 0; i-- {
|
|
||||||
p := vm.estack.Pop()
|
|
||||||
assert.Equal(t, testCase.params[i-1], p.value.Value())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCALLIAltStack(t *testing.T) {
|
|
||||||
prog := []byte{
|
|
||||||
byte(opcode.PUSH1),
|
|
||||||
byte(opcode.DUP),
|
|
||||||
byte(opcode.TOALTSTACK),
|
|
||||||
byte(opcode.JMP), 0x5, 0, // to CALLI (+5 including JMP parameters)
|
|
||||||
byte(opcode.FROMALTSTACK), // altstack is empty, so panic here
|
|
||||||
byte(opcode.RET),
|
|
||||||
byte(opcode.CALLI), byte(0), byte(0), 0xfc, 0xff, // to FROMALTSTACK (-4) with clean stacks
|
|
||||||
byte(opcode.RET),
|
|
||||||
}
|
|
||||||
|
|
||||||
vm := load(prog)
|
|
||||||
checkVMFailed(t, vm)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCALLIDEPTH(t *testing.T) {
|
|
||||||
prog := []byte{
|
|
||||||
byte(opcode.PUSH3),
|
|
||||||
byte(opcode.PUSH2),
|
|
||||||
byte(opcode.PUSH1),
|
|
||||||
byte(opcode.JMP), 0x5, 0, // to CALLI (+5 including JMP parameters)
|
|
||||||
byte(opcode.DEPTH), // depth is 2, put it on stack
|
|
||||||
byte(opcode.RET),
|
|
||||||
byte(opcode.CALLI), byte(3), byte(2), 0xfc, 0xff, // to DEPTH (-4) with [21 on stack
|
|
||||||
byte(opcode.RET),
|
|
||||||
}
|
|
||||||
|
|
||||||
vm := load(prog)
|
|
||||||
|
|
||||||
runVM(t, vm)
|
|
||||||
assert.Equal(t, 4, vm.estack.Len())
|
|
||||||
assert.Equal(t, int64(2), vm.estack.Pop().BigInt().Int64()) // depth was 2
|
|
||||||
for i := 1; i < 4; i++ {
|
|
||||||
assert.Equal(t, int64(i), vm.estack.Pop().BigInt().Int64())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCALLI(t *testing.T) {
|
|
||||||
prog := []byte{
|
|
||||||
byte(opcode.PUSH3),
|
|
||||||
byte(opcode.PUSH2),
|
|
||||||
byte(opcode.PUSH1),
|
|
||||||
byte(opcode.JMP), 0x5, 0, // to CALLI (+5 including JMP parameters)
|
|
||||||
byte(opcode.SUB), // 2 - 1
|
|
||||||
byte(opcode.RET),
|
|
||||||
byte(opcode.CALLI), byte(1), byte(2), 0xfc, 0xff, // to SUB (-4) with [21 on stack
|
|
||||||
byte(opcode.MUL), // 3 * 1
|
|
||||||
byte(opcode.RET),
|
|
||||||
}
|
|
||||||
|
|
||||||
vm := load(prog)
|
|
||||||
|
|
||||||
runVM(t, vm)
|
|
||||||
assert.Equal(t, 1, vm.estack.Len())
|
|
||||||
assert.Equal(t, int64(3), vm.estack.Pop().BigInt().Int64())
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeProgram(opcodes ...opcode.Opcode) []byte {
|
func makeProgram(opcodes ...opcode.Opcode) []byte {
|
||||||
prog := make([]byte, len(opcodes)+1) // RET
|
prog := make([]byte, len(opcodes)+1) // RET
|
||||||
for i := 0; i < len(opcodes); i++ {
|
for i := 0; i < len(opcodes); i++ {
|
||||||
|
|
Loading…
Reference in a new issue