diff --git a/pkg/compiler/analysis.go b/pkg/compiler/analysis.go index fb25eab49..45105b2c3 100644 --- a/pkg/compiler/analysis.go +++ b/pkg/compiler/analysis.go @@ -17,6 +17,7 @@ var ( "AppCall", "FromAddress", "Equals", "panic", + "ToBool", "ToByteArray", "ToInteger", } ) diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 9199e5607..f2a150daa 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -123,7 +123,9 @@ func (c *codegen) emitLoadConst(t types.TypeAndValue) { func (c *codegen) convertBasicType(t types.TypeAndValue, typ *types.Basic) { switch typ.Kind() { - case types.Int, types.UntypedInt, types.Uint: + case types.Int, types.UntypedInt, types.Uint, + types.Int16, types.Uint16, + types.Int32, types.Uint32, types.Int64, types.Uint64: val, _ := constant.Int64Val(t.Value) emit.Int(c.prog.BinWriter, val) case types.String, types.UntypedString: @@ -1053,6 +1055,15 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) { } else { c.prog.Err = errors.New("panic should have string or nil argument") } + case "ToInteger", "ToByteArray", "ToBool": + typ := vm.IntegerT + switch name { + case "ToByteArray": + typ = vm.ByteArrayT + case "ToBool": + typ = vm.BooleanT + } + emit.Instruction(c.prog.BinWriter, opcode.CONVERT, []byte{byte(typ)}) case "SHA256": emit.Opcode(c.prog.BinWriter, opcode.SHA256) case "SHA1": diff --git a/pkg/compiler/convert_test.go b/pkg/compiler/convert_test.go new file mode 100644 index 000000000..0bcd8eace --- /dev/null +++ b/pkg/compiler/convert_test.go @@ -0,0 +1,62 @@ +package compiler_test + +import ( + "fmt" + "math/big" + "testing" +) + +type convertTestCase struct { + returnType string + argValue string + result interface{} +} + +func getFunctionName(typ string) string { + switch typ { + case "bool": + return "Bool" + case "[]byte": + return "ByteArray" + case "int64": + return "Integer" + } + panic("invalid type") +} + +func TestConvert(t *testing.T) { + srcTmpl := `package foo + import "github.com/nspcc-dev/neo-go/pkg/interop/convert" + func Main() %s { + arg := %s + return convert.To%s(arg) + }` + + convertTestCases := []convertTestCase{ + {"bool", "true", true}, + {"bool", "false", false}, + {"bool", "12", true}, + {"bool", "0", false}, + {"bool", "[]byte{0, 1, 0}", true}, + {"bool", "[]byte{0}", false}, + {"int64", "true", big.NewInt(1)}, + {"int64", "false", big.NewInt(0)}, + {"int64", "12", big.NewInt(12)}, + {"int64", "0", big.NewInt(0)}, + {"int64", "[]byte{0, 1, 0}", big.NewInt(256)}, + {"int64", "[]byte{0}", big.NewInt(0)}, + {"[]byte", "true", []byte{1}}, + {"[]byte", "false", []byte{0}}, + {"[]byte", "12", []byte{0x0C}}, + {"[]byte", "0", []byte{}}, + {"[]byte", "[]byte{0, 1, 0}", []byte{0, 1, 0}}, + } + + for _, tc := range convertTestCases { + name := getFunctionName(tc.returnType) + t.Run(tc.argValue+"->"+name, func(t *testing.T) { + src := fmt.Sprintf(srcTmpl, tc.returnType, tc.argValue, name) + eval(t, src, tc.result) + }) + } +} diff --git a/pkg/compiler/return_test.go b/pkg/compiler/return_test.go index a97c3a25a..ae656ae7a 100644 --- a/pkg/compiler/return_test.go +++ b/pkg/compiler/return_test.go @@ -5,6 +5,14 @@ import ( "testing" ) +func TestReturnInt64(t *testing.T) { + src := `package foo + func Main() int64 { + return 1 + }` + eval(t, src, big.NewInt(1)) +} + func TestMultipleReturn1(t *testing.T) { src := ` package hello diff --git a/pkg/compiler/struct_test.go b/pkg/compiler/struct_test.go index 3cca26fd7..aa0da998e 100644 --- a/pkg/compiler/struct_test.go +++ b/pkg/compiler/struct_test.go @@ -274,7 +274,7 @@ var structTestCases = []testCase{ vm.NewBigIntegerItem(big.NewInt(1)), vm.NewBigIntegerItem(big.NewInt(2)), vm.NewByteArrayItem([]byte("hello")), - vm.NewByteArrayItem([]byte{}), + vm.NewBoolItem(false), }, }, { diff --git a/pkg/consensus/payload.go b/pkg/consensus/payload.go index a8bca0391..f202e4065 100644 --- a/pkg/consensus/payload.go +++ b/pkg/consensus/payload.go @@ -222,9 +222,7 @@ func (p *Payload) Verify(scriptHash util.Uint160) bool { return false } - res, err := v.Estack().Pop().TryBool() - - return err == nil && res + return v.Estack().Pop().Bool() } // DecodeBinaryUnsigned reads payload from w excluding signature. diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 06ca8a4d6..01b7b6dd8 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -1656,11 +1656,7 @@ func (bc *Blockchain) verifyHashAgainstScript(hash util.Uint160, witness *transa } resEl := vm.Estack().Pop() if resEl != nil { - res, err := resEl.TryBool() - if err != nil { - return err - } - if !res { + if !resEl.Bool() { return errors.Errorf("signature check failed") } if useKeys { diff --git a/pkg/interop/convert/convert.go b/pkg/interop/convert/convert.go new file mode 100644 index 000000000..9cba7ae0f --- /dev/null +++ b/pkg/interop/convert/convert.go @@ -0,0 +1,17 @@ +// Package convert provides functions for type conversion. +package convert + +// ToInteger converts it's argument to an Integer. +func ToInteger(v interface{}) int64 { + return 0 +} + +// ToByteArray converts it's argument to a ByteArray. +func ToByteArray(v interface{}) []byte { + return nil +} + +// ToBool converts it's argument to a Boolean. +func ToBool(v interface{}) bool { + return false +} diff --git a/pkg/vm/context.go b/pkg/vm/context.go index 8296f2a8c..4d1b9c00e 100644 --- a/pkg/vm/context.go +++ b/pkg/vm/context.go @@ -101,7 +101,7 @@ func (c *Context) Next() (opcode.Opcode, []byte, error) { } case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.JMPEQ, opcode.JMPNE, opcode.JMPGT, opcode.JMPGE, opcode.JMPLT, opcode.JMPLE, - opcode.CALL, opcode.ISTYPE, opcode.NEWARRAYT: + opcode.CALL, opcode.ISTYPE, opcode.CONVERT, opcode.NEWARRAYT: numtoread = 1 case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL, opcode.JMPEQL, opcode.JMPNEL, opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLTL, opcode.JMPLEL, @@ -176,6 +176,9 @@ func (c *Context) Dup() StackItem { return c } +// Bool implements StackItem interface. +func (c *Context) Bool() bool { panic("can't convert Context to Bool") } + // TryBytes implements StackItem interface. func (c *Context) TryBytes() ([]byte, error) { return nil, errors.New("can't convert Context to ByteArray") @@ -189,6 +192,11 @@ func (c *Context) TryInteger() (*big.Int, error) { // Type implements StackItem interface. func (c *Context) Type() StackItemType { panic("Context cannot appear on evaluation stack") } +// Convert implements StackItem interface. +func (c *Context) Convert(_ StackItemType) (StackItem, error) { + panic("Context cannot be converted to anything") +} + // Equals implements StackItem interface. func (c *Context) Equals(s StackItem) bool { return c == s diff --git a/pkg/vm/emit/emit.go b/pkg/vm/emit/emit.go index 1401d4364..f510efb7d 100644 --- a/pkg/vm/emit/emit.go +++ b/pkg/vm/emit/emit.go @@ -31,6 +31,7 @@ func Bool(w *io.BinWriter, ok bool) { return } Opcode(w, opcode.PUSHF) + Instruction(w, opcode.CONVERT, []byte{0x20}) // 0x20 for Boolean type } func padRight(s int, buf []byte) []byte { diff --git a/pkg/vm/opcode/opcode.go b/pkg/vm/opcode/opcode.go index e6f3aacab..d918e8d87 100644 --- a/pkg/vm/opcode/opcode.go +++ b/pkg/vm/opcode/opcode.go @@ -73,8 +73,6 @@ const ( SYSCALL Opcode = 0x68 TAILCALL Opcode = 0x69 - ISNULL Opcode = 0x70 - // Stack DUPFROMALTSTACK Opcode = 0x6A TOALTSTACK Opcode = 0x6B @@ -163,7 +161,9 @@ const ( CLEARITEMS Opcode = 0xD3 // Types - ISTYPE Opcode = 0xD9 + ISNULL Opcode = 0xD8 + ISTYPE Opcode = 0xD9 + CONVERT Opcode = 0xDB // Exceptions THROW Opcode = 0xF0 diff --git a/pkg/vm/opcode/opcode_string.go b/pkg/vm/opcode/opcode_string.go index b550db451..2447a7dca 100644 --- a/pkg/vm/opcode/opcode_string.go +++ b/pkg/vm/opcode/opcode_string.go @@ -64,7 +64,6 @@ func _() { _ = x[APPCALL-103] _ = x[SYSCALL-104] _ = x[TAILCALL-105] - _ = x[ISNULL-112] _ = x[DUPFROMALTSTACK-106] _ = x[TOALTSTACK-107] _ = x[FROMALTSTACK-108] @@ -140,12 +139,14 @@ func _() { _ = x[REVERSEITEMS-209] _ = x[REMOVE-210] _ = x[CLEARITEMS-211] + _ = x[ISNULL-216] _ = x[ISTYPE-217] + _ = x[CONVERT-219] _ = x[THROW-240] _ = x[THROWIFNOT-241] } -const _Opcode_name = "PUSHINT8PUSHINT16PUSHINT32PUSHINT64PUSHINT128PUSHINT256PUSHNULLPUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16NOPJMPJMPLJMPIFJMPIFLJMPIFNOTJMPIFNOTLJMPEQJMPEQLJMPNEJMPNELJMPGTJMPGTLJMPGEJMPGELJMPLTJMPLTLJMPLEJMPLELCALLCALLLOLDPUSH1RETAPPCALLSYSCALLTAILCALLDUPFROMALTSTACKTOALTSTACKFROMALTSTACKXDROPISNULLXSWAPXTUCKDEPTHDROPDUPNIPOVERPICKROLLROTSWAPTUCKCATSUBSTRLEFTRIGHTINVERTANDORXOREQUALINCDECSIGNNEGATEABSNOTNZADDSUBMULDIVMODSHLSHRBOOLANDBOOLORNUMEQUALNUMNOTEQUALLTGTLTEGTEMINMAXWITHINSHA1SHA256HASH160HASH256CHECKSIGVERIFYCHECKMULTISIGPACKUNPACKNEWARRAY0NEWARRAYNEWARRAYTNEWSTRUCT0NEWSTRUCTNEWMAPSIZEHASKEYKEYSVALUESPICKITEMAPPENDSETITEMREVERSEITEMSREMOVECLEARITEMSISTYPETHROWTHROWIFNOT" +const _Opcode_name = "PUSHINT8PUSHINT16PUSHINT32PUSHINT64PUSHINT128PUSHINT256PUSHNULLPUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16NOPJMPJMPLJMPIFJMPIFLJMPIFNOTJMPIFNOTLJMPEQJMPEQLJMPNEJMPNELJMPGTJMPGTLJMPGEJMPGELJMPLTJMPLTLJMPLEJMPLELCALLCALLLOLDPUSH1RETAPPCALLSYSCALLTAILCALLDUPFROMALTSTACKTOALTSTACKFROMALTSTACKXDROPXSWAPXTUCKDEPTHDROPDUPNIPOVERPICKROLLROTSWAPTUCKCATSUBSTRLEFTRIGHTINVERTANDORXOREQUALINCDECSIGNNEGATEABSNOTNZADDSUBMULDIVMODSHLSHRBOOLANDBOOLORNUMEQUALNUMNOTEQUALLTGTLTEGTEMINMAXWITHINSHA1SHA256HASH160HASH256CHECKSIGVERIFYCHECKMULTISIGPACKUNPACKNEWARRAY0NEWARRAYNEWARRAYTNEWSTRUCT0NEWSTRUCTNEWMAPSIZEHASKEYKEYSVALUESPICKITEMAPPENDSETITEMREVERSEITEMSREMOVECLEARITEMSISNULLISTYPECONVERTTHROWTHROWIFNOT" var _Opcode_map = map[Opcode]string{ 0: _Opcode_name[0:8], @@ -206,81 +207,82 @@ var _Opcode_map = map[Opcode]string{ 107: _Opcode_name[349:359], 108: _Opcode_name[359:371], 109: _Opcode_name[371:376], - 112: _Opcode_name[376:382], - 114: _Opcode_name[382:387], - 115: _Opcode_name[387:392], - 116: _Opcode_name[392:397], - 117: _Opcode_name[397:401], - 118: _Opcode_name[401:404], - 119: _Opcode_name[404:407], - 120: _Opcode_name[407:411], - 121: _Opcode_name[411:415], - 122: _Opcode_name[415:419], - 123: _Opcode_name[419:422], - 124: _Opcode_name[422:426], - 125: _Opcode_name[426:430], - 126: _Opcode_name[430:433], - 127: _Opcode_name[433:439], - 128: _Opcode_name[439:443], - 129: _Opcode_name[443:448], - 131: _Opcode_name[448:454], - 132: _Opcode_name[454:457], - 133: _Opcode_name[457:459], - 134: _Opcode_name[459:462], - 135: _Opcode_name[462:467], - 139: _Opcode_name[467:470], - 140: _Opcode_name[470:473], - 141: _Opcode_name[473:477], - 143: _Opcode_name[477:483], - 144: _Opcode_name[483:486], - 145: _Opcode_name[486:489], - 146: _Opcode_name[489:491], - 147: _Opcode_name[491:494], - 148: _Opcode_name[494:497], - 149: _Opcode_name[497:500], - 150: _Opcode_name[500:503], - 151: _Opcode_name[503:506], - 152: _Opcode_name[506:509], - 153: _Opcode_name[509:512], - 154: _Opcode_name[512:519], - 155: _Opcode_name[519:525], - 156: _Opcode_name[525:533], - 158: _Opcode_name[533:544], - 159: _Opcode_name[544:546], - 160: _Opcode_name[546:548], - 161: _Opcode_name[548:551], - 162: _Opcode_name[551:554], - 163: _Opcode_name[554:557], - 164: _Opcode_name[557:560], - 165: _Opcode_name[560:566], - 167: _Opcode_name[566:570], - 168: _Opcode_name[570:576], - 169: _Opcode_name[576:583], - 170: _Opcode_name[583:590], - 172: _Opcode_name[590:598], - 173: _Opcode_name[598:604], - 174: _Opcode_name[604:617], - 192: _Opcode_name[617:621], - 193: _Opcode_name[621:627], - 194: _Opcode_name[627:636], - 195: _Opcode_name[636:644], - 196: _Opcode_name[644:653], - 197: _Opcode_name[653:663], - 198: _Opcode_name[663:672], - 200: _Opcode_name[672:678], - 202: _Opcode_name[678:682], - 203: _Opcode_name[682:688], - 204: _Opcode_name[688:692], - 205: _Opcode_name[692:698], - 206: _Opcode_name[698:706], - 207: _Opcode_name[706:712], - 208: _Opcode_name[712:719], - 209: _Opcode_name[719:731], - 210: _Opcode_name[731:737], - 211: _Opcode_name[737:747], + 114: _Opcode_name[376:381], + 115: _Opcode_name[381:386], + 116: _Opcode_name[386:391], + 117: _Opcode_name[391:395], + 118: _Opcode_name[395:398], + 119: _Opcode_name[398:401], + 120: _Opcode_name[401:405], + 121: _Opcode_name[405:409], + 122: _Opcode_name[409:413], + 123: _Opcode_name[413:416], + 124: _Opcode_name[416:420], + 125: _Opcode_name[420:424], + 126: _Opcode_name[424:427], + 127: _Opcode_name[427:433], + 128: _Opcode_name[433:437], + 129: _Opcode_name[437:442], + 131: _Opcode_name[442:448], + 132: _Opcode_name[448:451], + 133: _Opcode_name[451:453], + 134: _Opcode_name[453:456], + 135: _Opcode_name[456:461], + 139: _Opcode_name[461:464], + 140: _Opcode_name[464:467], + 141: _Opcode_name[467:471], + 143: _Opcode_name[471:477], + 144: _Opcode_name[477:480], + 145: _Opcode_name[480:483], + 146: _Opcode_name[483:485], + 147: _Opcode_name[485:488], + 148: _Opcode_name[488:491], + 149: _Opcode_name[491:494], + 150: _Opcode_name[494:497], + 151: _Opcode_name[497:500], + 152: _Opcode_name[500:503], + 153: _Opcode_name[503:506], + 154: _Opcode_name[506:513], + 155: _Opcode_name[513:519], + 156: _Opcode_name[519:527], + 158: _Opcode_name[527:538], + 159: _Opcode_name[538:540], + 160: _Opcode_name[540:542], + 161: _Opcode_name[542:545], + 162: _Opcode_name[545:548], + 163: _Opcode_name[548:551], + 164: _Opcode_name[551:554], + 165: _Opcode_name[554:560], + 167: _Opcode_name[560:564], + 168: _Opcode_name[564:570], + 169: _Opcode_name[570:577], + 170: _Opcode_name[577:584], + 172: _Opcode_name[584:592], + 173: _Opcode_name[592:598], + 174: _Opcode_name[598:611], + 192: _Opcode_name[611:615], + 193: _Opcode_name[615:621], + 194: _Opcode_name[621:630], + 195: _Opcode_name[630:638], + 196: _Opcode_name[638:647], + 197: _Opcode_name[647:657], + 198: _Opcode_name[657:666], + 200: _Opcode_name[666:672], + 202: _Opcode_name[672:676], + 203: _Opcode_name[676:682], + 204: _Opcode_name[682:686], + 205: _Opcode_name[686:692], + 206: _Opcode_name[692:700], + 207: _Opcode_name[700:706], + 208: _Opcode_name[706:713], + 209: _Opcode_name[713:725], + 210: _Opcode_name[725:731], + 211: _Opcode_name[731:741], + 216: _Opcode_name[741:747], 217: _Opcode_name[747:753], - 240: _Opcode_name[753:758], - 241: _Opcode_name[758:768], + 219: _Opcode_name[753:760], + 240: _Opcode_name[760:765], + 241: _Opcode_name[765:775], } func (i Opcode) String() string { diff --git a/pkg/vm/stack.go b/pkg/vm/stack.go index 6730c4456..fd779e941 100644 --- a/pkg/vm/stack.go +++ b/pkg/vm/stack.go @@ -80,38 +80,9 @@ func (e *Element) BigInt() *big.Int { return val } -// TryBool attempts to get the underlying value of the element as a boolean. -// Returns error if can't convert value to boolean type. -func (e *Element) TryBool() (bool, error) { - switch t := e.value.(type) { - case *BigIntegerItem: - return t.value.Int64() != 0, nil - case *BoolItem: - return t.value, nil - case *ArrayItem, *StructItem: - return true, nil - case *ByteArrayItem: - for _, b := range t.value { - if b != 0 { - return true, nil - } - } - return false, nil - case *InteropItem: - return t.value != nil, nil - default: - return false, fmt.Errorf("can't convert to bool: " + t.String()) - } -} - -// Bool attempts to get the underlying value of the element as a boolean. -// Will panic if the assertion failed which will be caught by the VM. +// Bool converts an underlying value of the element to a boolean. func (e *Element) Bool() bool { - val, err := e.TryBool() - if err != nil { - panic(err) - } - return val + return e.value.Bool() } // Bytes attempts to get the underlying value of the element as a byte array. diff --git a/pkg/vm/stack_item.go b/pkg/vm/stack_item.go index fbe80346f..bca4e2745 100644 --- a/pkg/vm/stack_item.go +++ b/pkg/vm/stack_item.go @@ -20,6 +20,8 @@ type StackItem interface { Value() interface{} // Dup duplicates current StackItem. Dup() StackItem + // Bool converts StackItem to a boolean value. + Bool() bool // TryBytes converts StackItem to a byte slice. TryBytes() ([]byte, error) // TryInteger converts StackItem to an integer. @@ -30,8 +32,12 @@ type StackItem interface { ToContractParameter(map[StackItem]bool) smartcontract.Parameter // Type returns stack item type. Type() StackItemType + // Convert converts StackItem to another type. + Convert(StackItemType) (StackItem, error) } +var errInvalidConversion = errors.New("invalid conversion type") + func makeStackItem(v interface{}) StackItem { switch val := v.(type) { case int: @@ -106,6 +112,33 @@ func makeStackItem(v interface{}) StackItem { } } +// convertPrimitive converts primitive item to a specified type. +func convertPrimitive(item StackItem, typ StackItemType) (StackItem, error) { + if item.Type() == typ { + return item, nil + } + switch typ { + case IntegerT: + bi, err := item.TryInteger() + if err != nil { + return nil, err + } + return NewBigIntegerItem(bi), nil + case ByteArrayT: + b, err := item.TryBytes() + if err != nil { + return nil, err + } + return NewByteArrayItem(b), nil + case BufferT: + panic("TODO") // #877 + case BooleanT: + return NewBoolItem(item.Bool()), nil + default: + return nil, errInvalidConversion + } +} + // StructItem represents a struct on the stack. type StructItem struct { value []StackItem @@ -133,6 +166,9 @@ func (i *StructItem) Dup() StackItem { return i } +// Bool implements StackItem interface. +func (i *StructItem) Bool() bool { return true } + // TryBytes implements StackItem interface. func (i *StructItem) TryBytes() ([]byte, error) { return nil, errors.New("can't convert Struct to ByteArray") @@ -182,6 +218,20 @@ func (i *StructItem) ToContractParameter(seen map[StackItem]bool) smartcontract. // Type implements StackItem interface. func (i *StructItem) Type() StackItemType { return StructT } +// Convert implements StackItem interface. +func (i *StructItem) Convert(typ StackItemType) (StackItem, error) { + switch typ { + case StructT: + return i, nil + case ArrayT: + return NewArrayItem(i.value), nil + case BooleanT: + return NewBoolItem(i.Bool()), nil + default: + return nil, errInvalidConversion + } +} + // Clone returns a Struct with all Struct fields copied by value. // Array fields are still copied by reference. func (i *StructItem) Clone() *StructItem { @@ -217,6 +267,9 @@ func (i NullItem) Dup() StackItem { return i } +// Bool implements StackItem interface. +func (i NullItem) Bool() bool { return false } + // TryBytes implements StackItem interface. func (i NullItem) TryBytes() ([]byte, error) { return nil, errors.New("can't convert Null to ByteArray") @@ -243,6 +296,14 @@ func (i NullItem) ToContractParameter(map[StackItem]bool) smartcontract.Paramete // Type implements StackItem interface. func (i NullItem) Type() StackItemType { return AnyT } +// Convert implements StackItem interface. +func (i NullItem) Convert(typ StackItemType) (StackItem, error) { + if typ == AnyT || !typ.IsValid() { + return nil, errInvalidConversion + } + return i, nil +} + // BigIntegerItem represents a big integer on the stack. type BigIntegerItem struct { value *big.Int @@ -260,6 +321,11 @@ func (i *BigIntegerItem) Bytes() []byte { return emit.IntToBytes(i.value) } +// Bool implements StackItem interface. +func (i *BigIntegerItem) Bool() bool { + return i.value.Sign() != 0 +} + // TryBytes implements StackItem interface. func (i *BigIntegerItem) TryBytes() ([]byte, error) { return i.Bytes(), nil @@ -311,6 +377,11 @@ func (i *BigIntegerItem) ToContractParameter(map[StackItem]bool) smartcontract.P // Type implements StackItem interface. func (i *BigIntegerItem) Type() StackItemType { return IntegerT } +// Convert implements StackItem interface. +func (i *BigIntegerItem) Convert(typ StackItemType) (StackItem, error) { + return convertPrimitive(i, typ) +} + // MarshalJSON implements the json.Marshaler interface. func (i *BigIntegerItem) MarshalJSON() ([]byte, error) { return json.Marshal(i.value) @@ -347,6 +418,9 @@ func (i *BoolItem) Dup() StackItem { return &BoolItem{i.value} } +// Bool implements StackItem interface. +func (i *BoolItem) Bool() bool { return i.value } + // Bytes converts BoolItem to bytes. func (i *BoolItem) Bytes() []byte { if i.value { @@ -394,6 +468,11 @@ func (i *BoolItem) ToContractParameter(map[StackItem]bool) smartcontract.Paramet // Type implements StackItem interface. func (i *BoolItem) Type() StackItemType { return BooleanT } +// Convert implements StackItem interface. +func (i *BoolItem) Convert(typ StackItemType) (StackItem, error) { + return convertPrimitive(i, typ) +} + // ByteArrayItem represents a byte array on the stack. type ByteArrayItem struct { value []byte @@ -420,6 +499,19 @@ func (i *ByteArrayItem) String() string { return "ByteArray" } +// Bool implements StackItem interface. +func (i *ByteArrayItem) Bool() bool { + if len(i.value) > MaxBigIntegerSizeBits/8 { + return true + } + for _, b := range i.value { + if b != 0 { + return true + } + } + return false +} + // TryBytes implements StackItem interface. func (i *ByteArrayItem) TryBytes() ([]byte, error) { return i.value, nil @@ -459,6 +551,11 @@ func (i *ByteArrayItem) ToContractParameter(map[StackItem]bool) smartcontract.Pa // Type implements StackItem interface. func (i *ByteArrayItem) Type() StackItemType { return ByteArrayT } +// Convert implements StackItem interface. +func (i *ByteArrayItem) Convert(typ StackItemType) (StackItem, error) { + return convertPrimitive(i, typ) +} + // ArrayItem represents a new ArrayItem object. type ArrayItem struct { value []StackItem @@ -485,6 +582,9 @@ func (i *ArrayItem) String() string { return "Array" } +// Bool implements StackItem interface. +func (i *ArrayItem) Bool() bool { return true } + // TryBytes implements StackItem interface. func (i *ArrayItem) TryBytes() ([]byte, error) { return nil, errors.New("can't convert Array to ByteArray") @@ -526,6 +626,20 @@ func (i *ArrayItem) ToContractParameter(seen map[StackItem]bool) smartcontract.P // Type implements StackItem interface. func (i *ArrayItem) Type() StackItemType { return ArrayT } +// Convert implements StackItem interface. +func (i *ArrayItem) Convert(typ StackItemType) (StackItem, error) { + switch typ { + case ArrayT: + return i, nil + case StructT: + return NewStructItem(i.value), nil + case BooleanT: + return NewBoolItem(i.Bool()), nil + default: + return nil, errInvalidConversion + } +} + // MapElement is a key-value pair of StackItems. type MapElement struct { Key StackItem @@ -553,6 +667,9 @@ func (i *MapItem) Value() interface{} { return i.value } +// Bool implements StackItem interface. +func (i *MapItem) Bool() bool { return true } + // TryBytes implements StackItem interface. func (i *MapItem) TryBytes() ([]byte, error) { return nil, errors.New("can't convert Map to ByteArray") @@ -614,6 +731,18 @@ func (i *MapItem) ToContractParameter(seen map[StackItem]bool) smartcontract.Par // Type implements StackItem interface. func (i *MapItem) Type() StackItemType { return MapT } +// Convert implements StackItem interface. +func (i *MapItem) Convert(typ StackItemType) (StackItem, error) { + switch typ { + case MapT: + return i, nil + case BooleanT: + return NewBoolItem(i.Bool()), nil + default: + return nil, errInvalidConversion + } +} + // Add adds key-value pair to the map. func (i *MapItem) Add(key, value StackItem) { if !isValidMapKey(key) { @@ -672,6 +801,9 @@ func (i *InteropItem) Dup() StackItem { return i } +// Bool implements StackItem interface. +func (i *InteropItem) Bool() bool { return true } + // TryBytes implements StackItem interface. func (i *InteropItem) TryBytes() ([]byte, error) { return nil, errors.New("can't convert Interop to ByteArray") @@ -704,6 +836,18 @@ func (i *InteropItem) ToContractParameter(map[StackItem]bool) smartcontract.Para // Type implements StackItem interface. func (i *InteropItem) Type() StackItemType { return InteropT } +// Convert implements StackItem interface. +func (i *InteropItem) Convert(typ StackItemType) (StackItem, error) { + switch typ { + case InteropT: + return i, nil + case BooleanT: + return NewBoolItem(i.Bool()), nil + default: + return nil, errInvalidConversion + } +} + // MarshalJSON implements the json.Marshaler interface. func (i *InteropItem) MarshalJSON() ([]byte, error) { return json.Marshal(i.value) diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index e09663755..a645ee48c 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -569,6 +569,15 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro res := v.estack.Pop().Item() v.estack.PushVal(res.Type() == StackItemType(parameter[0])) + case opcode.CONVERT: + typ := StackItemType(parameter[0]) + item := v.estack.Pop().Item() + result, err := item.Convert(typ) + if err != nil { + panic(err) + } + v.estack.PushVal(result) + // Stack operations. case opcode.TOALTSTACK: v.astack.Push(v.estack.Pop()) diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 90d08df36..6bba08c69 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/binary" "encoding/hex" + "fmt" "math/big" "math/rand" "testing" @@ -256,6 +257,128 @@ func TestISTYPE(t *testing.T) { }) } +func testCONVERT(isErr bool, to StackItemType, item, res StackItem) func(t *testing.T) { + return func(t *testing.T) { + prog := []byte{byte(opcode.CONVERT), byte(to)} + v := load(prog) + v.estack.PushVal(item) + if isErr { + checkVMFailed(t, v) + return + } + runVM(t, v) + require.Equal(t, 1, v.estack.Len()) + require.Equal(t, res, v.estack.Pop().value) + } +} + +func TestCONVERT(t *testing.T) { + type convertTC struct { + item, res StackItem + } + arr := []StackItem{ + NewBigIntegerItem(big.NewInt(7)), + NewByteArrayItem([]byte{4, 8, 15}), + } + m := NewMapItem() + m.Add(NewByteArrayItem([]byte{1}), NewByteArrayItem([]byte{2})) + + getName := func(item StackItem, typ StackItemType) string { return fmt.Sprintf("%s->%s", item, typ) } + + t.Run("->Bool", func(t *testing.T) { + testBool := func(a, b StackItem) func(t *testing.T) { + return testCONVERT(false, BooleanT, a, b) + } + + trueCases := []StackItem{ + NewBoolItem(true), NewBigIntegerItem(big.NewInt(11)), NewByteArrayItem([]byte{1, 2, 3}), + NewArrayItem(arr), NewArrayItem(nil), + NewStructItem(arr), NewStructItem(nil), + NewMapItem(), m, NewInteropItem(struct{}{}), + } + for i := range trueCases { + t.Run(getName(trueCases[i], BooleanT), testBool(trueCases[i], NewBoolItem(true))) + } + + falseCases := []StackItem{ + NewBigIntegerItem(big.NewInt(0)), NewByteArrayItem([]byte{0, 0}), NewBoolItem(false), + } + for i := range falseCases { + testBool(falseCases[i], NewBoolItem(false)) + } + }) + + t.Run("compound/interop -> basic", func(t *testing.T) { + types := []StackItemType{IntegerT, ByteArrayT} + items := []StackItem{NewArrayItem(nil), NewStructItem(nil), NewMapItem(), NewInteropItem(struct{}{})} + for _, typ := range types { + for j := range items { + t.Run(getName(items[j], typ), testCONVERT(true, typ, items[j], nil)) + } + } + }) + + t.Run("primitive -> Integer/ByteArray", func(t *testing.T) { + n := big.NewInt(42) + b := emit.IntToBytes(n) + + itemInt := NewBigIntegerItem(n) + itemBytes := NewByteArrayItem(b) + + trueCases := map[StackItemType][]convertTC{ + IntegerT: { + {itemInt, itemInt}, + {itemBytes, itemInt}, + {NewBoolItem(true), NewBigIntegerItem(big.NewInt(1))}, + {NewBoolItem(false), NewBigIntegerItem(big.NewInt(0))}, + }, + ByteArrayT: { + {itemInt, itemBytes}, + {itemBytes, itemBytes}, + {NewBoolItem(true), NewByteArrayItem([]byte{1})}, + {NewBoolItem(false), NewByteArrayItem([]byte{0})}, + }, + } + + for typ := range trueCases { + for _, tc := range trueCases[typ] { + t.Run(getName(tc.item, typ), testCONVERT(false, typ, tc.item, tc.res)) + } + } + }) + + t.Run("Struct<->Array", func(t *testing.T) { + arrayItem := NewArrayItem(arr) + structItem := NewStructItem(arr) + t.Run("Array->Array", testCONVERT(false, ArrayT, arrayItem, arrayItem)) + t.Run("Array->Struct", testCONVERT(false, StructT, arrayItem, structItem)) + t.Run("Struct->Array", testCONVERT(false, ArrayT, structItem, arrayItem)) + t.Run("Struct->Struct", testCONVERT(false, StructT, structItem, structItem)) + }) + + t.Run("Map->Map", testCONVERT(false, MapT, m, m)) + + t.Run("Null->", func(t *testing.T) { + types := []StackItemType{ + BooleanT, ByteArrayT, IntegerT, ArrayT, StructT, MapT, InteropT, + } + for i := range types { + t.Run(types[i].String(), testCONVERT(false, types[i], NullItem{}, NullItem{})) + } + }) + + t.Run("->Any", func(t *testing.T) { + items := []StackItem{ + NewBigIntegerItem(big.NewInt(1)), NewByteArrayItem([]byte{1}), NewBoolItem(true), + NewArrayItem(arr), NewStructItem(arr), m, NewInteropItem(struct{}{}), + } + + for i := range items { + t.Run(items[i].String(), testCONVERT(true, AnyT, items[i], nil)) + } + }) +} + // appendBigStruct returns a program which: // 1. pushes size Structs on stack // 2. packs them into a new struct