Merge pull request #911 from nspcc-dev/feature/convert

vm: implement CONVERT opcode
This commit is contained in:
Roman Khimov 2020-04-28 18:08:36 +03:00 committed by GitHub
commit c1aa96d614
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 472 additions and 121 deletions

View file

@ -17,6 +17,7 @@ var (
"AppCall", "AppCall",
"FromAddress", "Equals", "FromAddress", "Equals",
"panic", "panic",
"ToBool", "ToByteArray", "ToInteger",
} }
) )

View file

@ -123,7 +123,9 @@ func (c *codegen) emitLoadConst(t types.TypeAndValue) {
func (c *codegen) convertBasicType(t types.TypeAndValue, typ *types.Basic) { func (c *codegen) convertBasicType(t types.TypeAndValue, typ *types.Basic) {
switch typ.Kind() { 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) val, _ := constant.Int64Val(t.Value)
emit.Int(c.prog.BinWriter, val) emit.Int(c.prog.BinWriter, val)
case types.String, types.UntypedString: case types.String, types.UntypedString:
@ -1053,6 +1055,15 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) {
} else { } else {
c.prog.Err = errors.New("panic should have string or nil argument") 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": case "SHA256":
emit.Opcode(c.prog.BinWriter, opcode.SHA256) emit.Opcode(c.prog.BinWriter, opcode.SHA256)
case "SHA1": case "SHA1":

View file

@ -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)
})
}
}

View file

@ -5,6 +5,14 @@ import (
"testing" "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) { func TestMultipleReturn1(t *testing.T) {
src := ` src := `
package hello package hello

View file

@ -274,7 +274,7 @@ var structTestCases = []testCase{
vm.NewBigIntegerItem(big.NewInt(1)), vm.NewBigIntegerItem(big.NewInt(1)),
vm.NewBigIntegerItem(big.NewInt(2)), vm.NewBigIntegerItem(big.NewInt(2)),
vm.NewByteArrayItem([]byte("hello")), vm.NewByteArrayItem([]byte("hello")),
vm.NewByteArrayItem([]byte{}), vm.NewBoolItem(false),
}, },
}, },
{ {

View file

@ -222,9 +222,7 @@ func (p *Payload) Verify(scriptHash util.Uint160) bool {
return false return false
} }
res, err := v.Estack().Pop().TryBool() return v.Estack().Pop().Bool()
return err == nil && res
} }
// DecodeBinaryUnsigned reads payload from w excluding signature. // DecodeBinaryUnsigned reads payload from w excluding signature.

View file

@ -1656,11 +1656,7 @@ func (bc *Blockchain) verifyHashAgainstScript(hash util.Uint160, witness *transa
} }
resEl := vm.Estack().Pop() resEl := vm.Estack().Pop()
if resEl != nil { if resEl != nil {
res, err := resEl.TryBool() if !resEl.Bool() {
if err != nil {
return err
}
if !res {
return errors.Errorf("signature check failed") return errors.Errorf("signature check failed")
} }
if useKeys { if useKeys {

View file

@ -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
}

View file

@ -101,7 +101,7 @@ func (c *Context) Next() (opcode.Opcode, []byte, error) {
} }
case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.JMPEQ, opcode.JMPNE, case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.JMPEQ, opcode.JMPNE,
opcode.JMPGT, opcode.JMPGE, opcode.JMPLT, opcode.JMPLE, opcode.JMPGT, opcode.JMPGE, opcode.JMPLT, opcode.JMPLE,
opcode.CALL, opcode.ISTYPE, opcode.NEWARRAYT: opcode.CALL, opcode.ISTYPE, opcode.CONVERT, opcode.NEWARRAYT:
numtoread = 1 numtoread = 1
case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL, opcode.JMPEQL, opcode.JMPNEL, case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL, opcode.JMPEQL, opcode.JMPNEL,
opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLTL, opcode.JMPLEL, opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLTL, opcode.JMPLEL,
@ -176,6 +176,9 @@ func (c *Context) Dup() StackItem {
return c return c
} }
// Bool implements StackItem interface.
func (c *Context) Bool() bool { panic("can't convert Context to Bool") }
// TryBytes implements StackItem interface. // TryBytes implements StackItem interface.
func (c *Context) TryBytes() ([]byte, error) { func (c *Context) TryBytes() ([]byte, error) {
return nil, errors.New("can't convert Context to ByteArray") 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. // Type implements StackItem interface.
func (c *Context) Type() StackItemType { panic("Context cannot appear on evaluation stack") } 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. // Equals implements StackItem interface.
func (c *Context) Equals(s StackItem) bool { func (c *Context) Equals(s StackItem) bool {
return c == s return c == s

View file

@ -31,6 +31,7 @@ func Bool(w *io.BinWriter, ok bool) {
return return
} }
Opcode(w, opcode.PUSHF) Opcode(w, opcode.PUSHF)
Instruction(w, opcode.CONVERT, []byte{0x20}) // 0x20 for Boolean type
} }
func padRight(s int, buf []byte) []byte { func padRight(s int, buf []byte) []byte {

View file

@ -73,8 +73,6 @@ const (
SYSCALL Opcode = 0x68 SYSCALL Opcode = 0x68
TAILCALL Opcode = 0x69 TAILCALL Opcode = 0x69
ISNULL Opcode = 0x70
// Stack // Stack
DUPFROMALTSTACK Opcode = 0x6A DUPFROMALTSTACK Opcode = 0x6A
TOALTSTACK Opcode = 0x6B TOALTSTACK Opcode = 0x6B
@ -163,7 +161,9 @@ const (
CLEARITEMS Opcode = 0xD3 CLEARITEMS Opcode = 0xD3
// Types // Types
ISNULL Opcode = 0xD8
ISTYPE Opcode = 0xD9 ISTYPE Opcode = 0xD9
CONVERT Opcode = 0xDB
// Exceptions // Exceptions
THROW Opcode = 0xF0 THROW Opcode = 0xF0

View file

@ -64,7 +64,6 @@ func _() {
_ = x[APPCALL-103] _ = x[APPCALL-103]
_ = x[SYSCALL-104] _ = x[SYSCALL-104]
_ = x[TAILCALL-105] _ = x[TAILCALL-105]
_ = x[ISNULL-112]
_ = x[DUPFROMALTSTACK-106] _ = x[DUPFROMALTSTACK-106]
_ = x[TOALTSTACK-107] _ = x[TOALTSTACK-107]
_ = x[FROMALTSTACK-108] _ = x[FROMALTSTACK-108]
@ -140,12 +139,14 @@ func _() {
_ = x[REVERSEITEMS-209] _ = x[REVERSEITEMS-209]
_ = x[REMOVE-210] _ = x[REMOVE-210]
_ = x[CLEARITEMS-211] _ = x[CLEARITEMS-211]
_ = x[ISNULL-216]
_ = x[ISTYPE-217] _ = x[ISTYPE-217]
_ = x[CONVERT-219]
_ = x[THROW-240] _ = x[THROW-240]
_ = x[THROWIFNOT-241] _ = x[THROWIFNOT-241]
} }
const _Opcode_name = "PUSHINT8PUSHINT16PUSHINT32PUSHINT64PUSHINT128PUSHINT256PUSHNULLPUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16NOPJMPJMPLJMPIFJMPIFLJMPIFNOTJMPIFNOTLJMPEQJMPEQLJMPNEJMPNELJMPGTJMPGTLJMPGEJMPGELJMPLTJMPLTLJMPLEJMPLELCALLCALLLOLDPUSH1RETAPPCALLSYSCALLTAILCALLDUPFROMALTSTACKTOALTSTACKFROMALTSTACKXDROPISNULLXSWAPXTUCKDEPTHDROPDUPNIPOVERPICKROLLROTSWAPTUCKCATSUBSTRLEFTRIGHTINVERTANDORXOREQUALINCDECSIGNNEGATEABSNOTNZADDSUBMULDIVMODSHLSHRBOOLANDBOOLORNUMEQUALNUMNOTEQUALLTGTLTEGTEMINMAXWITHINSHA1SHA256HASH160HASH256CHECKSIGVERIFYCHECKMULTISIGPACKUNPACKNEWARRAY0NEWARRAYNEWARRAYTNEWSTRUCT0NEWSTRUCTNEWMAPSIZEHASKEYKEYSVALUESPICKITEMAPPENDSETITEMREVERSEITEMSREMOVECLEARITEMSISTYPETHROWTHROWIFNOT" const _Opcode_name = "PUSHINT8PUSHINT16PUSHINT32PUSHINT64PUSHINT128PUSHINT256PUSHNULLPUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16NOPJMPJMPLJMPIFJMPIFLJMPIFNOTJMPIFNOTLJMPEQJMPEQLJMPNEJMPNELJMPGTJMPGTLJMPGEJMPGELJMPLTJMPLTLJMPLEJMPLELCALLCALLLOLDPUSH1RETAPPCALLSYSCALLTAILCALLDUPFROMALTSTACKTOALTSTACKFROMALTSTACKXDROPXSWAPXTUCKDEPTHDROPDUPNIPOVERPICKROLLROTSWAPTUCKCATSUBSTRLEFTRIGHTINVERTANDORXOREQUALINCDECSIGNNEGATEABSNOTNZADDSUBMULDIVMODSHLSHRBOOLANDBOOLORNUMEQUALNUMNOTEQUALLTGTLTEGTEMINMAXWITHINSHA1SHA256HASH160HASH256CHECKSIGVERIFYCHECKMULTISIGPACKUNPACKNEWARRAY0NEWARRAYNEWARRAYTNEWSTRUCT0NEWSTRUCTNEWMAPSIZEHASKEYKEYSVALUESPICKITEMAPPENDSETITEMREVERSEITEMSREMOVECLEARITEMSISNULLISTYPECONVERTTHROWTHROWIFNOT"
var _Opcode_map = map[Opcode]string{ var _Opcode_map = map[Opcode]string{
0: _Opcode_name[0:8], 0: _Opcode_name[0:8],
@ -206,81 +207,82 @@ var _Opcode_map = map[Opcode]string{
107: _Opcode_name[349:359], 107: _Opcode_name[349:359],
108: _Opcode_name[359:371], 108: _Opcode_name[359:371],
109: _Opcode_name[371:376], 109: _Opcode_name[371:376],
112: _Opcode_name[376:382], 114: _Opcode_name[376:381],
114: _Opcode_name[382:387], 115: _Opcode_name[381:386],
115: _Opcode_name[387:392], 116: _Opcode_name[386:391],
116: _Opcode_name[392:397], 117: _Opcode_name[391:395],
117: _Opcode_name[397:401], 118: _Opcode_name[395:398],
118: _Opcode_name[401:404], 119: _Opcode_name[398:401],
119: _Opcode_name[404:407], 120: _Opcode_name[401:405],
120: _Opcode_name[407:411], 121: _Opcode_name[405:409],
121: _Opcode_name[411:415], 122: _Opcode_name[409:413],
122: _Opcode_name[415:419], 123: _Opcode_name[413:416],
123: _Opcode_name[419:422], 124: _Opcode_name[416:420],
124: _Opcode_name[422:426], 125: _Opcode_name[420:424],
125: _Opcode_name[426:430], 126: _Opcode_name[424:427],
126: _Opcode_name[430:433], 127: _Opcode_name[427:433],
127: _Opcode_name[433:439], 128: _Opcode_name[433:437],
128: _Opcode_name[439:443], 129: _Opcode_name[437:442],
129: _Opcode_name[443:448], 131: _Opcode_name[442:448],
131: _Opcode_name[448:454], 132: _Opcode_name[448:451],
132: _Opcode_name[454:457], 133: _Opcode_name[451:453],
133: _Opcode_name[457:459], 134: _Opcode_name[453:456],
134: _Opcode_name[459:462], 135: _Opcode_name[456:461],
135: _Opcode_name[462:467], 139: _Opcode_name[461:464],
139: _Opcode_name[467:470], 140: _Opcode_name[464:467],
140: _Opcode_name[470:473], 141: _Opcode_name[467:471],
141: _Opcode_name[473:477], 143: _Opcode_name[471:477],
143: _Opcode_name[477:483], 144: _Opcode_name[477:480],
144: _Opcode_name[483:486], 145: _Opcode_name[480:483],
145: _Opcode_name[486:489], 146: _Opcode_name[483:485],
146: _Opcode_name[489:491], 147: _Opcode_name[485:488],
147: _Opcode_name[491:494], 148: _Opcode_name[488:491],
148: _Opcode_name[494:497], 149: _Opcode_name[491:494],
149: _Opcode_name[497:500], 150: _Opcode_name[494:497],
150: _Opcode_name[500:503], 151: _Opcode_name[497:500],
151: _Opcode_name[503:506], 152: _Opcode_name[500:503],
152: _Opcode_name[506:509], 153: _Opcode_name[503:506],
153: _Opcode_name[509:512], 154: _Opcode_name[506:513],
154: _Opcode_name[512:519], 155: _Opcode_name[513:519],
155: _Opcode_name[519:525], 156: _Opcode_name[519:527],
156: _Opcode_name[525:533], 158: _Opcode_name[527:538],
158: _Opcode_name[533:544], 159: _Opcode_name[538:540],
159: _Opcode_name[544:546], 160: _Opcode_name[540:542],
160: _Opcode_name[546:548], 161: _Opcode_name[542:545],
161: _Opcode_name[548:551], 162: _Opcode_name[545:548],
162: _Opcode_name[551:554], 163: _Opcode_name[548:551],
163: _Opcode_name[554:557], 164: _Opcode_name[551:554],
164: _Opcode_name[557:560], 165: _Opcode_name[554:560],
165: _Opcode_name[560:566], 167: _Opcode_name[560:564],
167: _Opcode_name[566:570], 168: _Opcode_name[564:570],
168: _Opcode_name[570:576], 169: _Opcode_name[570:577],
169: _Opcode_name[576:583], 170: _Opcode_name[577:584],
170: _Opcode_name[583:590], 172: _Opcode_name[584:592],
172: _Opcode_name[590:598], 173: _Opcode_name[592:598],
173: _Opcode_name[598:604], 174: _Opcode_name[598:611],
174: _Opcode_name[604:617], 192: _Opcode_name[611:615],
192: _Opcode_name[617:621], 193: _Opcode_name[615:621],
193: _Opcode_name[621:627], 194: _Opcode_name[621:630],
194: _Opcode_name[627:636], 195: _Opcode_name[630:638],
195: _Opcode_name[636:644], 196: _Opcode_name[638:647],
196: _Opcode_name[644:653], 197: _Opcode_name[647:657],
197: _Opcode_name[653:663], 198: _Opcode_name[657:666],
198: _Opcode_name[663:672], 200: _Opcode_name[666:672],
200: _Opcode_name[672:678], 202: _Opcode_name[672:676],
202: _Opcode_name[678:682], 203: _Opcode_name[676:682],
203: _Opcode_name[682:688], 204: _Opcode_name[682:686],
204: _Opcode_name[688:692], 205: _Opcode_name[686:692],
205: _Opcode_name[692:698], 206: _Opcode_name[692:700],
206: _Opcode_name[698:706], 207: _Opcode_name[700:706],
207: _Opcode_name[706:712], 208: _Opcode_name[706:713],
208: _Opcode_name[712:719], 209: _Opcode_name[713:725],
209: _Opcode_name[719:731], 210: _Opcode_name[725:731],
210: _Opcode_name[731:737], 211: _Opcode_name[731:741],
211: _Opcode_name[737:747], 216: _Opcode_name[741:747],
217: _Opcode_name[747:753], 217: _Opcode_name[747:753],
240: _Opcode_name[753:758], 219: _Opcode_name[753:760],
241: _Opcode_name[758:768], 240: _Opcode_name[760:765],
241: _Opcode_name[765:775],
} }
func (i Opcode) String() string { func (i Opcode) String() string {

View file

@ -80,38 +80,9 @@ func (e *Element) BigInt() *big.Int {
return val return val
} }
// TryBool attempts to get the underlying value of the element as a boolean. // Bool converts an underlying value of the element to 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.
func (e *Element) Bool() bool { func (e *Element) Bool() bool {
val, err := e.TryBool() return e.value.Bool()
if err != nil {
panic(err)
}
return val
} }
// Bytes attempts to get the underlying value of the element as a byte array. // Bytes attempts to get the underlying value of the element as a byte array.

View file

@ -20,6 +20,8 @@ type StackItem interface {
Value() interface{} Value() interface{}
// Dup duplicates current StackItem. // Dup duplicates current StackItem.
Dup() StackItem Dup() StackItem
// Bool converts StackItem to a boolean value.
Bool() bool
// TryBytes converts StackItem to a byte slice. // TryBytes converts StackItem to a byte slice.
TryBytes() ([]byte, error) TryBytes() ([]byte, error)
// TryInteger converts StackItem to an integer. // TryInteger converts StackItem to an integer.
@ -30,8 +32,12 @@ type StackItem interface {
ToContractParameter(map[StackItem]bool) smartcontract.Parameter ToContractParameter(map[StackItem]bool) smartcontract.Parameter
// Type returns stack item type. // Type returns stack item type.
Type() StackItemType Type() StackItemType
// Convert converts StackItem to another type.
Convert(StackItemType) (StackItem, error)
} }
var errInvalidConversion = errors.New("invalid conversion type")
func makeStackItem(v interface{}) StackItem { func makeStackItem(v interface{}) StackItem {
switch val := v.(type) { switch val := v.(type) {
case int: 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. // StructItem represents a struct on the stack.
type StructItem struct { type StructItem struct {
value []StackItem value []StackItem
@ -133,6 +166,9 @@ func (i *StructItem) Dup() StackItem {
return i return i
} }
// Bool implements StackItem interface.
func (i *StructItem) Bool() bool { return true }
// TryBytes implements StackItem interface. // TryBytes implements StackItem interface.
func (i *StructItem) TryBytes() ([]byte, error) { func (i *StructItem) TryBytes() ([]byte, error) {
return nil, errors.New("can't convert Struct to ByteArray") 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. // Type implements StackItem interface.
func (i *StructItem) Type() StackItemType { return StructT } 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. // Clone returns a Struct with all Struct fields copied by value.
// Array fields are still copied by reference. // Array fields are still copied by reference.
func (i *StructItem) Clone() *StructItem { func (i *StructItem) Clone() *StructItem {
@ -217,6 +267,9 @@ func (i NullItem) Dup() StackItem {
return i return i
} }
// Bool implements StackItem interface.
func (i NullItem) Bool() bool { return false }
// TryBytes implements StackItem interface. // TryBytes implements StackItem interface.
func (i NullItem) TryBytes() ([]byte, error) { func (i NullItem) TryBytes() ([]byte, error) {
return nil, errors.New("can't convert Null to ByteArray") 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. // Type implements StackItem interface.
func (i NullItem) Type() StackItemType { return AnyT } 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. // BigIntegerItem represents a big integer on the stack.
type BigIntegerItem struct { type BigIntegerItem struct {
value *big.Int value *big.Int
@ -260,6 +321,11 @@ func (i *BigIntegerItem) Bytes() []byte {
return emit.IntToBytes(i.value) return emit.IntToBytes(i.value)
} }
// Bool implements StackItem interface.
func (i *BigIntegerItem) Bool() bool {
return i.value.Sign() != 0
}
// TryBytes implements StackItem interface. // TryBytes implements StackItem interface.
func (i *BigIntegerItem) TryBytes() ([]byte, error) { func (i *BigIntegerItem) TryBytes() ([]byte, error) {
return i.Bytes(), nil return i.Bytes(), nil
@ -311,6 +377,11 @@ func (i *BigIntegerItem) ToContractParameter(map[StackItem]bool) smartcontract.P
// Type implements StackItem interface. // Type implements StackItem interface.
func (i *BigIntegerItem) Type() StackItemType { return IntegerT } 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. // MarshalJSON implements the json.Marshaler interface.
func (i *BigIntegerItem) MarshalJSON() ([]byte, error) { func (i *BigIntegerItem) MarshalJSON() ([]byte, error) {
return json.Marshal(i.value) return json.Marshal(i.value)
@ -347,6 +418,9 @@ func (i *BoolItem) Dup() StackItem {
return &BoolItem{i.value} return &BoolItem{i.value}
} }
// Bool implements StackItem interface.
func (i *BoolItem) Bool() bool { return i.value }
// Bytes converts BoolItem to bytes. // Bytes converts BoolItem to bytes.
func (i *BoolItem) Bytes() []byte { func (i *BoolItem) Bytes() []byte {
if i.value { if i.value {
@ -394,6 +468,11 @@ func (i *BoolItem) ToContractParameter(map[StackItem]bool) smartcontract.Paramet
// Type implements StackItem interface. // Type implements StackItem interface.
func (i *BoolItem) Type() StackItemType { return BooleanT } 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. // ByteArrayItem represents a byte array on the stack.
type ByteArrayItem struct { type ByteArrayItem struct {
value []byte value []byte
@ -420,6 +499,19 @@ func (i *ByteArrayItem) String() string {
return "ByteArray" 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. // TryBytes implements StackItem interface.
func (i *ByteArrayItem) TryBytes() ([]byte, error) { func (i *ByteArrayItem) TryBytes() ([]byte, error) {
return i.value, nil return i.value, nil
@ -459,6 +551,11 @@ func (i *ByteArrayItem) ToContractParameter(map[StackItem]bool) smartcontract.Pa
// Type implements StackItem interface. // Type implements StackItem interface.
func (i *ByteArrayItem) Type() StackItemType { return ByteArrayT } 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. // ArrayItem represents a new ArrayItem object.
type ArrayItem struct { type ArrayItem struct {
value []StackItem value []StackItem
@ -485,6 +582,9 @@ func (i *ArrayItem) String() string {
return "Array" return "Array"
} }
// Bool implements StackItem interface.
func (i *ArrayItem) Bool() bool { return true }
// TryBytes implements StackItem interface. // TryBytes implements StackItem interface.
func (i *ArrayItem) TryBytes() ([]byte, error) { func (i *ArrayItem) TryBytes() ([]byte, error) {
return nil, errors.New("can't convert Array to ByteArray") 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. // Type implements StackItem interface.
func (i *ArrayItem) Type() StackItemType { return ArrayT } 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. // MapElement is a key-value pair of StackItems.
type MapElement struct { type MapElement struct {
Key StackItem Key StackItem
@ -553,6 +667,9 @@ func (i *MapItem) Value() interface{} {
return i.value return i.value
} }
// Bool implements StackItem interface.
func (i *MapItem) Bool() bool { return true }
// TryBytes implements StackItem interface. // TryBytes implements StackItem interface.
func (i *MapItem) TryBytes() ([]byte, error) { func (i *MapItem) TryBytes() ([]byte, error) {
return nil, errors.New("can't convert Map to ByteArray") 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. // Type implements StackItem interface.
func (i *MapItem) Type() StackItemType { return MapT } 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. // Add adds key-value pair to the map.
func (i *MapItem) Add(key, value StackItem) { func (i *MapItem) Add(key, value StackItem) {
if !isValidMapKey(key) { if !isValidMapKey(key) {
@ -672,6 +801,9 @@ func (i *InteropItem) Dup() StackItem {
return i return i
} }
// Bool implements StackItem interface.
func (i *InteropItem) Bool() bool { return true }
// TryBytes implements StackItem interface. // TryBytes implements StackItem interface.
func (i *InteropItem) TryBytes() ([]byte, error) { func (i *InteropItem) TryBytes() ([]byte, error) {
return nil, errors.New("can't convert Interop to ByteArray") 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. // Type implements StackItem interface.
func (i *InteropItem) Type() StackItemType { return InteropT } 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. // MarshalJSON implements the json.Marshaler interface.
func (i *InteropItem) MarshalJSON() ([]byte, error) { func (i *InteropItem) MarshalJSON() ([]byte, error) {
return json.Marshal(i.value) return json.Marshal(i.value)

View file

@ -569,6 +569,15 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
res := v.estack.Pop().Item() res := v.estack.Pop().Item()
v.estack.PushVal(res.Type() == StackItemType(parameter[0])) 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. // Stack operations.
case opcode.TOALTSTACK: case opcode.TOALTSTACK:
v.astack.Push(v.estack.Pop()) v.astack.Push(v.estack.Pop())

View file

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"encoding/hex" "encoding/hex"
"fmt"
"math/big" "math/big"
"math/rand" "math/rand"
"testing" "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: // appendBigStruct returns a program which:
// 1. pushes size Structs on stack // 1. pushes size Structs on stack
// 2. packs them into a new struct // 2. packs them into a new struct