diff --git a/pkg/smartcontract/param_context.go b/pkg/smartcontract/param_context.go index fe8a2561b..b1b8e3330 100644 --- a/pkg/smartcontract/param_context.go +++ b/pkg/smartcontract/param_context.go @@ -46,21 +46,94 @@ type rawParameter struct { Value json.RawMessage `json:"value"` } +type keyValuePair struct { + Key rawParameter `json:"key"` + Value rawParameter `json:"value"` +} + +type rawKeyValuePair struct { + Key json.RawMessage `json:"key"` + Value json.RawMessage `json:"value"` +} + +// MarshalJSON implements Marshaler interface. +func (p *Parameter) MarshalJSON() ([]byte, error) { + var ( + resultRawValue json.RawMessage + resultErr error + ) + switch p.Type { + case BoolType, IntegerType, StringType, Hash256Type, Hash160Type: + resultRawValue, resultErr = json.Marshal(p.Value) + case PublicKeyType, ByteArrayType, SignatureType: + resultRawValue, resultErr = json.Marshal(hex.EncodeToString(p.Value.([]byte))) + case ArrayType: + var value = make([]rawParameter, 0) + for _, parameter := range p.Value.([]Parameter) { + rawValue, err := json.Marshal(parameter.Value) + if err != nil { + return nil, err + } + value = append(value, rawParameter{ + Type: parameter.Type, + Value: rawValue, + }) + } + resultRawValue, resultErr = json.Marshal(value) + case MapType: + var value []keyValuePair + for key, val := range p.Value.(map[Parameter]Parameter) { + rawKey, err := json.Marshal(key.Value) + if err != nil { + return nil, err + } + rawValue, err := json.Marshal(val.Value) + if err != nil { + return nil, err + } + value = append(value, keyValuePair{ + Key: rawParameter{ + Type: key.Type, + Value: rawKey, + }, + Value: rawParameter{ + Type: val.Type, + Value: rawValue, + }, + }) + } + resultRawValue, resultErr = json.Marshal(value) + default: + resultErr = errors.Errorf("Marshaller for type %s not implemented", p.Type) + } + if resultErr != nil { + return nil, resultErr + } + return json.Marshal(rawParameter{ + Type: p.Type, + Value: resultRawValue, + }) +} + // UnmarshalJSON implements Unmarshaler interface. func (p *Parameter) UnmarshalJSON(data []byte) (err error) { var ( - r rawParameter - i int64 - s string - b []byte + r rawParameter + i int64 + s string + b []byte + boolean bool ) - if err = json.Unmarshal(data, &r); err != nil { return } - switch p.Type = r.Type; r.Type { - case ByteArrayType: + case BoolType: + if err = json.Unmarshal(r.Value, &boolean); err != nil { + return + } + p.Value = boolean + case ByteArrayType, PublicKeyType: if err = json.Unmarshal(r.Value, &s); err != nil { return } @@ -93,6 +166,23 @@ func (p *Parameter) UnmarshalJSON(data []byte) (err error) { return } p.Value = rs + case MapType: + var rawMap []rawKeyValuePair + if err = json.Unmarshal(r.Value, &rawMap); err != nil { + return + } + rs := make(map[Parameter]Parameter) + for _, p := range rawMap { + var key, value Parameter + if err = json.Unmarshal(p.Key, &key); err != nil { + return + } + if err = json.Unmarshal(p.Value, &value); err != nil { + return + } + rs[key] = value + } + p.Value = rs case Hash160Type: var h util.Uint160 if err = json.Unmarshal(r.Value, &h); err != nil { @@ -106,7 +196,7 @@ func (p *Parameter) UnmarshalJSON(data []byte) (err error) { } p.Value = h default: - return errors.New("not implemented") + return errors.Errorf("Unmarshaller for type %s not implemented", p.Type) } return } diff --git a/pkg/smartcontract/param_context_test.go b/pkg/smartcontract/param_context_test.go index 288c8558c..1a188a4be 100644 --- a/pkg/smartcontract/param_context_test.go +++ b/pkg/smartcontract/param_context_test.go @@ -2,6 +2,7 @@ package smartcontract import ( "encoding/json" + "math" "reflect" "testing" @@ -9,10 +10,129 @@ import ( "github.com/stretchr/testify/assert" ) -var testCases = []struct { +var marshalJSONTestCases = []struct { + input Parameter + result string +}{ + { + input: Parameter{Type: IntegerType, Value: int64(12345)}, + result: `{"type":"Integer","value":12345}`, + }, + { + input: Parameter{Type: StringType, Value: "Some string"}, + result: `{"type":"String","value":"Some string"}`, + }, + { + input: Parameter{Type: BoolType, Value: true}, + result: `{"type":"Boolean","value":true}`, + }, + { + input: Parameter{Type: ByteArrayType, Value: []byte{0x01, 0x02, 0x03}}, + result: `{"type":"ByteArray","value":"010203"}`, + }, + { + input: Parameter{ + Type: PublicKeyType, + Value: []byte{0x03, 0xb3, 0xbf, 0x15, 0x02, 0xfb, 0xdc, 0x05, 0x44, 0x9b, 0x50, 0x6a, 0xaf, 0x04, 0x57, 0x97, 0x24, 0x02, 0x4b, 0x06, 0x54, 0x2e, 0x49, 0x26, 0x2b, 0xfa, 0xa3, 0xf7, 0x0e, 0x20, 0x00, 0x40, 0xa9}, + }, + result: `{"type":"PublicKey","value":"03b3bf1502fbdc05449b506aaf04579724024b06542e49262bfaa3f70e200040a9"}`, + }, + { + input: Parameter{ + Type: ArrayType, + Value: []Parameter{ + {Type: StringType, Value: "str 1"}, + {Type: IntegerType, Value: int64(2)}, + }, + }, + result: `{"type":"Array","value":[{"type":"String","value":"str 1"},{"type":"Integer","value":2}]}`, + }, + { + input: Parameter{ + Type: MapType, + Value: map[Parameter]Parameter{ + {Type: StringType, Value: "key1"}: {Type: IntegerType, Value: 1}, + {Type: StringType, Value: "key2"}: {Type: StringType, Value: "two"}, + }, + }, + result: `{"type":"Map","value":[{"key":{"type":"String","value":"key1"},"value":{"type":"Integer","value":1}},{"key":{"type":"String","value":"key2"},"value":{"type":"String","value":"two"}}]}`, + }, + { + input: Parameter{ + Type: MapType, + Value: map[Parameter]Parameter{ + {Type: StringType, Value: "key1"}: {Type: ArrayType, Value: []Parameter{ + {Type: StringType, Value: "str 1"}, + {Type: IntegerType, Value: int64(2)}, + }}, + }, + }, + result: `{"type":"Map","value":[{"key":{"type":"String","value":"key1"},"value":{"type":"Array","value":[{"type":"String","value":"str 1"},{"type":"Integer","value":2}]}}]}`, + }, + { + input: 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, + }, + }, + result: `{"type":"Hash160","value":"0x0bcd2978634d961c24f5aea0802297ff128724d6"}`, + }, + { + input: 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, + }, + }, + result: `{"type":"Hash256","value":"0xf037308fa0ab18155bccfc08485468c112409ea5064595699e98c545f245f32d"}`, + }, +} + +var marshalJSONErrorCases = []Parameter{ + { + Type: UnknownType, + Value: nil, + }, + { + Type: InteropInterfaceType, + Value: nil, + }, + { + Type: IntegerType, + Value: math.Inf(1), + }, +} + +func TestParam_MarshalJSON(t *testing.T) { + for _, tc := range marshalJSONTestCases { + res, err := json.Marshal(&tc.input) + assert.NoError(t, err) + var actual, expected Parameter + assert.NoError(t, json.Unmarshal(res, &actual)) + assert.NoError(t, json.Unmarshal([]byte(tc.result), &expected)) + + assert.Equal(t, expected, actual) + } + + for _, input := range marshalJSONErrorCases { + _, err := json.Marshal(&input) + assert.Error(t, err) + } +} + +var unmarshalJSONTestCases = []struct { input string result Parameter }{ + { + input: `{"type":"Bool","value":true}`, + result: Parameter{Type: BoolType, Value: true}, + }, { input: `{"type":"Integer","value":12345}`, result: Parameter{Type: IntegerType, Value: int64(12345)}, @@ -63,9 +183,38 @@ var testCases = []struct { }, }, }, + { + input: `{"type":"Map","value":[{"key":{"type":"String","value":"key1"},"value":{"type":"Integer","value":1}},{"key":{"type":"String","value":"key2"},"value":{"type":"String","value":"two"}}]}`, + result: Parameter{ + Type: MapType, + Value: map[Parameter]Parameter{ + {Type: StringType, Value: "key1"}: {Type: IntegerType, Value: int64(1)}, + {Type: StringType, Value: "key2"}: {Type: StringType, Value: "two"}, + }, + }, + }, + { + input: `{"type":"Map","value":[{"key":{"type":"String","value":"key1"},"value":{"type":"Array","value":[{"type":"String","value":"str 1"},{"type":"Integer","value":2}]}}]}`, + result: Parameter{ + Type: MapType, + Value: map[Parameter]Parameter{ + {Type: StringType, Value: "key1"}: {Type: ArrayType, Value: []Parameter{ + {Type: StringType, Value: "str 1"}, + {Type: IntegerType, Value: int64(2)}, + }}, + }, + }, + }, + { + result: Parameter{ + Type: PublicKeyType, + Value: []byte{0x03, 0xb3, 0xbf, 0x15, 0x02, 0xfb, 0xdc, 0x05, 0x44, 0x9b, 0x50, 0x6a, 0xaf, 0x04, 0x57, 0x97, 0x24, 0x02, 0x4b, 0x06, 0x54, 0x2e, 0x49, 0x26, 0x2b, 0xfa, 0xa3, 0xf7, 0x0e, 0x20, 0x00, 0x40, 0xa9}, + }, + input: `{"type":"PublicKey","value":"03b3bf1502fbdc05449b506aaf04579724024b06542e49262bfaa3f70e200040a9"}`, + }, } -var errorCases = []string{ +var unmarshalJSONErrorCases = []string{ `{"type": "ByteArray","value":`, // incorrect JSON `{"type": "ByteArray","value":1}`, // incorrect Value `{"type": "ByteArray","value":"12zz"}`, // incorrect ByteArray value @@ -78,19 +227,23 @@ var errorCases = []string{ `{"type": "Hash256","value": "0bcd"}`, // incorrect Uint256 value `{"type": "Stringg","value": ""}`, // incorrect type `{"type": {},"value": ""}`, // incorrect value + `{"type": "Boolean","value": qwerty}`, // incorrect Bool value + `{"type": "Boolean","value": ""}`, // incorrect Bool value + `{"type": "Map","value": ["key": {}]}`, // incorrect Map value + `{"type": "Map","value": ["key": {"type":"String", "value":"qwer"}, "value": {"type":"Boolean"}]}`, // incorrect Map Value value + `{"type": "Map","value": ["key": {"type":"String"}, "value": {"type":"Boolean", "value":true}]}`, // incorrect Map Key value `{"type": "InteropInterface","value": ""}`, // ununmarshable type - `{"type": "Map","value": ""}`, //unmarshable type } func TestParam_UnmarshalJSON(t *testing.T) { var s Parameter - for _, tc := range testCases { + for _, tc := range unmarshalJSONTestCases { assert.NoError(t, json.Unmarshal([]byte(tc.input), &s)) assert.Equal(t, s, tc.result) } - for _, input := range errorCases { + for _, input := range unmarshalJSONErrorCases { assert.Error(t, json.Unmarshal([]byte(input), &s)) } }