From 6daf8e4c11c5ed876ddf3b55f535c2b4548cde1b Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Wed, 15 Nov 2023 18:06:37 +0100 Subject: [PATCH 1/4] Add Uint64 and Int64 types --- types/types.go | 41 ++++++++++++++++++++++++ types/types_test.go | 78 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 types/types_test.go diff --git a/types/types.go b/types/types.go index a8a8548c9..9b05f39e3 100644 --- a/types/types.go +++ b/types/types.go @@ -2,9 +2,50 @@ package types import ( "encoding/json" + "fmt" "strconv" ) +// Uint64 is a wrapper for uint64, but it is marshalled to and from JSON as a string +type Uint64 uint64 + +func (u Uint64) MarshalJSON() ([]byte, error) { + return json.Marshal(strconv.FormatUint(uint64(u), 10)) +} + +func (u *Uint64) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return fmt.Errorf("cannot unmarshal %s into Uint64, expected string-encoded integer", data) + } + v, err := strconv.ParseUint(s, 10, 64) + if err != nil { + return fmt.Errorf("cannot unmarshal %s into Uint64, expected string-encoded integer", data) + } + *u = Uint64(v) + return nil +} + +// Int64 is a wrapper for int64, but it is marshalled to and from JSON as a string +type Int64 int64 + +func (i Int64) MarshalJSON() ([]byte, error) { + return json.Marshal(strconv.FormatInt(int64(i), 10)) +} + +func (i *Int64) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return fmt.Errorf("cannot unmarshal %s into Int64, expected string-encoded integer", data) + } + v, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return fmt.Errorf("cannot unmarshal %s into Int64, expected string-encoded integer", data) + } + *i = Int64(v) + return nil +} + // HumanAddress is a printable (typically bech32 encoded) address string. Just use it as a label for developers. type HumanAddress = string diff --git a/types/types_test.go b/types/types_test.go new file mode 100644 index 000000000..9ee1d0f0d --- /dev/null +++ b/types/types_test.go @@ -0,0 +1,78 @@ +package types + +import ( + "encoding/json" + "math" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestUint64JSON(t *testing.T) { + var u Uint64 + + // test unmarshal + err := json.Unmarshal([]byte(`"123"`), &u) + require.NoError(t, err) + require.Equal(t, uint64(123), uint64(u)) + // test marshal + bz, err := json.Marshal(u) + require.NoError(t, err) + require.Equal(t, `"123"`, string(bz)) + + // test max value unmarshal + err = json.Unmarshal([]byte(`"18446744073709551615"`), &u) + require.NoError(t, err) + require.Equal(t, uint64(math.MaxUint64), uint64(u)) + // test max value marshal + bz, err = json.Marshal(Uint64(uint64(math.MaxUint64))) + require.NoError(t, err) + require.Equal(t, `"18446744073709551615"`, string(bz)) + + // test max value + 1 + err = json.Unmarshal([]byte(`"18446744073709551616"`), &u) + require.Error(t, err) + + // test unquoted unmarshal + err = json.Unmarshal([]byte(`123`), &u) + require.EqualError(t, err, "cannot unmarshal 123 into Uint64, expected string-encoded integer") +} + +func TestInt64JSON(t *testing.T) { + var i Int64 + + // test unmarshal + err := json.Unmarshal([]byte(`"-123"`), &i) + require.NoError(t, err) + require.Equal(t, int64(-123), int64(i)) + // test marshal + bz, err := json.Marshal(i) + require.NoError(t, err) + require.Equal(t, `"-123"`, string(bz)) + + // test max value unmarshal + err = json.Unmarshal([]byte(`"9223372036854775807"`), &i) + require.NoError(t, err) + require.Equal(t, int64(math.MaxInt64), int64(i)) + // test max value marshal + bz, err = json.Marshal(Int64(int64(math.MaxInt64))) + require.NoError(t, err) + require.Equal(t, `"9223372036854775807"`, string(bz)) + + // test max value + 1 + err = json.Unmarshal([]byte(`"9223372036854775808"`), &i) + require.Error(t, err) + + // test min value unmarshal + err = json.Unmarshal([]byte(`"-9223372036854775808"`), &i) + require.NoError(t, err) + require.Equal(t, int64(math.MinInt64), int64(i)) + // test min value marshal + bz, err = json.Marshal(Int64(int64(math.MinInt64))) + require.NoError(t, err) + require.Equal(t, `"-9223372036854775808"`, string(bz)) + + // test unquoted unmarshal + err = json.Unmarshal([]byte(`-123`), &i) + require.EqualError(t, err, "cannot unmarshal -123 into Int64, expected string-encoded integer") +} From 3f0224cb7833cf677d7edb2df7e29a2741c773a9 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Wed, 15 Nov 2023 18:30:51 +0100 Subject: [PATCH 2/4] Use Uint64 for BlockInfo Time --- spec/Specification.md | 4 ++-- types/env.go | 4 ++-- types/env_test.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/Specification.md b/spec/Specification.md index ab858e9b1..b2edd2a32 100644 --- a/spec/Specification.md +++ b/spec/Specification.md @@ -81,8 +81,8 @@ type Params struct { type BlockInfo struct { // block height this transaction is executed Height uint64 `json:"height"` - // time in nanoseconds since unix epoch. Uses string to ensure JavaScript compatibility. - Time uint64 `json:"time,string"` + // time in nanoseconds since unix epoch. Uses Uint64 to ensure JavaScript compatibility. + Time Uint64 `json:"time"` ChainID string `json:"chain_id"` } diff --git a/types/env.go b/types/env.go index 9dc882b06..8ad0bbbe1 100644 --- a/types/env.go +++ b/types/env.go @@ -16,8 +16,8 @@ type Env struct { type BlockInfo struct { // block height this transaction is executed Height uint64 `json:"height"` - // time in nanoseconds since unix epoch. Uses string to ensure JavaScript compatibility. - Time uint64 `json:"time,string"` + // time in nanoseconds since unix epoch. Uses Uint64 to ensure JavaScript compatibility. + Time Uint64 `json:"time"` ChainID string `json:"chain_id"` } diff --git a/types/env_test.go b/types/env_test.go index c460e345e..e0258d9a4 100644 --- a/types/env_test.go +++ b/types/env_test.go @@ -92,5 +92,5 @@ func TestBlockInfoDeserialization(t *testing.T) { // Empty string is not a valid uint64 string err = json.Unmarshal([]byte(`{"height":0,"time":"","chain_id":""}`), &block) - require.ErrorContains(t, err, "invalid use of ,string struct tag, trying to unmarshal \"\" into uint64") + require.ErrorContains(t, err, "cannot unmarshal \"\" into Uint64, expected string-encoded integer") } From 56a5bd680d9a96fafe2df900ee602de1fbed11ac Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Mon, 20 Nov 2023 14:51:26 +0100 Subject: [PATCH 3/4] Use different error messages --- types/env_test.go | 2 +- types/types.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/types/env_test.go b/types/env_test.go index e0258d9a4..2cc092cab 100644 --- a/types/env_test.go +++ b/types/env_test.go @@ -92,5 +92,5 @@ func TestBlockInfoDeserialization(t *testing.T) { // Empty string is not a valid uint64 string err = json.Unmarshal([]byte(`{"height":0,"time":"","chain_id":""}`), &block) - require.ErrorContains(t, err, "cannot unmarshal \"\" into Uint64, expected string-encoded integer") + require.ErrorContains(t, err, "cannot unmarshal \"\" into Uint64, failed to parse integer") } diff --git a/types/types.go b/types/types.go index 9b05f39e3..bd00713cf 100644 --- a/types/types.go +++ b/types/types.go @@ -20,7 +20,7 @@ func (u *Uint64) UnmarshalJSON(data []byte) error { } v, err := strconv.ParseUint(s, 10, 64) if err != nil { - return fmt.Errorf("cannot unmarshal %s into Uint64, expected string-encoded integer", data) + return fmt.Errorf("cannot unmarshal %s into Uint64, failed to parse integer", data) } *u = Uint64(v) return nil @@ -40,7 +40,7 @@ func (i *Int64) UnmarshalJSON(data []byte) error { } v, err := strconv.ParseInt(s, 10, 64) if err != nil { - return fmt.Errorf("cannot unmarshal %s into Int64, expected string-encoded integer", data) + return fmt.Errorf("cannot unmarshal %s into Int64, failed to parse integer", data) } *i = Int64(v) return nil From 4cd270ec819efe4afb2d1aecaea57bb302b530d3 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Mon, 20 Nov 2023 14:54:13 +0100 Subject: [PATCH 4/4] Test empty string for Uint64 and Int64 --- types/types_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/types/types_test.go b/types/types_test.go index 9ee1d0f0d..af1d472e5 100644 --- a/types/types_test.go +++ b/types/types_test.go @@ -36,6 +36,10 @@ func TestUint64JSON(t *testing.T) { // test unquoted unmarshal err = json.Unmarshal([]byte(`123`), &u) require.EqualError(t, err, "cannot unmarshal 123 into Uint64, expected string-encoded integer") + + // test empty string + err = json.Unmarshal([]byte(`""`), &u) + require.EqualError(t, err, "cannot unmarshal \"\" into Uint64, failed to parse integer") } func TestInt64JSON(t *testing.T) { @@ -75,4 +79,8 @@ func TestInt64JSON(t *testing.T) { // test unquoted unmarshal err = json.Unmarshal([]byte(`-123`), &i) require.EqualError(t, err, "cannot unmarshal -123 into Int64, expected string-encoded integer") + + // test empty string + err = json.Unmarshal([]byte(`""`), &i) + require.EqualError(t, err, "cannot unmarshal \"\" into Int64, failed to parse integer") }