diff --git a/pkg/abi/abi.go b/pkg/abi/abi.go index 1b384c90..8e4dc6a2 100644 --- a/pkg/abi/abi.go +++ b/pkg/abi/abi.go @@ -394,7 +394,9 @@ func (pa ParameterArray) ParseJSON(data []byte) (*ComponentValue, error) { func (pa ParameterArray) ParseJSONCtx(ctx context.Context, data []byte) (*ComponentValue, error) { var jsonTree interface{} - err := json.Unmarshal(data, &jsonTree) + decoder := json.NewDecoder(bytes.NewReader(data)) + decoder.UseNumber() + err := decoder.Decode(&jsonTree) if err != nil { return nil, err } diff --git a/pkg/abi/abi_test.go b/pkg/abi/abi_test.go index e9371f4e..9cb5bb80 100644 --- a/pkg/abi/abi_test.go +++ b/pkg/abi/abi_test.go @@ -685,7 +685,7 @@ func TestParseJSONArrayLotsOfTypes(t *testing.T) { func TestParseJSONBadData(t *testing.T) { inputs := testABI(t, sampleABI1)[0].Inputs _, err := inputs.ParseJSON([]byte(`{`)) - assert.Regexp(t, "unexpected end", err) + assert.Regexp(t, "unexpected EOF", err) } diff --git a/pkg/abi/outputserialization.go b/pkg/abi/outputserialization.go index 5ca8adca..eb85b781 100644 --- a/pkg/abi/outputserialization.go +++ b/pkg/abi/outputserialization.go @@ -18,6 +18,7 @@ package abi import ( "context" + "encoding/base64" "encoding/hex" "encoding/json" "fmt" @@ -26,16 +27,19 @@ import ( "github.com/hyperledger/firefly-common/pkg/i18n" "github.com/hyperledger/firefly-signer/internal/signermsgs" + "github.com/hyperledger/firefly-signer/pkg/ethtypes" ) // Serializer contains a set of options for how to serialize an parsed // ABI value tree, into JSON. type Serializer struct { - ts FormattingMode - is IntSerializer - fs FloatSerializer - bs ByteSerializer - dn DefaultNameGenerator + ts FormattingMode + is IntSerializer + fs FloatSerializer + bs ByteSerializer + dn DefaultNameGenerator + ad AddressSerializer + pretty bool } // NewSerializer creates a new ABI value tree serializer, with the default @@ -50,6 +54,7 @@ func NewSerializer() *Serializer { fs: Base10StringFloatSerializer, bs: HexByteSerializer, dn: NumericDefaultNameGenerator, + ad: nil, // we fall back to bytes serializer to preserve compatibility } } @@ -80,6 +85,8 @@ type FloatSerializer func(f *big.Float) interface{} type ByteSerializer func(b []byte) interface{} +type AddressSerializer func(addr [20]byte) interface{} + func (s *Serializer) SetFormattingMode(ts FormattingMode) *Serializer { s.ts = ts return s @@ -100,17 +107,36 @@ func (s *Serializer) SetByteSerializer(bs ByteSerializer) *Serializer { return s } +func (s *Serializer) SetAddressSerializer(ad AddressSerializer) *Serializer { + s.ad = ad + return s +} + func (s *Serializer) SetDefaultNameGenerator(dn DefaultNameGenerator) *Serializer { s.dn = dn return s } +func (s *Serializer) SetPretty(pretty bool) *Serializer { + s.pretty = pretty + return s +} + func Base10StringIntSerializer(i *big.Int) interface{} { return i.String() } func HexIntSerializer0xPrefix(i *big.Int) interface{} { - return "0x" + i.Text(16) + absHi := new(big.Int).Abs(i) + sign := "" + if i.Sign() < 0 { + sign = "-" + } + return fmt.Sprintf("%s0x%s", sign, absHi.Text(16)) +} + +func JSONNumberIntSerializer(i *big.Int) interface{} { + return json.Number(i.String()) } func Base10StringFloatSerializer(f *big.Float) interface{} { @@ -142,6 +168,22 @@ func HexByteSerializer0xPrefix(b []byte) interface{} { return "0x" + hex.EncodeToString(b) } +func HexAddrSerializer0xPrefix(addr [20]byte) interface{} { + return ethtypes.Address0xHex(addr).String() +} + +func HexAddrSerializerPlain(addr [20]byte) interface{} { + return ethtypes.AddressPlainHex(addr).String() +} + +func ChecksumAddrSerializer(addr [20]byte) interface{} { + return ethtypes.AddressWithChecksum(addr).String() +} + +func Base64ByteSerializer(b []byte) interface{} { + return base64.StdEncoding.EncodeToString(b) +} + func NumericDefaultNameGenerator(idx int) string { return strconv.FormatInt(int64(idx), 10) } @@ -163,6 +205,9 @@ func (s *Serializer) SerializeJSONCtx(ctx context.Context, cv *ComponentValue) ( if err != nil { return nil, err } + if s.pretty { + return json.MarshalIndent(&v, "", " ") + } return json.Marshal(&v) } @@ -187,9 +232,12 @@ func (s *Serializer) serializeElementaryType(ctx context.Context, breadcrumbs st case ElementaryTypeInt, ElementaryTypeUint: return s.is(cv.Value.(*big.Int)), nil case ElementaryTypeAddress: - addr := make([]byte, 20) - cv.Value.(*big.Int).FillBytes(addr) - return s.bs(addr), nil + var addr [20]byte + cv.Value.(*big.Int).FillBytes(addr[:]) + if s.ad == nil { + return s.bs(addr[:]), nil + } + return s.ad(addr), nil case ElementaryTypeBool: return (cv.Value.(*big.Int).Int64() == 1), nil case ElementaryTypeFixed, ElementaryTypeUfixed: diff --git a/pkg/abi/outputserialization_test.go b/pkg/abi/outputserialization_test.go index c609c4b3..4d00c1fa 100644 --- a/pkg/abi/outputserialization_test.go +++ b/pkg/abi/outputserialization_test.go @@ -18,7 +18,6 @@ package abi import ( "context" - "encoding/base64" "math/big" "strconv" "testing" @@ -60,6 +59,7 @@ func TestJSONSerializationFormatsTuple(t *testing.T) { SetFormattingMode(FormatAsFlatArrays). SetIntSerializer(HexIntSerializer0xPrefix). SetByteSerializer(HexByteSerializer0xPrefix). + SetPretty(true). SerializeJSON(v) assert.NoError(t, err) assert.JSONEq(t, `[ @@ -72,15 +72,14 @@ func TestJSONSerializationFormatsTuple(t *testing.T) { "0xfeedbeef" ] ]`, string(j2)) + assert.Contains(t, string(j2), "\n") j3, err := NewSerializer(). SetFormattingMode(FormatAsSelfDescribingArrays). SetIntSerializer(func(i *big.Int) interface{} { return "0o" + i.Text(8) }). - SetByteSerializer(func(b []byte) interface{} { - return base64.StdEncoding.EncodeToString(b) - }). + SetByteSerializer(Base64ByteSerializer). SerializeJSON(v) assert.NoError(t, err) assert.JSONEq(t, `[ @@ -111,6 +110,87 @@ func TestJSONSerializationFormatsTuple(t *testing.T) { ]`, string(j3)) } +func TestJSONSerializationNumbers(t *testing.T) { + + v, err := (ParameterArray{{Type: "uint"}, {Type: "int"}}).ParseJSON([]byte(`[ + "123000000000000000000000112233", + "-0x18d6f3720c92d9d437801b669" + ]`)) + assert.NoError(t, err) + + j, err := v.JSON() + assert.NoError(t, err) + assert.JSONEq(t, `{ + "0": "123000000000000000000000112233", + "1": "-123000000000000000000000112233" + }`, string(j)) + + j, err = NewSerializer().SetIntSerializer(HexIntSerializer0xPrefix).SerializeJSON(v) + assert.NoError(t, err) + assert.JSONEq(t, `{ + "0": "0x18d6f3720c92d9d437801b669", + "1": "-0x18d6f3720c92d9d437801b669" + }`, string(j)) + + j, err = NewSerializer().SetIntSerializer(JSONNumberIntSerializer).SerializeJSON(v) + assert.NoError(t, err) + assert.JSONEq(t, `{ + "0": 123000000000000000000000112233, + "1": -123000000000000000000000112233 + }`, string(j)) + +} + +func TestJSONSerializationAddresses(t *testing.T) { + + v, err := (ParameterArray{{Type: "address"}}).ParseJSON([]byte(`[ + "0xC66A547171fdE5FbEfC546B58E66AaC300Cb6150" + ]`)) + assert.NoError(t, err) + + j, err := v.JSON() + assert.NoError(t, err) + assert.JSONEq(t, `{ + "0": "c66a547171fde5fbefc546b58e66aac300cb6150" + }`, string(j)) + + j, err = NewSerializer(). + SetByteSerializer(Base64ByteSerializer). // confirming no effect + SerializeJSON(v) + assert.NoError(t, err) + assert.JSONEq(t, `{ + "0": "xmpUcXH95fvvxUa1jmaqwwDLYVA=" + }`, string(j)) + + j, err = NewSerializer(). + SetByteSerializer(Base64ByteSerializer). // confirming no effect + SetAddressSerializer(HexAddrSerializer0xPrefix). + SerializeJSON(v) + assert.NoError(t, err) + assert.JSONEq(t, `{ + "0": "0xc66a547171fde5fbefc546b58e66aac300cb6150" + }`, string(j)) + + j, err = NewSerializer(). + SetByteSerializer(Base64ByteSerializer). // confirming no effect + SetAddressSerializer(HexAddrSerializerPlain). + SerializeJSON(v) + assert.NoError(t, err) + assert.JSONEq(t, `{ + "0": "c66a547171fde5fbefc546b58e66aac300cb6150" + }`, string(j)) + + j, err = NewSerializer(). + SetByteSerializer(Base64ByteSerializer). // confirming no effect + SetAddressSerializer(ChecksumAddrSerializer). + SerializeJSON(v) + assert.NoError(t, err) + assert.JSONEq(t, `{ + "0": "0xC66A547171fdE5FbEfC546B58E66AaC300Cb6150" + }`, string(j)) + +} + func TestJSONSerializationForTypes(t *testing.T) { abi := testABI(t, sampleABI2)