From 648e0bb242da9daaeef4f1bbbad69367fc26c836 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 21 Feb 2020 17:34:18 +0300 Subject: [PATCH] rpc/smartcontract: merge contract parameter types Merged two types: - smartcontract.ParamType - rpc.StackParamType into single one: - smartcontract.ParamType as they duplicated the functionality. NOTE: type smartcontract.MapType was added (as in C# implementation). From now, list of supported smartcontract parameter types: UnknownType SignatureType BoolType IntegerType Hash160Type Hash256Type ByteArrayType PublicKeyType StringType ArrayType MapType InteropInterfaceType VoidType --- cli/smartcontract/smart_contract.go | 8 +- pkg/rpc/request/param.go | 5 +- pkg/rpc/request/param_test.go | 5 +- pkg/rpc/request/scdetails.go | 6 +- pkg/rpc/request/stack_param.go | 291 --------------- pkg/rpc/request/stack_param_test.go | 200 ----------- pkg/rpc/request/txBuilder.go | 14 +- pkg/rpc/request/tx_builder_test.go | 41 +-- pkg/rpc/response/types.go | 4 +- pkg/smartcontract/param_context.go | 382 +++++++++----------- pkg/smartcontract/param_context_test.go | 457 ++++++++++-------------- pkg/smartcontract/param_type.go | 269 ++++++++++++++ pkg/smartcontract/param_type_test.go | 298 +++++++++++++++ 13 files changed, 979 insertions(+), 1001 deletions(-) delete mode 100644 pkg/rpc/request/stack_param.go delete mode 100644 pkg/rpc/request/stack_param_test.go create mode 100644 pkg/smartcontract/param_type.go create mode 100644 pkg/smartcontract/param_type_test.go diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index 31e8c09cf..db59a2844 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -296,10 +296,10 @@ func initSmartContract(ctx *cli.Context) error { // TODO: Fix the missing neo-go.yml file with the `init` command when the package manager is in place. if !ctx.Bool("skip-details") { details := parseContractDetails() - details.ReturnType = request.ByteArray - details.Parameters = make([]request.StackParamType, 2) - details.Parameters[0] = request.String - details.Parameters[1] = request.Array + details.ReturnType = smartcontract.ByteArrayType + details.Parameters = make([]smartcontract.ParamType, 2) + details.Parameters[0] = smartcontract.StringType + details.Parameters[1] = smartcontract.ArrayType project := &ProjectConfig{Contract: details} b, err := yaml.Marshal(project) diff --git a/pkg/rpc/request/param.go b/pkg/rpc/request/param.go index e9f453175..77dbd38bd 100644 --- a/pkg/rpc/request/param.go +++ b/pkg/rpc/request/param.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/CityOfZion/neo-go/pkg/encoding/address" + "github.com/CityOfZion/neo-go/pkg/smartcontract" "github.com/CityOfZion/neo-go/pkg/util" "github.com/pkg/errors" ) @@ -24,8 +25,8 @@ type ( // FuncParam represents a function argument parameter used in the // invokefunction RPC method. FuncParam struct { - Type StackParamType `json:"type"` - Value Param `json:"value"` + Type smartcontract.ParamType `json:"type"` + Value Param `json:"value"` } ) diff --git a/pkg/rpc/request/param_test.go b/pkg/rpc/request/param_test.go index 127fab9ad..deaf36290 100644 --- a/pkg/rpc/request/param_test.go +++ b/pkg/rpc/request/param_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/CityOfZion/neo-go/pkg/encoding/address" + "github.com/CityOfZion/neo-go/pkg/smartcontract" "github.com/CityOfZion/neo-go/pkg/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -41,7 +42,7 @@ func TestParam_UnmarshalJSON(t *testing.T) { { Type: FuncParamT, Value: FuncParam{ - Type: String, + Type: smartcontract.StringType, Value: Param{ Type: StringT, Value: "jajaja", @@ -146,7 +147,7 @@ func TestParamGetUint160FromAddress(t *testing.T) { func TestParamGetFuncParam(t *testing.T) { fp := FuncParam{ - Type: String, + Type: smartcontract.StringType, Value: Param{ Type: StringT, Value: "jajaja", diff --git a/pkg/rpc/request/scdetails.go b/pkg/rpc/request/scdetails.go index f6f910aff..f52302683 100644 --- a/pkg/rpc/request/scdetails.go +++ b/pkg/rpc/request/scdetails.go @@ -1,5 +1,7 @@ package request +import "github.com/CityOfZion/neo-go/pkg/smartcontract" + // ContractDetails contains contract metadata. type ContractDetails struct { Author string @@ -10,6 +12,6 @@ type ContractDetails struct { HasStorage bool HasDynamicInvocation bool IsPayable bool - ReturnType StackParamType - Parameters []StackParamType + ReturnType smartcontract.ParamType + Parameters []smartcontract.ParamType } diff --git a/pkg/rpc/request/stack_param.go b/pkg/rpc/request/stack_param.go deleted file mode 100644 index 192a2f0ef..000000000 --- a/pkg/rpc/request/stack_param.go +++ /dev/null @@ -1,291 +0,0 @@ -package request - -import ( - "encoding/binary" - "encoding/hex" - "encoding/json" - "strconv" - - "github.com/CityOfZion/neo-go/pkg/util" - "github.com/pkg/errors" -) - -// StackParamType represents different types of stack values. -type StackParamType int - -// All possible StackParamType values are listed here. -const ( - Unknown StackParamType = -1 - Signature StackParamType = 0x00 - Boolean StackParamType = 0x01 - Integer StackParamType = 0x02 - Hash160 StackParamType = 0x03 - Hash256 StackParamType = 0x04 - ByteArray StackParamType = 0x05 - PublicKey StackParamType = 0x06 - String StackParamType = 0x07 - Array StackParamType = 0x10 - InteropInterface StackParamType = 0xf0 - Void StackParamType = 0xff -) - -// String implements the stringer interface. -func (t StackParamType) String() string { - switch t { - case Signature: - return "Signature" - case Boolean: - return "Boolean" - case Integer: - return "Integer" - case Hash160: - return "Hash160" - case Hash256: - return "Hash256" - case ByteArray: - return "ByteArray" - case PublicKey: - return "PublicKey" - case String: - return "String" - case Array: - return "Array" - case InteropInterface: - return "InteropInterface" - case Void: - return "Void" - default: - return "Unknown" - } -} - -// StackParamTypeFromString converts string into the StackParamType. -func StackParamTypeFromString(s string) (StackParamType, error) { - switch s { - case "Signature": - return Signature, nil - case "Boolean": - return Boolean, nil - case "Integer": - return Integer, nil - case "Hash160": - return Hash160, nil - case "Hash256": - return Hash256, nil - case "ByteArray": - return ByteArray, nil - case "PublicKey": - return PublicKey, nil - case "String": - return String, nil - case "Array": - return Array, nil - case "InteropInterface": - return InteropInterface, nil - case "Void": - return Void, nil - default: - return Unknown, errors.Errorf("unknown stack parameter type: %s", s) - } -} - -// MarshalJSON implements the json.Marshaler interface. -func (t *StackParamType) MarshalJSON() ([]byte, error) { - return []byte(`"` + t.String() + `"`), nil -} - -// UnmarshalJSON sets StackParamType from JSON-encoded data. -func (t *StackParamType) UnmarshalJSON(data []byte) (err error) { - var ( - s = string(data) - l = len(s) - ) - if l < 2 || s[0] != '"' || s[l-1] != '"' { - *t = Unknown - return errors.Errorf("invalid type: %s", s) - } - *t, err = StackParamTypeFromString(s[1 : l-1]) - return -} - -// MarshalYAML implements the YAML Marshaler interface. -func (t *StackParamType) MarshalYAML() (interface{}, error) { - return t.String(), nil -} - -// UnmarshalYAML implements the YAML Unmarshaler interface. -func (t *StackParamType) UnmarshalYAML(unmarshal func(interface{}) error) error { - var name string - - err := unmarshal(&name) - if err != nil { - return err - } - *t, err = StackParamTypeFromString(name) - return err -} - -// StackParam represent a stack parameter. -type StackParam struct { - Type StackParamType `json:"type"` - Value interface{} `json:"value"` -} - -type rawStackParam struct { - Type StackParamType `json:"type"` - Value json.RawMessage `json:"value"` -} - -// UnmarshalJSON implements Unmarshaler interface. -func (p *StackParam) UnmarshalJSON(data []byte) (err error) { - var ( - r rawStackParam - i int64 - s string - b []byte - ) - - if err = json.Unmarshal(data, &r); err != nil { - return - } - - switch p.Type = r.Type; r.Type { - case ByteArray: - if err = json.Unmarshal(r.Value, &s); err != nil { - return - } - if b, err = hex.DecodeString(s); err != nil { - return - } - p.Value = b - case String: - if err = json.Unmarshal(r.Value, &s); err != nil { - return - } - p.Value = s - case Integer: - if err = json.Unmarshal(r.Value, &i); err == nil { - p.Value = i - return - } - // sometimes integer comes as string - if err = json.Unmarshal(r.Value, &s); err != nil { - return - } - if i, err = strconv.ParseInt(s, 10, 64); err != nil { - return - } - p.Value = i - case Array: - // https://github.com/neo-project/neo/blob/3d59ecca5a8deb057bdad94b3028a6d5e25ac088/neo/Network/RPC/RpcServer.cs#L67 - var rs []StackParam - if err = json.Unmarshal(r.Value, &rs); err != nil { - return - } - p.Value = rs - case Hash160: - var h util.Uint160 - if err = json.Unmarshal(r.Value, &h); err != nil { - return - } - p.Value = h - case Hash256: - var h util.Uint256 - if err = json.Unmarshal(r.Value, &h); err != nil { - return - } - p.Value = h - default: - return errors.New("not implemented") - } - return -} - -// StackParams is an array of StackParam (TODO: drop it?). -type StackParams []StackParam - -// TryParseArray converts an array of StackParam into an array of more appropriate things. -func (p StackParams) TryParseArray(vals ...interface{}) error { - var ( - err error - i int - par StackParam - ) - if len(p) != len(vals) { - return errors.New("receiver array doesn't fit the StackParams length") - } - for i, par = range p { - if err = par.TryParse(vals[i]); err != nil { - return err - } - } - return nil -} - -// TryParse converts one StackParam into something more appropriate. -func (p StackParam) TryParse(dest interface{}) error { - var ( - err error - ok bool - data []byte - ) - switch p.Type { - case ByteArray: - if data, ok = p.Value.([]byte); !ok { - return errors.Errorf("failed to cast %s to []byte", p.Value) - } - switch dest := dest.(type) { - case *util.Uint160: - if *dest, err = util.Uint160DecodeBytesBE(data); err != nil { - return err - } - return nil - case *[]byte: - *dest = data - return nil - case *util.Uint256: - if *dest, err = util.Uint256DecodeBytesLE(data); err != nil { - return err - } - return nil - case *int64, *int32, *int16, *int8, *int, *uint64, *uint32, *uint16, *uint8, *uint: - i := bytesToUint64(data) - switch dest := dest.(type) { - case *int64: - *dest = int64(i) - case *int32: - *dest = int32(i) - case *int16: - *dest = int16(i) - case *int8: - *dest = int8(i) - case *int: - *dest = int(i) - case *uint64: - *dest = i - case *uint32: - *dest = uint32(i) - case *uint16: - *dest = uint16(i) - case *uint8: - *dest = uint8(i) - case *uint: - *dest = uint(i) - } - case *string: - *dest = string(data) - return nil - default: - return errors.Errorf("cannot cast stackparam of type %s to type %s", p.Type, dest) - } - default: - return errors.New("cannot define stackparam type") - } - return nil -} - -func bytesToUint64(b []byte) uint64 { - data := make([]byte, 8) - copy(data[8-len(b):], util.ArrayReverse(b)) - return binary.BigEndian.Uint64(data) -} diff --git a/pkg/rpc/request/stack_param_test.go b/pkg/rpc/request/stack_param_test.go deleted file mode 100644 index 2d2029cff..000000000 --- a/pkg/rpc/request/stack_param_test.go +++ /dev/null @@ -1,200 +0,0 @@ -package request - -import ( - "encoding/json" - "reflect" - "testing" - - "github.com/CityOfZion/neo-go/pkg/util" - "github.com/stretchr/testify/assert" -) - -var testCases = []struct { - input string - result StackParam -}{ - { - input: `{"type":"Integer","value":12345}`, - result: StackParam{Type: Integer, Value: int64(12345)}, - }, - { - input: `{"type":"Integer","value":"12345"}`, - result: StackParam{Type: Integer, Value: int64(12345)}, - }, - { - input: `{"type":"ByteArray","value":"010203"}`, - result: StackParam{Type: ByteArray, Value: []byte{0x01, 0x02, 0x03}}, - }, - { - input: `{"type":"String","value":"Some string"}`, - result: StackParam{Type: String, Value: "Some string"}, - }, - { - input: `{"type":"Array","value":[ -{"type": "String", "value": "str 1"}, -{"type": "Integer", "value": 2}]}`, - result: StackParam{ - Type: Array, - Value: []StackParam{ - {Type: String, Value: "str 1"}, - {Type: Integer, Value: int64(2)}, - }, - }, - }, - { - input: `{"type": "Hash160", "value": "0bcd2978634d961c24f5aea0802297ff128724d6"}`, - result: StackParam{ - Type: Hash160, - Value: util.Uint160{ - 0x0b, 0xcd, 0x29, 0x78, 0x63, 0x4d, 0x96, 0x1c, 0x24, 0xf5, - 0xae, 0xa0, 0x80, 0x22, 0x97, 0xff, 0x12, 0x87, 0x24, 0xd6, - }, - }, - }, - { - input: `{"type": "Hash256", "value": "f037308fa0ab18155bccfc08485468c112409ea5064595699e98c545f245f32d"}`, - result: StackParam{ - Type: Hash256, - Value: util.Uint256{ - 0x2d, 0xf3, 0x45, 0xf2, 0x45, 0xc5, 0x98, 0x9e, - 0x69, 0x95, 0x45, 0x06, 0xa5, 0x9e, 0x40, 0x12, - 0xc1, 0x68, 0x54, 0x48, 0x08, 0xfc, 0xcc, 0x5b, - 0x15, 0x18, 0xab, 0xa0, 0x8f, 0x30, 0x37, 0xf0, - }, - }, - }, -} - -var errorCases = []string{ - `{"type": "ByteArray","value":`, // incorrect JSON - `{"type": "ByteArray","value":1}`, // incorrect Value - `{"type": "ByteArray","value":"12zz"}`, // incorrect ByteArray value - `{"type": "String","value":`, // incorrect JSON - `{"type": "String","value":1}`, // incorrect Value - `{"type": "Integer","value": "nn"}`, // incorrect Integer value - `{"type": "Integer","value": []}`, // incorrect Integer value - `{"type": "Array","value": 123}`, // incorrect Array value - `{"type": "Hash160","value": "0bcd"}`, // incorrect Uint160 value - `{"type": "Hash256","value": "0bcd"}`, // incorrect Uint256 value - `{"type": "Stringg","value": ""}`, // incorrect type - `{"type": {},"value": ""}`, // incorrect value - - `{"type": "InteropInterface","value": ""}`, // ununmarshable type -} - -func TestStackParam_UnmarshalJSON(t *testing.T) { - var s StackParam - for _, tc := range testCases { - assert.NoError(t, json.Unmarshal([]byte(tc.input), &s)) - assert.Equal(t, s, tc.result) - } - - for _, input := range errorCases { - assert.Error(t, json.Unmarshal([]byte(input), &s)) - } -} - -var tryParseTestCases = []struct { - input interface{} - expected interface{} -}{ - { - input: []byte{ - 0x0b, 0xcd, 0x29, 0x78, 0x63, 0x4d, 0x96, 0x1c, 0x24, 0xf5, - 0xae, 0xa0, 0x80, 0x22, 0x97, 0xff, 0x12, 0x87, 0x24, 0xd6, - }, - expected: util.Uint160{ - 0x0b, 0xcd, 0x29, 0x78, 0x63, 0x4d, 0x96, 0x1c, 0x24, 0xf5, - 0xae, 0xa0, 0x80, 0x22, 0x97, 0xff, 0x12, 0x87, 0x24, 0xd6, - }, - }, - { - input: []byte{ - 0xf0, 0x37, 0x30, 0x8f, 0xa0, 0xab, 0x18, 0x15, - 0x5b, 0xcc, 0xfc, 0x08, 0x48, 0x54, 0x68, 0xc1, - 0x12, 0x40, 0x9e, 0xa5, 0x06, 0x45, 0x95, 0x69, - 0x9e, 0x98, 0xc5, 0x45, 0xf2, 0x45, 0xf3, 0x2d, - }, - expected: util.Uint256{ - 0x2d, 0xf3, 0x45, 0xf2, 0x45, 0xc5, 0x98, 0x9e, - 0x69, 0x95, 0x45, 0x06, 0xa5, 0x9e, 0x40, 0x12, - 0xc1, 0x68, 0x54, 0x48, 0x08, 0xfc, 0xcc, 0x5b, - 0x15, 0x18, 0xab, 0xa0, 0x8f, 0x30, 0x37, 0xf0, - }, - }, - { - input: []byte{0, 1, 2, 3, 4, 9, 8, 6}, - expected: []byte{0, 1, 2, 3, 4, 9, 8, 6}, - }, - { - input: []byte{0x63, 0x78, 0x29, 0xcd, 0x0b}, - expected: int64(50686687331), - }, - { - input: []byte("this is a test string"), - expected: "this is a test string", - }, -} - -func TestStackParam_TryParse(t *testing.T) { - for _, tc := range tryParseTestCases { - t.Run(reflect.TypeOf(tc.expected).String(), func(t *testing.T) { - input := StackParam{ - Type: ByteArray, - Value: tc.input, - } - - val := reflect.New(reflect.TypeOf(tc.expected)) - assert.NoError(t, input.TryParse(val.Interface())) - assert.Equal(t, tc.expected, val.Elem().Interface()) - }) - } - - t.Run("[]Uint160", func(t *testing.T) { - exp1 := util.Uint160{1, 2, 3, 4, 5} - exp2 := util.Uint160{9, 8, 7, 6, 5} - - params := StackParams{ - { - Type: ByteArray, - Value: exp1.BytesBE(), - }, - { - Type: ByteArray, - Value: exp2.BytesBE(), - }, - } - - var out1, out2 util.Uint160 - - assert.NoError(t, params.TryParseArray(&out1, &out2)) - assert.Equal(t, exp1, out1) - assert.Equal(t, exp2, out2) - }) -} - -func TestStackParamType_String(t *testing.T) { - types := []StackParamType{ - Signature, - Boolean, - Integer, - Hash160, - Hash256, - ByteArray, - PublicKey, - String, - Array, - InteropInterface, - Void, - } - - for _, exp := range types { - actual, err := StackParamTypeFromString(exp.String()) - assert.NoError(t, err) - assert.Equal(t, exp, actual) - } - - actual, err := StackParamTypeFromString(Unknown.String()) - assert.Error(t, err) - assert.Equal(t, Unknown, actual) -} diff --git a/pkg/rpc/request/txBuilder.go b/pkg/rpc/request/txBuilder.go index df14e84c2..dad08b42a 100644 --- a/pkg/rpc/request/txBuilder.go +++ b/pkg/rpc/request/txBuilder.go @@ -148,31 +148,31 @@ func expandArrayIntoScript(script *io.BinWriter, slice []Param) error { return err } switch fp.Type { - case ByteArray, Signature: + case smartcontract.ByteArrayType, smartcontract.SignatureType: str, err := fp.Value.GetBytesHex() if err != nil { return err } emit.Bytes(script, str) - case String: + case smartcontract.StringType: str, err := fp.Value.GetString() if err != nil { return err } emit.String(script, str) - case Hash160: + case smartcontract.Hash160Type: hash, err := fp.Value.GetUint160FromHex() if err != nil { return err } emit.Bytes(script, hash.BytesBE()) - case Hash256: + case smartcontract.Hash256Type: hash, err := fp.Value.GetUint256() if err != nil { return err } emit.Bytes(script, hash.BytesBE()) - case PublicKey: + case smartcontract.PublicKeyType: str, err := fp.Value.GetString() if err != nil { return err @@ -182,13 +182,13 @@ func expandArrayIntoScript(script *io.BinWriter, slice []Param) error { return err } emit.Bytes(script, key.Bytes()) - case Integer: + case smartcontract.IntegerType: val, err := fp.Value.GetInt() if err != nil { return err } emit.Int(script, int64(val)) - case Boolean: + case smartcontract.BoolType: str, err := fp.Value.GetString() if err != nil { return err diff --git a/pkg/rpc/request/tx_builder_test.go b/pkg/rpc/request/tx_builder_test.go index a4e29edd5..8b5254e17 100644 --- a/pkg/rpc/request/tx_builder_test.go +++ b/pkg/rpc/request/tx_builder_test.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "testing" + "github.com/CityOfZion/neo-go/pkg/smartcontract" "github.com/CityOfZion/neo-go/pkg/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -29,31 +30,31 @@ func TestInvocationScriptCreationGood(t *testing.T) { ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{}}}, script: "00c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: ByteArray, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}}, + ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.ByteArrayType, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}}, script: "1450befd26fdf6e4d957c11e078b24ebce6291456f51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Signature, Value: Param{Type: StringT, Value: "4edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f"}}}}}}, + ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.SignatureType, Value: Param{Type: StringT, Value: "4edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f"}}}}}}, script: "404edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: String, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}}, + ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.StringType, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}}, script: "283530626566643236666466366534643935376331316530373862323465626365363239313435366651c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Hash160, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}}, + ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.Hash160Type, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}}, script: "146f459162ceeb248b071ec157d9e4f6fd26fdbe5051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Hash256, Value: Param{Type: StringT, Value: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"}}}}}}, + ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.Hash256Type, Value: Param{Type: StringT, Value: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"}}}}}}, script: "20e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: PublicKey, Value: Param{Type: StringT, Value: "03c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c1"}}}}}}, + ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.PublicKeyType, Value: Param{Type: StringT, Value: "03c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c1"}}}}}}, script: "2103c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c151c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Integer, Value: Param{Type: NumberT, Value: 42}}}}}}, + ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.IntegerType, Value: Param{Type: NumberT, Value: 42}}}}}}, script: "012a51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Boolean, Value: Param{Type: StringT, Value: "true"}}}}}}, + ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.BoolType, Value: Param{Type: StringT, Value: "true"}}}}}}, script: "5151c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Boolean, Value: Param{Type: StringT, Value: "false"}}}}}}, + ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.BoolType, Value: Param{Type: StringT, Value: "false"}}}}}}, script: "0051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }} for _, ps := range paramScripts { @@ -70,17 +71,17 @@ func TestInvocationScriptCreationBad(t *testing.T) { {{Type: NumberT, Value: "qwerty"}}, {{Type: ArrayT, Value: 42}}, {{Type: ArrayT, Value: []Param{{Type: NumberT, Value: 42}}}}, - {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: ByteArray, Value: Param{Type: StringT, Value: "qwerty"}}}}}}, - {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Signature, Value: Param{Type: StringT, Value: "qwerty"}}}}}}, - {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: String, Value: Param{Type: NumberT, Value: 42}}}}}}, - {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Hash160, Value: Param{Type: StringT, Value: "qwerty"}}}}}}, - {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Hash256, Value: Param{Type: StringT, Value: "qwerty"}}}}}}, - {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: PublicKey, Value: Param{Type: NumberT, Value: 42}}}}}}, - {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: PublicKey, Value: Param{Type: StringT, Value: "qwerty"}}}}}}, - {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Integer, Value: Param{Type: StringT, Value: "qwerty"}}}}}}, - {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Boolean, Value: Param{Type: NumberT, Value: 42}}}}}}, - {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Boolean, Value: Param{Type: StringT, Value: "qwerty"}}}}}}, - {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Unknown, Value: Param{}}}}}}, + {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.ByteArrayType, Value: Param{Type: StringT, Value: "qwerty"}}}}}}, + {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.SignatureType, Value: Param{Type: StringT, Value: "qwerty"}}}}}}, + {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.StringType, Value: Param{Type: NumberT, Value: 42}}}}}}, + {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.Hash160Type, Value: Param{Type: StringT, Value: "qwerty"}}}}}}, + {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.Hash256Type, Value: Param{Type: StringT, Value: "qwerty"}}}}}}, + {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.PublicKeyType, Value: Param{Type: NumberT, Value: 42}}}}}}, + {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.PublicKeyType, Value: Param{Type: StringT, Value: "qwerty"}}}}}}, + {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.IntegerType, Value: Param{Type: StringT, Value: "qwerty"}}}}}}, + {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.BoolType, Value: Param{Type: NumberT, Value: 42}}}}}}, + {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.BoolType, Value: Param{Type: StringT, Value: "qwerty"}}}}}}, + {{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.UnknownType, Value: Param{}}}}}}, } for _, ps := range testParams { _, err := CreateFunctionInvocationScript(contract, ps) diff --git a/pkg/rpc/response/types.go b/pkg/rpc/response/types.go index 6b13cb018..a655d6bab 100644 --- a/pkg/rpc/response/types.go +++ b/pkg/rpc/response/types.go @@ -3,8 +3,8 @@ package response import ( "encoding/json" - "github.com/CityOfZion/neo-go/pkg/rpc/request" "github.com/CityOfZion/neo-go/pkg/rpc/response/result" + "github.com/CityOfZion/neo-go/pkg/smartcontract" "github.com/CityOfZion/neo-go/pkg/vm" ) @@ -14,7 +14,7 @@ type InvokeResult struct { State vm.State `json:"state"` GasConsumed string `json:"gas_consumed"` Script string `json:"script"` - Stack []request.StackParam + Stack []smartcontract.Parameter } // Header is a generic JSON-RPC 2.0 response header (ID and JSON-RPC version). diff --git a/pkg/smartcontract/param_context.go b/pkg/smartcontract/param_context.go index a60d13c1e..fe8a2561b 100644 --- a/pkg/smartcontract/param_context.go +++ b/pkg/smartcontract/param_context.go @@ -1,33 +1,16 @@ package smartcontract import ( + "encoding/binary" "encoding/hex" "encoding/json" - "errors" + "math/bits" "strconv" "strings" "unicode/utf8" - "github.com/CityOfZion/neo-go/pkg/crypto/keys" - "github.com/CityOfZion/neo-go/pkg/encoding/address" - "github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/util" -) - -// ParamType represents the Type of the contract parameter. -type ParamType byte - -// A list of supported smart contract parameter types. -const ( - SignatureType ParamType = iota - BoolType - IntegerType - Hash160Type - Hash256Type - ByteArrayType - PublicKeyType - StringType - ArrayType + "github.com/pkg/errors" ) // PropertyState represents contract properties (flags). @@ -49,62 +32,6 @@ type Parameter struct { Value interface{} `json:"value"` } -func (pt ParamType) String() string { - switch pt { - case SignatureType: - return "Signature" - case BoolType: - return "Boolean" - case IntegerType: - return "Integer" - case Hash160Type: - return "Hash160" - case Hash256Type: - return "Hash256" - case ByteArrayType: - return "ByteArray" - case PublicKeyType: - return "PublicKey" - case StringType: - return "String" - case ArrayType: - return "Array" - default: - return "" - } -} - -// MarshalJSON implements the json.Marshaler interface. -func (pt ParamType) MarshalJSON() ([]byte, error) { - return []byte(`"` + pt.String() + `"`), nil -} - -// UnmarshalJSON implements json.Unmarshaler interface. -func (pt *ParamType) UnmarshalJSON(data []byte) error { - var s string - if err := json.Unmarshal(data, &s); err != nil { - return err - } - - p, err := parseParamType(s) - if err != nil { - return err - } - - *pt = p - return nil -} - -// EncodeBinary implements io.Serializable interface. -func (pt ParamType) EncodeBinary(w *io.BinWriter) { - w.WriteB(byte(pt)) -} - -// DecodeBinary implements io.Serializable interface. -func (pt *ParamType) DecodeBinary(r *io.BinReader) { - *pt = ParamType(r.ReadB()) -} - // NewParameter returns a Parameter with proper initialized Value // of the given ParamType. func NewParameter(t ParamType) Parameter { @@ -114,149 +41,188 @@ func NewParameter(t ParamType) Parameter { } } -// parseParamType is a user-friendly string to ParamType converter, it's -// case-insensitive and makes the following conversions: -// signature -> SignatureType -// bool -> BoolType -// int -> IntegerType -// hash160 -> Hash160Type -// hash256 -> Hash256Type -// bytes -> ByteArrayType -// key -> PublicKeyType -// string -> StringType -// anything else generates an error. -func parseParamType(typ string) (ParamType, error) { - switch strings.ToLower(typ) { - case "signature": - return SignatureType, nil - case "bool": - return BoolType, nil - case "int": - return IntegerType, nil - case "hash160": - return Hash160Type, nil - case "hash256": - return Hash256Type, nil - case "bytes", "bytearray": - return ByteArrayType, nil - case "key": - return PublicKeyType, nil - case "string": - return StringType, nil - default: - // We deliberately don't support array here. - return 0, errors.New("wrong or unsupported parameter type") - } +type rawParameter struct { + Type ParamType `json:"type"` + Value json.RawMessage `json:"value"` } -// adjustValToType is a value type-checker and converter. -func adjustValToType(typ ParamType, val string) (interface{}, error) { - switch typ { - case SignatureType: - b, err := hex.DecodeString(val) - if err != nil { - return nil, err - } - if len(b) != 64 { - return nil, errors.New("not a signature") - } - return val, nil - case BoolType: - switch val { - case "true": - return true, nil - case "false": - return false, nil - default: - return nil, errors.New("invalid boolean value") - } - case IntegerType: - return strconv.Atoi(val) - case Hash160Type: - u, err := address.StringToUint160(val) - if err == nil { - return hex.EncodeToString(u.BytesBE()), nil - } - b, err := hex.DecodeString(val) - if err != nil { - return nil, err - } - if len(b) != 20 { - return nil, errors.New("not a hash160") - } - return val, nil - case Hash256Type: - b, err := hex.DecodeString(val) - if err != nil { - return nil, err - } - if len(b) != 32 { - return nil, errors.New("not a hash256") - } - return val, nil +// UnmarshalJSON implements Unmarshaler interface. +func (p *Parameter) UnmarshalJSON(data []byte) (err error) { + var ( + r rawParameter + i int64 + s string + b []byte + ) + + if err = json.Unmarshal(data, &r); err != nil { + return + } + + switch p.Type = r.Type; r.Type { case ByteArrayType: - _, err := hex.DecodeString(val) - if err != nil { - return nil, err + if err = json.Unmarshal(r.Value, &s); err != nil { + return } - return val, nil - case PublicKeyType: - _, err := keys.NewPublicKeyFromString(val) - if err != nil { - return nil, err + if b, err = hex.DecodeString(s); err != nil { + return } - return val, nil + p.Value = b case StringType: - return val, nil + if err = json.Unmarshal(r.Value, &s); err != nil { + return + } + p.Value = s + case IntegerType: + if err = json.Unmarshal(r.Value, &i); err == nil { + p.Value = i + return + } + // sometimes integer comes as string + if err = json.Unmarshal(r.Value, &s); err != nil { + return + } + if i, err = strconv.ParseInt(s, 10, 64); err != nil { + return + } + p.Value = i + case ArrayType: + // https://github.com/neo-project/neo/blob/3d59ecca5a8deb057bdad94b3028a6d5e25ac088/neo/Network/RPC/RpcServer.cs#L67 + var rs []Parameter + if err = json.Unmarshal(r.Value, &rs); err != nil { + return + } + p.Value = rs + case Hash160Type: + var h util.Uint160 + if err = json.Unmarshal(r.Value, &h); err != nil { + return + } + p.Value = h + case Hash256Type: + var h util.Uint256 + if err = json.Unmarshal(r.Value, &h); err != nil { + return + } + p.Value = h default: - return nil, errors.New("unsupported parameter type") + return errors.New("not implemented") } + return } -// inferParamType tries to infer the value type from its contents. It returns -// IntegerType for anything that looks like decimal integer (can be converted -// with strconv.Atoi), BoolType for true and false values, Hash160Type for -// addresses and hex strings encoding 20 bytes long values, PublicKeyType for -// valid hex-encoded public keys, Hash256Type for hex-encoded 32 bytes values, -// SignatureType for hex-encoded 64 bytes values, ByteArrayType for any other -// valid hex-encoded values and StringType for anything else. -func inferParamType(val string) ParamType { - var err error +// Params is an array of Parameter (TODO: drop it?). +type Params []Parameter - _, err = strconv.Atoi(val) - if err == nil { - return IntegerType +// TryParseArray converts an array of Parameter into an array of more appropriate things. +func (p Params) TryParseArray(vals ...interface{}) error { + var ( + err error + i int + par Parameter + ) + if len(p) != len(vals) { + return errors.New("receiver array doesn't fit the Params length") } - - if val == "true" || val == "false" { - return BoolType - } - - _, err = address.StringToUint160(val) - if err == nil { - return Hash160Type - } - - _, err = keys.NewPublicKeyFromString(val) - if err == nil { - return PublicKeyType - } - - unhexed, err := hex.DecodeString(val) - if err == nil { - switch len(unhexed) { - case 20: - return Hash160Type - case 32: - return Hash256Type - case 64: - return SignatureType - default: - return ByteArrayType + for i, par = range p { + if err = par.TryParse(vals[i]); err != nil { + return err } } - // Anything can be a string. - return StringType + return nil +} + +// TryParse converts one Parameter into something more appropriate. +func (p Parameter) TryParse(dest interface{}) error { + var ( + err error + ok bool + data []byte + ) + switch p.Type { + case ByteArrayType: + if data, ok = p.Value.([]byte); !ok { + return errors.Errorf("failed to cast %s to []byte", p.Value) + } + switch dest := dest.(type) { + case *util.Uint160: + if *dest, err = util.Uint160DecodeBytesBE(data); err != nil { + return err + } + return nil + case *[]byte: + *dest = data + return nil + case *util.Uint256: + if *dest, err = util.Uint256DecodeBytesLE(data); err != nil { + return err + } + return nil + case *int64, *int32, *int16, *int8, *int, *uint64, *uint32, *uint16, *uint8, *uint: + var size int + switch dest.(type) { + case *int64, *uint64: + size = 64 + case *int32, *uint32: + size = 32 + case *int16, *uint16: + size = 16 + case *int8, *uint8: + size = 8 + case *int, *uint: + size = bits.UintSize + } + + i, err := bytesToUint64(data, size) + if err != nil { + return err + } + + switch dest := dest.(type) { + case *int64: + *dest = int64(i) + case *int32: + *dest = int32(i) + case *int16: + *dest = int16(i) + case *int8: + *dest = int8(i) + case *int: + *dest = int(i) + case *uint64: + *dest = i + case *uint32: + *dest = uint32(i) + case *uint16: + *dest = uint16(i) + case *uint8: + *dest = uint8(i) + case *uint: + *dest = uint(i) + } + case *string: + *dest = string(data) + return nil + default: + return errors.Errorf("cannot cast param of type %s to type %s", p.Type, dest) + } + default: + return errors.New("cannot define param type") + } + return nil +} + +func bytesToUint64(b []byte, size int) (uint64, error) { + var length = size / 8 + if len(b) > length { + return 0, errors.Errorf("input doesn't fit into %d bits", size) + } + if len(b) < length { + data := make([]byte, length) + copy(data, b) + return binary.LittleEndian.Uint64(data), nil + } + return binary.LittleEndian.Uint64(b), nil } // NewParameterFromString returns a new Parameter initialized from the given @@ -282,10 +248,14 @@ func NewParameterFromString(in string) (*Parameter, error) { } if char == ':' && !escaped && !hadType { typStr := buf.String() - res.Type, err = parseParamType(typStr) + res.Type, err = ParseParamType(typStr) if err != nil { return nil, err } + // We currently do not support following types: + if res.Type == ArrayType || res.Type == MapType || res.Type == InteropInterfaceType || res.Type == VoidType { + return nil, errors.Errorf("Unsupported contract parameter type: %s", res.Type) + } buf.Reset() hadType = true continue diff --git a/pkg/smartcontract/param_context_test.go b/pkg/smartcontract/param_context_test.go index 1f95d3128..288c8558c 100644 --- a/pkg/smartcontract/param_context_test.go +++ b/pkg/smartcontract/param_context_test.go @@ -1,283 +1,204 @@ package smartcontract import ( + "encoding/json" + "reflect" "testing" + "github.com/CityOfZion/neo-go/pkg/util" "github.com/stretchr/testify/assert" ) -func TestParseParamType(t *testing.T) { - var inouts = []struct { - in string - out ParamType - err bool - }{{ - in: "signature", - out: SignatureType, - }, { - in: "Signature", - out: SignatureType, - }, { - in: "SiGnAtUrE", - out: SignatureType, - }, { - in: "bool", - out: BoolType, - }, { - in: "int", - out: IntegerType, - }, { - in: "hash160", - out: Hash160Type, - }, { - in: "hash256", - out: Hash256Type, - }, { - in: "bytes", - out: ByteArrayType, - }, { - in: "key", - out: PublicKeyType, - }, { - in: "string", - out: StringType, - }, { - in: "array", - err: true, - }, { - in: "qwerty", - err: true, - }} - for _, inout := range inouts { - out, err := parseParamType(inout.in) - if inout.err { - assert.NotNil(t, err, "should error on '%s' input", inout.in) - } else { - assert.Nil(t, err, "shouldn't error on '%s' input", inout.in) - assert.Equal(t, inout.out, out, "bad output for '%s' input", inout.in) - } +var testCases = []struct { + input string + result Parameter +}{ + { + input: `{"type":"Integer","value":12345}`, + result: Parameter{Type: IntegerType, Value: int64(12345)}, + }, + { + input: `{"type":"Integer","value":"12345"}`, + result: Parameter{Type: IntegerType, Value: int64(12345)}, + }, + { + input: `{"type":"ByteArray","value":"010203"}`, + result: Parameter{Type: ByteArrayType, Value: []byte{0x01, 0x02, 0x03}}, + }, + { + input: `{"type":"String","value":"Some string"}`, + result: Parameter{Type: StringType, Value: "Some string"}, + }, + { + input: `{"type":"Array","value":[ + {"type": "String", "value": "str 1"}, + {"type": "Integer", "value": 2}]}`, + result: Parameter{ + Type: ArrayType, + Value: []Parameter{ + {Type: StringType, Value: "str 1"}, + {Type: IntegerType, Value: int64(2)}, + }, + }, + }, + { + input: `{"type": "Hash160", "value": "0bcd2978634d961c24f5aea0802297ff128724d6"}`, + result: Parameter{ + Type: Hash160Type, + Value: util.Uint160{ + 0x0b, 0xcd, 0x29, 0x78, 0x63, 0x4d, 0x96, 0x1c, 0x24, 0xf5, + 0xae, 0xa0, 0x80, 0x22, 0x97, 0xff, 0x12, 0x87, 0x24, 0xd6, + }, + }, + }, + { + input: `{"type": "Hash256", "value": "f037308fa0ab18155bccfc08485468c112409ea5064595699e98c545f245f32d"}`, + result: Parameter{ + Type: Hash256Type, + Value: util.Uint256{ + 0x2d, 0xf3, 0x45, 0xf2, 0x45, 0xc5, 0x98, 0x9e, + 0x69, 0x95, 0x45, 0x06, 0xa5, 0x9e, 0x40, 0x12, + 0xc1, 0x68, 0x54, 0x48, 0x08, 0xfc, 0xcc, 0x5b, + 0x15, 0x18, 0xab, 0xa0, 0x8f, 0x30, 0x37, 0xf0, + }, + }, + }, +} + +var errorCases = []string{ + `{"type": "ByteArray","value":`, // incorrect JSON + `{"type": "ByteArray","value":1}`, // incorrect Value + `{"type": "ByteArray","value":"12zz"}`, // incorrect ByteArray value + `{"type": "String","value":`, // incorrect JSON + `{"type": "String","value":1}`, // incorrect Value + `{"type": "Integer","value": "nn"}`, // incorrect Integer value + `{"type": "Integer","value": []}`, // incorrect Integer value + `{"type": "Array","value": 123}`, // incorrect Array value + `{"type": "Hash160","value": "0bcd"}`, // incorrect Uint160 value + `{"type": "Hash256","value": "0bcd"}`, // incorrect Uint256 value + `{"type": "Stringg","value": ""}`, // incorrect type + `{"type": {},"value": ""}`, // incorrect value + + `{"type": "InteropInterface","value": ""}`, // ununmarshable type + `{"type": "Map","value": ""}`, //unmarshable type +} + +func TestParam_UnmarshalJSON(t *testing.T) { + var s Parameter + for _, tc := range testCases { + assert.NoError(t, json.Unmarshal([]byte(tc.input), &s)) + assert.Equal(t, s, tc.result) + } + + for _, input := range errorCases { + assert.Error(t, json.Unmarshal([]byte(input), &s)) } } -func TestInferParamType(t *testing.T) { - var inouts = []struct { - in string - out ParamType - }{{ - in: "42", - out: IntegerType, - }, { - in: "-42", - out: IntegerType, - }, { - in: "0", - out: IntegerType, - }, { - in: "2e10", - out: ByteArrayType, - }, { - in: "true", - out: BoolType, - }, { - in: "false", - out: BoolType, - }, { - in: "truee", - out: StringType, - }, { - in: "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y", - out: Hash160Type, - }, { - in: "ZK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y", - out: StringType, - }, { - in: "50befd26fdf6e4d957c11e078b24ebce6291456f", - out: Hash160Type, - }, { - in: "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", - out: PublicKeyType, - }, { - in: "30b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", - out: ByteArrayType, - }, { - in: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7", - out: Hash256Type, - }, { - in: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7da", - out: ByteArrayType, - }, { - in: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b", - out: SignatureType, - }, { - in: "qwerty", - out: StringType, - }, { - in: "ab", - out: ByteArrayType, - }, { - in: "az", - out: StringType, - }, { - in: "bad", - out: StringType, - }, { - in: "фыва", - out: StringType, - }, { - in: "dead", - out: ByteArrayType, - }} - for _, inout := range inouts { - out := inferParamType(inout.in) - assert.Equal(t, inout.out, out, "bad output for '%s' input", inout.in) - } +var tryParseTestCases = []struct { + input interface{} + expected interface{} +}{ + { + input: []byte{ + 0x0b, 0xcd, 0x29, 0x78, 0x63, 0x4d, 0x96, 0x1c, 0x24, 0xf5, + 0xae, 0xa0, 0x80, 0x22, 0x97, 0xff, 0x12, 0x87, 0x24, 0xd6, + }, + expected: util.Uint160{ + 0x0b, 0xcd, 0x29, 0x78, 0x63, 0x4d, 0x96, 0x1c, 0x24, 0xf5, + 0xae, 0xa0, 0x80, 0x22, 0x97, 0xff, 0x12, 0x87, 0x24, 0xd6, + }, + }, + { + input: []byte{ + 0xf0, 0x37, 0x30, 0x8f, 0xa0, 0xab, 0x18, 0x15, + 0x5b, 0xcc, 0xfc, 0x08, 0x48, 0x54, 0x68, 0xc1, + 0x12, 0x40, 0x9e, 0xa5, 0x06, 0x45, 0x95, 0x69, + 0x9e, 0x98, 0xc5, 0x45, 0xf2, 0x45, 0xf3, 0x2d, + }, + expected: util.Uint256{ + 0x2d, 0xf3, 0x45, 0xf2, 0x45, 0xc5, 0x98, 0x9e, + 0x69, 0x95, 0x45, 0x06, 0xa5, 0x9e, 0x40, 0x12, + 0xc1, 0x68, 0x54, 0x48, 0x08, 0xfc, 0xcc, 0x5b, + 0x15, 0x18, 0xab, 0xa0, 0x8f, 0x30, 0x37, 0xf0, + }, + }, + { + input: []byte{0, 1, 2, 3, 4, 9, 8, 6}, + expected: []byte{0, 1, 2, 3, 4, 9, 8, 6}, + }, + { + input: []byte{0x63, 0x78, 0x29, 0xcd, 0x0b}, + expected: int64(50686687331), + }, + { + input: []byte("this is a test string"), + expected: "this is a test string", + }, } -func TestAdjustValToType(t *testing.T) { - var inouts = []struct { - typ ParamType - val string - out interface{} - err bool - }{{ - typ: SignatureType, - val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b", - out: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b", - }, { - typ: SignatureType, - val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c", - err: true, - }, { - typ: SignatureType, - val: "qwerty", - err: true, - }, { - typ: BoolType, - val: "false", - out: false, - }, { - typ: BoolType, - val: "true", - out: true, - }, { - typ: BoolType, - val: "qwerty", - err: true, - }, { - typ: BoolType, - val: "42", - err: true, - }, { - typ: BoolType, - val: "0", - err: true, - }, { - typ: IntegerType, - val: "0", - out: 0, - }, { - typ: IntegerType, - val: "42", - out: 42, - }, { - typ: IntegerType, - val: "-42", - out: -42, - }, { - typ: IntegerType, - val: "q", - err: true, - }, { - typ: Hash160Type, - val: "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y", - out: "23ba2703c53263e8d6e522dc32203339dcd8eee9", - }, { - typ: Hash160Type, - val: "50befd26fdf6e4d957c11e078b24ebce6291456f", - out: "50befd26fdf6e4d957c11e078b24ebce6291456f", - }, { - typ: Hash160Type, - val: "befd26fdf6e4d957c11e078b24ebce6291456f", - err: true, - }, { - typ: Hash160Type, - val: "q", - err: true, - }, { - typ: Hash256Type, - val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7", - out: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7", - }, { - typ: Hash256Type, - val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282d", - err: true, - }, { - typ: Hash256Type, - val: "q", - err: true, - }, { - typ: ByteArrayType, - val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282d", - out: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282d", - }, { - typ: ByteArrayType, - val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7", - out: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7", - }, { - typ: ByteArrayType, - val: "50befd26fdf6e4d957c11e078b24ebce6291456f", - out: "50befd26fdf6e4d957c11e078b24ebce6291456f", - }, { - typ: ByteArrayType, - val: "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y", - err: true, - }, { - typ: ByteArrayType, - val: "q", - err: true, - }, { - typ: ByteArrayType, - val: "ab", - out: "ab", - }, { - typ: PublicKeyType, - val: "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", - out: "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", - }, { - typ: PublicKeyType, - val: "01b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", - err: true, - }, { - typ: PublicKeyType, - val: "q", - err: true, - }, { - typ: StringType, - val: "q", - out: "q", - }, { - typ: StringType, - val: "dead", - out: "dead", - }, { - typ: StringType, - val: "йцукен", - out: "йцукен", - }, { - typ: ArrayType, - val: "", - err: true, - }} +func TestParam_TryParse(t *testing.T) { + for _, tc := range tryParseTestCases { + t.Run(reflect.TypeOf(tc.expected).String(), func(t *testing.T) { + input := Parameter{ + Type: ByteArrayType, + Value: tc.input, + } - for _, inout := range inouts { - out, err := adjustValToType(inout.typ, inout.val) - if inout.err { - assert.NotNil(t, err, "should error on '%s/%s' input", inout.typ, inout.val) - } else { - assert.Nil(t, err, "shouldn't error on '%s/%s' input", inout.typ, inout.val) - assert.Equal(t, inout.out, out, "bad output for '%s/%s' input", inout.typ, inout.val) - } + val := reflect.New(reflect.TypeOf(tc.expected)) + assert.NoError(t, input.TryParse(val.Interface())) + assert.Equal(t, tc.expected, val.Elem().Interface()) + }) } + + t.Run("[]Uint160", func(t *testing.T) { + exp1 := util.Uint160{1, 2, 3, 4, 5} + exp2 := util.Uint160{9, 8, 7, 6, 5} + + params := Params{ + { + Type: ByteArrayType, + Value: exp1.BytesBE(), + }, + { + Type: ByteArrayType, + Value: exp2.BytesBE(), + }, + } + + var out1, out2 util.Uint160 + + assert.NoError(t, params.TryParseArray(&out1, &out2)) + assert.Equal(t, exp1, out1) + assert.Equal(t, exp2, out2) + }) +} + +func TestParamType_String(t *testing.T) { + types := []ParamType{ + SignatureType, + BoolType, + IntegerType, + Hash160Type, + Hash256Type, + ByteArrayType, + PublicKeyType, + StringType, + ArrayType, + InteropInterfaceType, + MapType, + VoidType, + } + + for _, exp := range types { + actual, err := ParseParamType(exp.String()) + assert.NoError(t, err) + assert.Equal(t, exp, actual) + } + + actual, err := ParseParamType(UnknownType.String()) + assert.Error(t, err) + assert.Equal(t, UnknownType, actual) } func TestNewParameterFromString(t *testing.T) { @@ -330,6 +251,12 @@ func TestNewParameterFromString(t *testing.T) { }, { in: `bool:asdf`, err: true, + }, { + in: `InteropInterface:123`, + err: true, + }, { + in: `Map:[]`, + err: true, }} for _, inout := range inouts { out, err := NewParameterFromString(inout.in) diff --git a/pkg/smartcontract/param_type.go b/pkg/smartcontract/param_type.go new file mode 100644 index 000000000..e253ba846 --- /dev/null +++ b/pkg/smartcontract/param_type.go @@ -0,0 +1,269 @@ +package smartcontract + +import ( + "encoding/hex" + "encoding/json" + "strconv" + "strings" + + "github.com/CityOfZion/neo-go/pkg/crypto/keys" + "github.com/CityOfZion/neo-go/pkg/encoding/address" + "github.com/CityOfZion/neo-go/pkg/io" + "github.com/pkg/errors" +) + +// ParamType represents the Type of the smart contract parameter. +type ParamType int + +// A list of supported smart contract parameter types. +const ( + UnknownType ParamType = -1 + SignatureType ParamType = 0x00 + BoolType ParamType = 0x01 + IntegerType ParamType = 0x02 + Hash160Type ParamType = 0x03 + Hash256Type ParamType = 0x04 + ByteArrayType ParamType = 0x05 + PublicKeyType ParamType = 0x06 + StringType ParamType = 0x07 + ArrayType ParamType = 0x10 + MapType ParamType = 0x12 + InteropInterfaceType ParamType = 0xf0 + VoidType ParamType = 0xff +) + +// String implements the stringer interface. +func (pt ParamType) String() string { + switch pt { + case SignatureType: + return "Signature" + case BoolType: + return "Boolean" + case IntegerType: + return "Integer" + case Hash160Type: + return "Hash160" + case Hash256Type: + return "Hash256" + case ByteArrayType: + return "ByteArray" + case PublicKeyType: + return "PublicKey" + case StringType: + return "String" + case ArrayType: + return "Array" + case MapType: + return "Map" + case InteropInterfaceType: + return "InteropInterface" + case VoidType: + return "Void" + default: + return "" + } +} + +// MarshalJSON implements the json.Marshaler interface. +func (pt ParamType) MarshalJSON() ([]byte, error) { + return []byte(`"` + pt.String() + `"`), nil +} + +// UnmarshalJSON implements json.Unmarshaler interface. +func (pt *ParamType) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + + p, err := ParseParamType(s) + if err != nil { + return err + } + + *pt = p + return nil +} + +// MarshalYAML implements the YAML Marshaler interface. +func (pt *ParamType) MarshalYAML() (interface{}, error) { + return pt.String(), nil +} + +// UnmarshalYAML implements the YAML Unmarshaler interface. +func (pt *ParamType) UnmarshalYAML(unmarshal func(interface{}) error) error { + var name string + + err := unmarshal(&name) + if err != nil { + return err + } + *pt, err = ParseParamType(name) + return err +} + +// EncodeBinary implements io.Serializable interface. +func (pt ParamType) EncodeBinary(w *io.BinWriter) { + w.WriteB(byte(pt)) +} + +// DecodeBinary implements io.Serializable interface. +func (pt *ParamType) DecodeBinary(r *io.BinReader) { + *pt = ParamType(r.ReadB()) +} + +// ParseParamType is a user-friendly string to ParamType converter, it's +// case-insensitive and makes the following conversions: +// signature -> SignatureType +// bool, boolean -> BoolType +// int, integer -> IntegerType +// hash160 -> Hash160Type +// hash256 -> Hash256Type +// bytes, bytearray -> ByteArrayType +// key, publickey -> PublicKeyType +// string -> StringType +// array -> ArrayType +// map -> MapType +// interopinterface -> InteropInterfaceType +// void -> VoidType +// anything else generates an error. +func ParseParamType(typ string) (ParamType, error) { + switch strings.ToLower(typ) { + case "signature": + return SignatureType, nil + case "bool", "boolean": + return BoolType, nil + case "int", "integer": + return IntegerType, nil + case "hash160": + return Hash160Type, nil + case "hash256": + return Hash256Type, nil + case "bytes", "bytearray": + return ByteArrayType, nil + case "key", "publickey": + return PublicKeyType, nil + case "string": + return StringType, nil + case "array": + return ArrayType, nil + case "map": + return MapType, nil + case "interopinterface": + return InteropInterfaceType, nil + case "void": + return VoidType, nil + default: + return UnknownType, errors.Errorf("Unknown contract parameter type: %s", typ) + } +} + +// adjustValToType is a value type-checker and converter. +func adjustValToType(typ ParamType, val string) (interface{}, error) { + switch typ { + case SignatureType: + b, err := hex.DecodeString(val) + if err != nil { + return nil, err + } + if len(b) != 64 { + return nil, errors.New("not a signature") + } + return val, nil + case BoolType: + switch val { + case "true": + return true, nil + case "false": + return false, nil + default: + return nil, errors.New("invalid boolean value") + } + case IntegerType: + return strconv.Atoi(val) + case Hash160Type: + u, err := address.StringToUint160(val) + if err == nil { + return hex.EncodeToString(u.BytesBE()), nil + } + b, err := hex.DecodeString(val) + if err != nil { + return nil, err + } + if len(b) != 20 { + return nil, errors.New("not a hash160") + } + return val, nil + case Hash256Type: + b, err := hex.DecodeString(val) + if err != nil { + return nil, err + } + if len(b) != 32 { + return nil, errors.New("not a hash256") + } + return val, nil + case ByteArrayType: + _, err := hex.DecodeString(val) + if err != nil { + return nil, err + } + return val, nil + case PublicKeyType: + _, err := keys.NewPublicKeyFromString(val) + if err != nil { + return nil, err + } + return val, nil + case StringType: + return val, nil + default: + return nil, errors.New("unsupported parameter type") + } +} + +// inferParamType tries to infer the value type from its contents. It returns +// IntegerType for anything that looks like decimal integer (can be converted +// with strconv.Atoi), BoolType for true and false values, Hash160Type for +// addresses and hex strings encoding 20 bytes long values, PublicKeyType for +// valid hex-encoded public keys, Hash256Type for hex-encoded 32 bytes values, +// SignatureType for hex-encoded 64 bytes values, ByteArrayType for any other +// valid hex-encoded values and StringType for anything else. +func inferParamType(val string) ParamType { + var err error + + _, err = strconv.Atoi(val) + if err == nil { + return IntegerType + } + + if val == "true" || val == "false" { + return BoolType + } + + _, err = address.StringToUint160(val) + if err == nil { + return Hash160Type + } + + _, err = keys.NewPublicKeyFromString(val) + if err == nil { + return PublicKeyType + } + + unhexed, err := hex.DecodeString(val) + if err == nil { + switch len(unhexed) { + case 20: + return Hash160Type + case 32: + return Hash256Type + case 64: + return SignatureType + default: + return ByteArrayType + } + } + // Anything can be a string. + return StringType +} diff --git a/pkg/smartcontract/param_type_test.go b/pkg/smartcontract/param_type_test.go new file mode 100644 index 000000000..629f1eca5 --- /dev/null +++ b/pkg/smartcontract/param_type_test.go @@ -0,0 +1,298 @@ +package smartcontract + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParseParamType(t *testing.T) { + var inouts = []struct { + in string + out ParamType + err bool + }{{ + in: "signature", + out: SignatureType, + }, { + in: "Signature", + out: SignatureType, + }, { + in: "SiGnAtUrE", + out: SignatureType, + }, { + in: "bool", + out: BoolType, + }, { + in: "int", + out: IntegerType, + }, { + in: "hash160", + out: Hash160Type, + }, { + in: "hash256", + out: Hash256Type, + }, { + in: "bytes", + out: ByteArrayType, + }, { + in: "key", + out: PublicKeyType, + }, { + in: "string", + out: StringType, + }, { + in: "array", + out: ArrayType, + }, { + in: "map", + out: MapType, + }, { + in: "interopinterface", + out: InteropInterfaceType, + }, { + in: "void", + out: VoidType, + }, { + in: "qwerty", + err: true, + }} + for _, inout := range inouts { + out, err := ParseParamType(inout.in) + if inout.err { + assert.NotNil(t, err, "should error on '%s' input", inout.in) + } else { + assert.Nil(t, err, "shouldn't error on '%s' input", inout.in) + assert.Equal(t, inout.out, out, "bad output for '%s' input", inout.in) + } + } +} + +func TestInferParamType(t *testing.T) { + var inouts = []struct { + in string + out ParamType + }{{ + in: "42", + out: IntegerType, + }, { + in: "-42", + out: IntegerType, + }, { + in: "0", + out: IntegerType, + }, { + in: "2e10", + out: ByteArrayType, + }, { + in: "true", + out: BoolType, + }, { + in: "false", + out: BoolType, + }, { + in: "truee", + out: StringType, + }, { + in: "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y", + out: Hash160Type, + }, { + in: "ZK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y", + out: StringType, + }, { + in: "50befd26fdf6e4d957c11e078b24ebce6291456f", + out: Hash160Type, + }, { + in: "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", + out: PublicKeyType, + }, { + in: "30b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", + out: ByteArrayType, + }, { + in: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7", + out: Hash256Type, + }, { + in: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7da", + out: ByteArrayType, + }, { + in: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b", + out: SignatureType, + }, { + in: "qwerty", + out: StringType, + }, { + in: "ab", + out: ByteArrayType, + }, { + in: "az", + out: StringType, + }, { + in: "bad", + out: StringType, + }, { + in: "фыва", + out: StringType, + }, { + in: "dead", + out: ByteArrayType, + }} + for _, inout := range inouts { + out := inferParamType(inout.in) + assert.Equal(t, inout.out, out, "bad output for '%s' input", inout.in) + } +} + +func TestAdjustValToType(t *testing.T) { + var inouts = []struct { + typ ParamType + val string + out interface{} + err bool + }{{ + typ: SignatureType, + val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b", + out: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b", + }, { + typ: SignatureType, + val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c", + err: true, + }, { + typ: SignatureType, + val: "qwerty", + err: true, + }, { + typ: BoolType, + val: "false", + out: false, + }, { + typ: BoolType, + val: "true", + out: true, + }, { + typ: BoolType, + val: "qwerty", + err: true, + }, { + typ: BoolType, + val: "42", + err: true, + }, { + typ: BoolType, + val: "0", + err: true, + }, { + typ: IntegerType, + val: "0", + out: 0, + }, { + typ: IntegerType, + val: "42", + out: 42, + }, { + typ: IntegerType, + val: "-42", + out: -42, + }, { + typ: IntegerType, + val: "q", + err: true, + }, { + typ: Hash160Type, + val: "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y", + out: "23ba2703c53263e8d6e522dc32203339dcd8eee9", + }, { + typ: Hash160Type, + val: "50befd26fdf6e4d957c11e078b24ebce6291456f", + out: "50befd26fdf6e4d957c11e078b24ebce6291456f", + }, { + typ: Hash160Type, + val: "befd26fdf6e4d957c11e078b24ebce6291456f", + err: true, + }, { + typ: Hash160Type, + val: "q", + err: true, + }, { + typ: Hash256Type, + val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7", + out: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7", + }, { + typ: Hash256Type, + val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282d", + err: true, + }, { + typ: Hash256Type, + val: "q", + err: true, + }, { + typ: ByteArrayType, + val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282d", + out: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282d", + }, { + typ: ByteArrayType, + val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7", + out: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7", + }, { + typ: ByteArrayType, + val: "50befd26fdf6e4d957c11e078b24ebce6291456f", + out: "50befd26fdf6e4d957c11e078b24ebce6291456f", + }, { + typ: ByteArrayType, + val: "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y", + err: true, + }, { + typ: ByteArrayType, + val: "q", + err: true, + }, { + typ: ByteArrayType, + val: "ab", + out: "ab", + }, { + typ: PublicKeyType, + val: "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", + out: "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", + }, { + typ: PublicKeyType, + val: "01b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", + err: true, + }, { + typ: PublicKeyType, + val: "q", + err: true, + }, { + typ: StringType, + val: "q", + out: "q", + }, { + typ: StringType, + val: "dead", + out: "dead", + }, { + typ: StringType, + val: "йцукен", + out: "йцукен", + }, { + typ: ArrayType, + val: "", + err: true, + }, { + typ: MapType, + val: "[]", + err: true, + }, { + typ: InteropInterfaceType, + val: "", + err: true, + }} + + for _, inout := range inouts { + out, err := adjustValToType(inout.typ, inout.val) + if inout.err { + assert.NotNil(t, err, "should error on '%s/%s' input", inout.typ, inout.val) + } else { + assert.Nil(t, err, "shouldn't error on '%s/%s' input", inout.typ, inout.val) + assert.Equal(t, inout.out, out, "bad output for '%s/%s' input", inout.typ, inout.val) + } + } +}