diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 610cafd7c..510cacabe 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -435,13 +435,13 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { if n.Cond != nil { ast.Walk(c, n.Cond) - emit.Jmp(c.prog.BinWriter, opcode.JMPIFNOT, lElse) + emit.Jmp(c.prog.BinWriter, opcode.JMPIFNOTL, lElse) } c.setLabel(lIf) ast.Walk(c, n.Body) if n.Else != nil { - emit.Jmp(c.prog.BinWriter, opcode.JMP, lElseEnd) + emit.Jmp(c.prog.BinWriter, opcode.JMPL, lElseEnd) } c.setLabel(lElse) @@ -476,9 +476,9 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { ast.Walk(c, cc.List[j]) emit.Opcode(c.prog.BinWriter, eqOpcode) if j == l-1 { - emit.Jmp(c.prog.BinWriter, opcode.JMPIFNOT, lEnd) + emit.Jmp(c.prog.BinWriter, opcode.JMPIFNOTL, lEnd) } 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 for j, stmt := range cc.Body { 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 } ast.Walk(c, stmt) } - emit.Jmp(c.prog.BinWriter, opcode.JMP, switchEnd) + emit.Jmp(c.prog.BinWriter, opcode.JMPL, switchEnd) c.setLabel(lEnd) } @@ -562,9 +562,9 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { next := c.newLabel() end := c.newLabel() 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.Jmp(c.prog.BinWriter, opcode.JMP, end) + emit.Jmp(c.prog.BinWriter, opcode.JMPL, end) c.setLabel(next) ast.Walk(c, n.Y) c.setLabel(end) @@ -574,9 +574,9 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { next := c.newLabel() end := c.newLabel() 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.Jmp(c.prog.BinWriter, opcode.JMP, end) + emit.Jmp(c.prog.BinWriter, opcode.JMPL, end) c.setLabel(next) ast.Walk(c, n.Y) c.setLabel(end) @@ -686,7 +686,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { case isSyscall(f): c.convertSyscall(n, f.selector.Name, f.name) default: - emit.Call(c.prog.BinWriter, opcode.CALL, f.label) + emit.Call(c.prog.BinWriter, opcode.CALLL, f.label) } return nil @@ -782,10 +782,10 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { switch n.Tok { case token.BREAK: end := c.getLabelOffset(labelEnd, label) - emit.Jmp(c.prog.BinWriter, opcode.JMP, end) + emit.Jmp(c.prog.BinWriter, opcode.JMPL, end) case token.CONTINUE: post := c.getLabelOffset(labelPost, label) - emit.Jmp(c.prog.BinWriter, opcode.JMP, post) + emit.Jmp(c.prog.BinWriter, opcode.JMPL, post) } return nil @@ -819,7 +819,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { ast.Walk(c, n.Cond) // 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). @@ -830,7 +830,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { } // Jump back to condition. - emit.Jmp(c.prog.BinWriter, opcode.JMP, fstart) + emit.Jmp(c.prog.BinWriter, opcode.JMPL, fstart) c.setLabel(fend) 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.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 { emit.Opcode(c.prog.BinWriter, opcode.DUP) @@ -881,7 +881,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { c.setLabel(post) 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.dropStackLabel() @@ -1346,21 +1346,28 @@ func (c *codegen) writeJumps(b []byte) error { ctx := vm.NewContext(b) for op, _, err := ctx.Next(); err == nil && ctx.NextIP() < len(b); op, _, err = ctx.Next() { 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 nextIP := ctx.NextIP() - arg := b[nextIP-2:] + arg := b[nextIP-4:] index := binary.LittleEndian.Uint16(arg) if int(index) > len(c.l) { return fmt.Errorf("unexpected label number: %d (max %d)", index, len(c.l)) } - offset := c.l[index] - nextIP + 3 - if offset > math.MaxUint16 { - return fmt.Errorf("label offset is too big at the instruction %d: %d (max %d)", - nextIP-3, offset, math.MaxUint16) + offset := c.l[index] - nextIP + 5 + if offset > math.MaxInt32 || offset < math.MinInt32 { + return fmt.Errorf("label offset is too big at the instruction %d: %d (max %d, min %d)", + nextIP-5, offset, math.MaxInt32, math.MinInt32) } - binary.LittleEndian.PutUint16(arg, uint16(offset)) + binary.LittleEndian.PutUint32(arg, uint32(offset)) } } return nil diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 4c0448234..65f46dbe0 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -48,18 +48,18 @@ type rpcTestCase struct { check func(t *testing.T, e *executor, result interface{}) } -const testContractHash = "8bb068bca226bf013e7d19400b9d85c4eb865607" +const testContractHash = "6b9e88be61028590ebbb1296cbee09beed4ae75d" var rpcTestCases = map[string][]rpcTestCase{ "getapplicationlog": { { name: "positive", - params: `["66238fd4ac778326b0c151c025ee8f1c6d738e7e191820537564d2b887f3ecde"]`, + params: `["4459d8050e0abb0a42a9ec5a84dee1c5f8c449b11859265ef45a47c5ea369bc5"]`, result: func(e *executor) interface{} { return &result.ApplicationLog{} }, check: func(t *testing.T, e *executor, acc interface{}) { res, ok := acc.(*result.ApplicationLog) require.True(t, ok) - expectedTxHash, err := util.Uint256DecodeStringLE("66238fd4ac778326b0c151c025ee8f1c6d738e7e191820537564d2b887f3ecde") + expectedTxHash, err := util.Uint256DecodeStringLE("4459d8050e0abb0a42a9ec5a84dee1c5f8c449b11859265ef45a47c5ea369bc5") require.NoError(t, err) assert.Equal(t, expectedTxHash, res.TxHash) assert.Equal(t, 1, len(res.Executions)) diff --git a/pkg/rpc/server/testdata/test_contract.avm b/pkg/rpc/server/testdata/test_contract.avm index 19a8d3404..07f9c80ed 100755 Binary files a/pkg/rpc/server/testdata/test_contract.avm and b/pkg/rpc/server/testdata/test_contract.avm differ diff --git a/pkg/rpc/server/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc index 4f28d0d7a..32d68bff9 100644 Binary files a/pkg/rpc/server/testdata/testblocks.acc and b/pkg/rpc/server/testdata/testblocks.acc differ diff --git a/pkg/vm/context.go b/pkg/vm/context.go index 01d4c039a..3e67bc6f5 100644 --- a/pkg/vm/context.go +++ b/pkg/vm/context.go @@ -99,9 +99,13 @@ func (c *Context) Next() (opcode.Opcode, []byte, error) { numtoread = int(n) c.nextip += 4 } - case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.CALL: - numtoread = 2 - case opcode.SYSCALL: + case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.JMPEQ, opcode.JMPNE, + opcode.JMPGT, opcode.JMPGE, opcode.JMPLT, opcode.JMPLE, + 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 case opcode.APPCALL, opcode.TAILCALL: numtoread = 20 diff --git a/pkg/vm/emit/emit.go b/pkg/vm/emit/emit.go index 4aa5ef688..1401d4364 100644 --- a/pkg/vm/emit/emit.go +++ b/pkg/vm/emit/emit.go @@ -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()) return } - buf := make([]byte, 2) + buf := make([]byte, 4) binary.LittleEndian.PutUint16(buf, label) Instruction(w, op, buf) } @@ -172,10 +172,7 @@ func AppCallWithOperation(w *io.BinWriter, scriptHash util.Uint160, operation st } func isInstructionJmp(op opcode.Opcode) bool { - if op == opcode.JMP || op == opcode.JMPIFNOT || op == opcode.JMPIF || op == opcode.CALL { - return true - } - return false + return opcode.JMP <= op && op <= opcode.CALLL } // InteropNameToID returns an identificator of the method based on its name. diff --git a/pkg/vm/opcode/opcode.go b/pkg/vm/opcode/opcode.go index c4f5f50c8..3e4a5d640 100644 --- a/pkg/vm/opcode/opcode.go +++ b/pkg/vm/opcode/opcode.go @@ -42,14 +42,32 @@ const ( PUSH15 Opcode = 0x1F 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 - // Flow control - NOP Opcode = 0x61 - JMP Opcode = 0x62 - JMPIF Opcode = 0x63 - JMPIFNOT Opcode = 0x64 - CALL Opcode = 0x65 RET Opcode = 0x66 APPCALL Opcode = 0x67 SYSCALL Opcode = 0x68 diff --git a/pkg/vm/opcode/opcode_string.go b/pkg/vm/opcode/opcode_string.go index 72e791e08..3b34e8fdd 100644 --- a/pkg/vm/opcode/opcode_string.go +++ b/pkg/vm/opcode/opcode_string.go @@ -38,12 +38,28 @@ func _() { _ = x[PUSH14-30] _ = x[PUSH15-31] _ = x[PUSH16-32] + _ = x[NOP-33] + _ = x[JMP-34] + _ = x[JMPL-35] + _ = x[JMPIF-36] + _ = 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[NOP-97] - _ = x[JMP-98] - _ = x[JMPIF-99] - _ = x[JMPIFNOT-100] - _ = x[CALL-101] _ = x[RET-102] _ = x[APPCALL-103] _ = x[SYSCALL-104] @@ -125,7 +141,7 @@ func _() { _ = x[THROWIFNOT-241] } -const _Opcode_name = "PUSHINT8PUSHINT16PUSHINT32PUSHINT64PUSHINT128PUSHINT256PUSHNULLPUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16OLDPUSH1NOPJMPJMPIFJMPIFNOTCALLRETAPPCALLSYSCALLTAILCALLDUPFROMALTSTACKTOALTSTACKFROMALTSTACKXDROPISNULLXSWAPXTUCKDEPTHDROPDUPNIPOVERPICKROLLROTSWAPTUCKCATSUBSTRLEFTRIGHTSIZEINVERTANDORXOREQUALINCDECSIGNNEGATEABSNOTNZADDSUBMULDIVMODSHLSHRBOOLANDBOOLORNUMEQUALNUMNOTEQUALLTGTLTEGTEMINMAXWITHINSHA1SHA256HASH160HASH256CHECKSIGVERIFYCHECKMULTISIGARRAYSIZEPACKUNPACKPICKITEMSETITEMNEWARRAYNEWSTRUCTNEWMAPAPPENDREVERSEREMOVEHASKEYKEYSVALUESTHROWTHROWIFNOT" +const _Opcode_name = "PUSHINT8PUSHINT16PUSHINT32PUSHINT64PUSHINT128PUSHINT256PUSHNULLPUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16NOPJMPJMP_LJMPIFJMPIF_LJMPIFNOTJMPIFNOT_LJMPEQJMPEQ_LJMPNEJMPNE_LJMPGTJMPGT_LJMPGEJMPGE_LJMPLTJMPLT_LJMPLEJMPLE_LCALLCALL_LOLDPUSH1RETAPPCALLSYSCALLTAILCALLDUPFROMALTSTACKTOALTSTACKFROMALTSTACKXDROPISNULLXSWAPXTUCKDEPTHDROPDUPNIPOVERPICKROLLROTSWAPTUCKCATSUBSTRLEFTRIGHTSIZEINVERTANDORXOREQUALINCDECSIGNNEGATEABSNOTNZADDSUBMULDIVMODSHLSHRBOOLANDBOOLORNUMEQUALNUMNOTEQUALLTGTLTEGTEMINMAXWITHINSHA1SHA256HASH160HASH256CHECKSIGVERIFYCHECKMULTISIGARRAYSIZEPACKUNPACKPICKITEMSETITEMNEWARRAYNEWSTRUCTNEWMAPAPPENDREVERSEREMOVEHASKEYKEYSVALUESTHROWTHROWIFNOT" var _Opcode_map = map[Opcode]string{ 0: _Opcode_name[0:8], @@ -156,91 +172,107 @@ var _Opcode_map = map[Opcode]string{ 30: _Opcode_name[170:176], 31: _Opcode_name[176:182], 32: _Opcode_name[182:188], - 81: _Opcode_name[188:196], - 97: _Opcode_name[196:199], - 98: _Opcode_name[199:202], - 99: _Opcode_name[202:207], - 100: _Opcode_name[207:215], - 101: _Opcode_name[215:219], - 102: _Opcode_name[219:222], - 103: _Opcode_name[222:229], - 104: _Opcode_name[229:236], - 105: _Opcode_name[236:244], - 106: _Opcode_name[244:259], - 107: _Opcode_name[259:269], - 108: _Opcode_name[269:281], - 109: _Opcode_name[281:286], - 112: _Opcode_name[286:292], - 114: _Opcode_name[292:297], - 115: _Opcode_name[297:302], - 116: _Opcode_name[302:307], - 117: _Opcode_name[307:311], - 118: _Opcode_name[311:314], - 119: _Opcode_name[314:317], - 120: _Opcode_name[317:321], - 121: _Opcode_name[321:325], - 122: _Opcode_name[325:329], - 123: _Opcode_name[329:332], - 124: _Opcode_name[332:336], - 125: _Opcode_name[336:340], - 126: _Opcode_name[340:343], - 127: _Opcode_name[343:349], - 128: _Opcode_name[349:353], - 129: _Opcode_name[353:358], - 130: _Opcode_name[358:362], - 131: _Opcode_name[362:368], - 132: _Opcode_name[368:371], - 133: _Opcode_name[371:373], - 134: _Opcode_name[373:376], - 135: _Opcode_name[376:381], - 139: _Opcode_name[381:384], - 140: _Opcode_name[384:387], - 141: _Opcode_name[387:391], - 143: _Opcode_name[391:397], - 144: _Opcode_name[397:400], - 145: _Opcode_name[400:403], - 146: _Opcode_name[403:405], - 147: _Opcode_name[405:408], - 148: _Opcode_name[408:411], - 149: _Opcode_name[411:414], - 150: _Opcode_name[414:417], - 151: _Opcode_name[417:420], - 152: _Opcode_name[420:423], - 153: _Opcode_name[423:426], - 154: _Opcode_name[426:433], - 155: _Opcode_name[433:439], - 156: _Opcode_name[439:447], - 158: _Opcode_name[447:458], - 159: _Opcode_name[458:460], - 160: _Opcode_name[460:462], - 161: _Opcode_name[462:465], - 162: _Opcode_name[465:468], - 163: _Opcode_name[468:471], - 164: _Opcode_name[471:474], - 165: _Opcode_name[474:480], - 167: _Opcode_name[480:484], - 168: _Opcode_name[484:490], - 169: _Opcode_name[490:497], - 170: _Opcode_name[497:504], - 172: _Opcode_name[504:512], - 173: _Opcode_name[512:518], - 174: _Opcode_name[518:531], - 192: _Opcode_name[531:540], - 193: _Opcode_name[540:544], - 194: _Opcode_name[544:550], - 195: _Opcode_name[550:558], - 196: _Opcode_name[558:565], - 197: _Opcode_name[565:573], - 198: _Opcode_name[573:582], - 199: _Opcode_name[582:588], - 200: _Opcode_name[588:594], - 201: _Opcode_name[594:601], - 202: _Opcode_name[601:607], - 203: _Opcode_name[607:613], - 204: _Opcode_name[613:617], - 205: _Opcode_name[617:623], - 240: _Opcode_name[623:628], - 241: _Opcode_name[628:638], + 33: _Opcode_name[188:191], + 34: _Opcode_name[191:194], + 35: _Opcode_name[194:199], + 36: _Opcode_name[199:204], + 37: _Opcode_name[204:211], + 38: _Opcode_name[211:219], + 39: _Opcode_name[219:229], + 40: _Opcode_name[229:234], + 41: _Opcode_name[234:241], + 42: _Opcode_name[241:246], + 43: _Opcode_name[246:253], + 44: _Opcode_name[253:258], + 45: _Opcode_name[258:265], + 46: _Opcode_name[265:270], + 47: _Opcode_name[270:277], + 48: _Opcode_name[277:282], + 49: _Opcode_name[282:289], + 50: _Opcode_name[289:294], + 51: _Opcode_name[294:301], + 52: _Opcode_name[301:305], + 53: _Opcode_name[305:311], + 81: _Opcode_name[311:319], + 102: _Opcode_name[319:322], + 103: _Opcode_name[322:329], + 104: _Opcode_name[329:336], + 105: _Opcode_name[336:344], + 106: _Opcode_name[344:359], + 107: _Opcode_name[359:369], + 108: _Opcode_name[369:381], + 109: _Opcode_name[381:386], + 112: _Opcode_name[386:392], + 114: _Opcode_name[392:397], + 115: _Opcode_name[397:402], + 116: _Opcode_name[402:407], + 117: _Opcode_name[407:411], + 118: _Opcode_name[411:414], + 119: _Opcode_name[414:417], + 120: _Opcode_name[417:421], + 121: _Opcode_name[421:425], + 122: _Opcode_name[425:429], + 123: _Opcode_name[429:432], + 124: _Opcode_name[432:436], + 125: _Opcode_name[436:440], + 126: _Opcode_name[440:443], + 127: _Opcode_name[443:449], + 128: _Opcode_name[449:453], + 129: _Opcode_name[453:458], + 130: _Opcode_name[458:462], + 131: _Opcode_name[462:468], + 132: _Opcode_name[468:471], + 133: _Opcode_name[471:473], + 134: _Opcode_name[473:476], + 135: _Opcode_name[476:481], + 139: _Opcode_name[481:484], + 140: _Opcode_name[484:487], + 141: _Opcode_name[487:491], + 143: _Opcode_name[491:497], + 144: _Opcode_name[497:500], + 145: _Opcode_name[500:503], + 146: _Opcode_name[503:505], + 147: _Opcode_name[505:508], + 148: _Opcode_name[508:511], + 149: _Opcode_name[511:514], + 150: _Opcode_name[514:517], + 151: _Opcode_name[517:520], + 152: _Opcode_name[520:523], + 153: _Opcode_name[523:526], + 154: _Opcode_name[526:533], + 155: _Opcode_name[533:539], + 156: _Opcode_name[539:547], + 158: _Opcode_name[547:558], + 159: _Opcode_name[558:560], + 160: _Opcode_name[560:562], + 161: _Opcode_name[562:565], + 162: _Opcode_name[565:568], + 163: _Opcode_name[568:571], + 164: _Opcode_name[571:574], + 165: _Opcode_name[574:580], + 167: _Opcode_name[580:584], + 168: _Opcode_name[584:590], + 169: _Opcode_name[590:597], + 170: _Opcode_name[597:604], + 172: _Opcode_name[604:612], + 173: _Opcode_name[612:618], + 174: _Opcode_name[618:631], + 192: _Opcode_name[631:640], + 193: _Opcode_name[640:644], + 194: _Opcode_name[644:650], + 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 { diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index ece7ef0e4..f6a88ac8c 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -1118,16 +1118,25 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro arr := elem.Bytes() 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) cond := true - if op != opcode.JMP { - cond = v.estack.Pop().Bool() == (op == opcode.JMPIF) + switch op { + 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) - case opcode.CALL: + case opcode.CALL, opcode.CALLL: v.checkInvocationStackSize() newCtx := ctx.Copy() @@ -1293,6 +1302,27 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro 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. func (v *VM) jumpIf(ctx *Context, offset int, cond bool) { if cond { @@ -1302,9 +1332,18 @@ func (v *VM) jumpIf(ctx *Context, offset int, cond bool) { // getJumpOffset returns instruction number in a current context // 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 { - 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 if offset < 0 || offset > len(ctx.prog) { panic(fmt.Sprintf("JMP: invalid offset %d ip at %d", offset, ctx.ip)) diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 6f2738d24..29a793f5f 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -719,9 +719,9 @@ func callNTimes(n uint16) []byte { return makeProgram( opcode.PUSHINT16, opcode.Opcode(n), opcode.Opcode(n>>8), // little-endian opcode.TOALTSTACK, opcode.DUPFROMALTSTACK, - opcode.JMPIF, 0x4, 0, opcode.RET, + opcode.JMPIF, 0x3, opcode.RET, opcode.FROMALTSTACK, opcode.DEC, - opcode.CALL, 0xF8, 0xFF) // -8 -> JMP to TOALTSTACK) + opcode.CALL, 0xF9) // -7 -> JMP to TOALTSTACK) } func TestInvocationLimitGood(t *testing.T) { @@ -736,6 +736,96 @@ func TestInvocationLimitBad(t *testing.T) { 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) { prog := makeProgram(opcode.NOT) vm := load(prog) @@ -1798,7 +1888,7 @@ func TestSimpleCall(t *testing.T) { buf := io.NewBufBinWriter() w := buf.BinWriter 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.PUSH10) emit.Opcode(w, opcode.ADD)