From 1512cf486c2906a96220aa78af05905635002ba1 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 26 Aug 2025 17:25:24 +0300 Subject: [PATCH 1/4] add uint16 support --- common/hexutil/hexutil.go | 8 ++++ common/hexutil/hexutil_test.go | 16 ++++++++ common/hexutil/json.go | 66 ++++++++++++++++++++++++++++++ common/hexutil/json_test.go | 74 ++++++++++++++++++++++++++++++++++ 4 files changed, 164 insertions(+) diff --git a/common/hexutil/hexutil.go b/common/hexutil/hexutil.go index d3201850a8e..219edbc6ad1 100644 --- a/common/hexutil/hexutil.go +++ b/common/hexutil/hexutil.go @@ -47,6 +47,7 @@ var ( ErrOddLength = &decError{"hex string of odd length"} ErrEmptyNumber = &decError{"hex string \"0x\""} ErrLeadingZero = &decError{"hex number with leading zero digits"} + ErrUint16Range = &decError{"hex number > 16 bits"} ErrUint64Range = &decError{"hex number > 64 bits"} ErrUintRange = &decError{fmt.Sprintf("hex number > %d bits", uintBits)} ErrBig256Range = &decError{"hex number > 256 bits"} @@ -118,6 +119,13 @@ func EncodeUint64(i uint64) string { return string(strconv.AppendUint(enc, i, 16)) } +// EncodeUint16 encodes i as a hex string with 0x prefix. +func EncodeUint16(i uint16) string { + enc := make([]byte, 2, 6) + copy(enc, "0x") + return string(strconv.AppendUint(enc, uint64(i), 16)) +} + var bigWordNibbles int func init() { diff --git a/common/hexutil/hexutil_test.go b/common/hexutil/hexutil_test.go index f2b800d82c9..ebfcd21cf46 100644 --- a/common/hexutil/hexutil_test.go +++ b/common/hexutil/hexutil_test.go @@ -57,6 +57,13 @@ var ( {uint64(0x1122334455667788), "0x1122334455667788"}, } + encodeUint16Tests = []marshalTest{ + {uint16(0), "0x0"}, + {uint16(1), "0x1"}, + {uint16(0xff), "0xff"}, + {uint16(0x1122), "0x1122"}, + } + encodeUintTests = []marshalTest{ {uint(0), "0x0"}, {uint(1), "0x1"}, @@ -189,6 +196,15 @@ func TestEncodeUint64(t *testing.T) { } } +func TestEncodeUint16(t *testing.T) { + for _, test := range encodeUint16Tests { + enc := EncodeUint16(test.input.(uint16)) + if enc != test.want { + t.Errorf("input %x: wrong encoding %s", test.input, enc) + } + } +} + func TestDecodeUint64(t *testing.T) { for _, test := range decodeUint64Tests { dec, err := DecodeUint64(test.input) diff --git a/common/hexutil/json.go b/common/hexutil/json.go index e0ac98f52d1..9a217059ee5 100644 --- a/common/hexutil/json.go +++ b/common/hexutil/json.go @@ -31,6 +31,7 @@ var ( bytesT = reflect.TypeOf(Bytes(nil)) bigT = reflect.TypeOf((*Big)(nil)) uintT = reflect.TypeOf(Uint(0)) + uint16T = reflect.TypeOf(Uint16(0)) uint64T = reflect.TypeOf(Uint64(0)) u256T = reflect.TypeOf((*uint256.Int)(nil)) ) @@ -369,6 +370,71 @@ func (b Uint) String() string { return EncodeUint64(uint64(b)) } +// Uint16 marshals/unmarshals as a JSON string with 0x prefix. +// The zero value marshals as "0x0". +type Uint16 uint16 + +// MarshalText implements encoding.TextMarshaler. +func (b Uint16) MarshalText() ([]byte, error) { + buf := make([]byte, 2, 6) + copy(buf, `0x`) + buf = strconv.AppendUint(buf, uint64(b), 16) + return buf, nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (b *Uint16) UnmarshalJSON(input []byte) error { + if !isString(input) { + return errNonString(uint16T) + } + return wrapTypeError(b.UnmarshalText(input[1:len(input)-1]), uint16T) +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (b *Uint16) UnmarshalText(input []byte) error { + raw, err := checkNumberText(input) + if err != nil { + return err + } + if len(raw) > 4 { + return ErrUint16Range + } + var dec uint16 + for _, byte := range raw { + nib := decodeNibble(byte) + if nib == badNibble { + return ErrSyntax + } + dec *= 16 + dec += uint16(nib) + } + + *b = Uint16(dec) + return nil +} + +// String returns the hex encoding of b. +func (b Uint16) String() string { + return EncodeUint16(uint16(b)) +} + +// ImplementsGraphQLType returns true if Uint16 implements the provided GraphQL type. +func (b Uint16) ImplementsGraphQLType(name string) bool { return name == "Int" } + +// UnmarshalGraphQL unmarshals the provided GraphQL query data. +func (b *Uint16) UnmarshalGraphQL(input interface{}) error { + var err error + switch input := input.(type) { + case string: + return b.UnmarshalText([]byte(input)) + case int32: + *b = Uint16(input) + default: + err = fmt.Errorf("unexpected type %T for Int", input) + } + return err +} + func isString(input []byte) bool { return len(input) >= 2 && input[0] == '"' && input[len(input)-1] == '"' } diff --git a/common/hexutil/json_test.go b/common/hexutil/json_test.go index 7cca300951c..0c7b5102e9b 100644 --- a/common/hexutil/json_test.go +++ b/common/hexutil/json_test.go @@ -398,6 +398,80 @@ func TestUnmarshalUint(t *testing.T) { } } +var unmarshalUint16Tests = []unmarshalTest{ + // invalid encoding + {input: "", wantErr: errJSONEOF}, + {input: "null", wantErr: errNonString(uint16T)}, + {input: "10", wantErr: errNonString(uint16T)}, + {input: `"0"`, wantErr: wrapTypeError(ErrMissingPrefix, uint16T)}, + {input: `"0x"`, wantErr: wrapTypeError(ErrEmptyNumber, uint16T)}, + {input: `"0x01"`, wantErr: wrapTypeError(ErrLeadingZero, uint16T)}, + {input: `"0x10000"`, wantErr: wrapTypeError(ErrUint16Range, uint16T)}, + {input: `"0xx"`, wantErr: wrapTypeError(ErrSyntax, uint16T)}, + {input: `"0x1zz01"`, wantErr: wrapTypeError(ErrSyntax, uint16T)}, + + // valid encoding + {input: `""`, want: uint16(0)}, + {input: `"0x0"`, want: uint16(0)}, + {input: `"0x2"`, want: uint16(0x2)}, + {input: `"0x2F2"`, want: uint16(0x2f2)}, + {input: `"0X2F2"`, want: uint16(0x2f2)}, + {input: `"0x1122"`, want: uint16(0x1122)}, + {input: `"0xbbb"`, want: uint16(0xbbb)}, + {input: `"0xffff"`, want: uint16(0xffff)}, +} + +func TestUnmarshalUint16(t *testing.T) { + for _, test := range unmarshalUint16Tests { + var v Uint16 + err := json.Unmarshal([]byte(test.input), &v) + if !checkError(t, test.input, err, test.wantErr) { + continue + } + if uint16(v) != test.want.(uint16) { + t.Errorf("input %s: value mismatch: got %d, want %d", test.input, v, test.want) + continue + } + } +} + +func BenchmarkUnmarshalUint16(b *testing.B) { + input := []byte(`"0x1234"`) + for i := 0; i < b.N; i++ { + var v Uint16 + v.UnmarshalJSON(input) + } +} + +func TestMarshalUint16(t *testing.T) { + tests := []struct { + input uint16 + want string + }{ + {0, "0x0"}, + {1, "0x1"}, + {0xff, "0xff"}, + {0x1122, "0x1122"}, + {0xffff, "0xffff"}, + } + + for _, test := range tests { + out, err := json.Marshal(Uint16(test.input)) + if err != nil { + t.Errorf("%d: %v", test.input, err) + continue + } + if want := `"` + test.want + `"`; string(out) != want { + t.Errorf("%d: MarshalJSON output mismatch: got %q, want %q", test.input, out, want) + continue + } + if out := Uint16(test.input).String(); out != test.want { + t.Errorf("%d: String mismatch: got %q, want %q", test.input, out, test.want) + continue + } + } +} + func TestUnmarshalFixedUnprefixedText(t *testing.T) { tests := []struct { input string From a98e92b77f595378364d99e46e995fd728c416fd Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 27 Aug 2025 15:42:01 +0300 Subject: [PATCH 2/4] add decoding --- common/hexutil/hexutil.go | 17 +++++++++++++++++ common/hexutil/hexutil_test.go | 31 +++++++++++++++++++++++++++++++ common/hexutil/json_test.go | 2 +- 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/common/hexutil/hexutil.go b/common/hexutil/hexutil.go index 219edbc6ad1..eb522f1abb7 100644 --- a/common/hexutil/hexutil.go +++ b/common/hexutil/hexutil.go @@ -112,6 +112,23 @@ func MustDecodeUint64(input string) uint64 { return dec } +// DecodeUint16 decodes a hex string with 0x prefix as a quantity. +func DecodeUint16(input string) (uint16, error) { + raw, err := checkNumber(input) + if err != nil { + return 0, err + } + dec, err := strconv.ParseUint(raw, 16, 16) + if err != nil { + err = mapError(err) + if err == ErrUint64Range { + return 0, ErrUint16Range + } + } + return uint16(dec), err +} + +// MustDecodeUint16 decodes a hex string with 0x prefix as a quantity. // EncodeUint64 encodes i as a hex string with 0x prefix. func EncodeUint64(i uint64) string { enc := make([]byte, 2, 10) diff --git a/common/hexutil/hexutil_test.go b/common/hexutil/hexutil_test.go index ebfcd21cf46..7829a256415 100644 --- a/common/hexutil/hexutil_test.go +++ b/common/hexutil/hexutil_test.go @@ -141,6 +141,24 @@ var ( {input: `0xbbb`, want: uint64(0xbbb)}, {input: `0xffffffffffffffff`, want: uint64(0xffffffffffffffff)}, } + + decodeUint16Tests = []unmarshalTest{ + // invalid + {input: `0`, wantErr: ErrMissingPrefix}, + {input: `0x`, wantErr: ErrEmptyNumber}, + {input: `0x01`, wantErr: ErrLeadingZero}, + {input: `0xfffff`, wantErr: ErrUint16Range}, + {input: `0xz1`, wantErr: ErrSyntax}, + // valid + {input: `0x0`, want: uint16(0)}, + {input: `0x2`, want: uint16(0x2)}, + {input: `0x2F2`, want: uint16(0x2f2)}, + {input: `0X2F2`, want: uint16(0x2f2)}, + {input: `0xff`, want: uint16(0xff)}, + {input: `0x12af`, want: uint16(0x12af)}, + {input: `0xbbb`, want: uint16(0xbbb)}, + {input: `0xffff`, want: uint16(0xffff)}, + } ) func TestEncode(t *testing.T) { @@ -218,6 +236,19 @@ func TestDecodeUint64(t *testing.T) { } } +func TestDecodeUint16(t *testing.T) { + for _, test := range decodeUint16Tests { + dec, err := DecodeUint16(test.input) + if !checkError(t, test.input, err, test.wantErr) { + continue + } + if dec != test.want.(uint16) { + t.Errorf("input %s: value mismatch: got %x, want %x", test.input, dec, test.want) + continue + } + } +} + func BenchmarkEncodeBig(b *testing.B) { for _, bench := range encodeBigTests { b.Run(bench.want, func(b *testing.B) { diff --git a/common/hexutil/json_test.go b/common/hexutil/json_test.go index 0c7b5102e9b..76a219fd77d 100644 --- a/common/hexutil/json_test.go +++ b/common/hexutil/json_test.go @@ -408,7 +408,7 @@ var unmarshalUint16Tests = []unmarshalTest{ {input: `"0x01"`, wantErr: wrapTypeError(ErrLeadingZero, uint16T)}, {input: `"0x10000"`, wantErr: wrapTypeError(ErrUint16Range, uint16T)}, {input: `"0xx"`, wantErr: wrapTypeError(ErrSyntax, uint16T)}, - {input: `"0x1zz01"`, wantErr: wrapTypeError(ErrSyntax, uint16T)}, + {input: `"0xz1"`, wantErr: wrapTypeError(ErrSyntax, uint16T)}, // valid encoding {input: `""`, want: uint16(0)}, From edcb5e4e050ed19833922a28bf34033d3051ef08 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 27 Aug 2025 15:59:08 +0300 Subject: [PATCH 3/4] move changes to libevm files --- common/hexutil/hexutil.go | 25 ------- common/hexutil/hexutil.libevm.go | 44 ++++++++++++ common/hexutil/hexutil.libevm_test.go | 68 +++++++++++++++++++ common/hexutil/hexutil_test.go | 47 ------------- common/hexutil/json.go | 66 ------------------ common/hexutil/json.libevm.go | 90 +++++++++++++++++++++++++ common/hexutil/json.libevm_test.go | 96 +++++++++++++++++++++++++++ common/hexutil/json_test.go | 74 --------------------- 8 files changed, 298 insertions(+), 212 deletions(-) create mode 100644 common/hexutil/hexutil.libevm.go create mode 100644 common/hexutil/hexutil.libevm_test.go create mode 100644 common/hexutil/json.libevm.go create mode 100644 common/hexutil/json.libevm_test.go diff --git a/common/hexutil/hexutil.go b/common/hexutil/hexutil.go index eb522f1abb7..d3201850a8e 100644 --- a/common/hexutil/hexutil.go +++ b/common/hexutil/hexutil.go @@ -47,7 +47,6 @@ var ( ErrOddLength = &decError{"hex string of odd length"} ErrEmptyNumber = &decError{"hex string \"0x\""} ErrLeadingZero = &decError{"hex number with leading zero digits"} - ErrUint16Range = &decError{"hex number > 16 bits"} ErrUint64Range = &decError{"hex number > 64 bits"} ErrUintRange = &decError{fmt.Sprintf("hex number > %d bits", uintBits)} ErrBig256Range = &decError{"hex number > 256 bits"} @@ -112,23 +111,6 @@ func MustDecodeUint64(input string) uint64 { return dec } -// DecodeUint16 decodes a hex string with 0x prefix as a quantity. -func DecodeUint16(input string) (uint16, error) { - raw, err := checkNumber(input) - if err != nil { - return 0, err - } - dec, err := strconv.ParseUint(raw, 16, 16) - if err != nil { - err = mapError(err) - if err == ErrUint64Range { - return 0, ErrUint16Range - } - } - return uint16(dec), err -} - -// MustDecodeUint16 decodes a hex string with 0x prefix as a quantity. // EncodeUint64 encodes i as a hex string with 0x prefix. func EncodeUint64(i uint64) string { enc := make([]byte, 2, 10) @@ -136,13 +118,6 @@ func EncodeUint64(i uint64) string { return string(strconv.AppendUint(enc, i, 16)) } -// EncodeUint16 encodes i as a hex string with 0x prefix. -func EncodeUint16(i uint16) string { - enc := make([]byte, 2, 6) - copy(enc, "0x") - return string(strconv.AppendUint(enc, uint64(i), 16)) -} - var bigWordNibbles int func init() { diff --git a/common/hexutil/hexutil.libevm.go b/common/hexutil/hexutil.libevm.go new file mode 100644 index 00000000000..b21a766bcd2 --- /dev/null +++ b/common/hexutil/hexutil.libevm.go @@ -0,0 +1,44 @@ +// Copyright 2025 the libevm authors. +// +// The libevm additions to go-ethereum are free software: you can redistribute +// them and/or modify them under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The libevm additions are distributed in the hope that they will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see +// . + +package hexutil + +import "strconv" + +var ErrUint16Range = &decError{"hex number > 16 bits"} + +// DecodeUint16 decodes a hex string with 0x prefix as a quantity. +func DecodeUint16(input string) (uint16, error) { + raw, err := checkNumber(input) + if err != nil { + return 0, err + } + dec, err := strconv.ParseUint(raw, 16, 16) + if err != nil { + err = mapError(err) + if err == ErrUint64Range { + return 0, ErrUint16Range + } + } + return uint16(dec), err +} + +// EncodeUint16 encodes i as a hex string with 0x prefix. +func EncodeUint16(i uint16) string { + enc := make([]byte, 2, 6) + copy(enc, "0x") + return string(strconv.AppendUint(enc, uint64(i), 16)) +} diff --git a/common/hexutil/hexutil.libevm_test.go b/common/hexutil/hexutil.libevm_test.go new file mode 100644 index 00000000000..d62a99e3561 --- /dev/null +++ b/common/hexutil/hexutil.libevm_test.go @@ -0,0 +1,68 @@ +// Copyright 2025 the libevm authors. +// +// The libevm additions to go-ethereum are free software: you can redistribute +// them and/or modify them under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The libevm additions are distributed in the hope that they will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see +// . + +package hexutil + +import "testing" + +var ( + encodeUint16Tests = []marshalTest{ + {uint16(0), "0x0"}, + {uint16(1), "0x1"}, + {uint16(0xff), "0xff"}, + {uint16(0x1122), "0x1122"}, + } + + decodeUint16Tests = []unmarshalTest{ + // invalid + {input: `0`, wantErr: ErrMissingPrefix}, + {input: `0x`, wantErr: ErrEmptyNumber}, + {input: `0x01`, wantErr: ErrLeadingZero}, + {input: `0xfffff`, wantErr: ErrUint16Range}, + {input: `0xz1`, wantErr: ErrSyntax}, + // valid + {input: `0x0`, want: uint16(0)}, + {input: `0x2`, want: uint16(0x2)}, + {input: `0x2F2`, want: uint16(0x2f2)}, + {input: `0X2F2`, want: uint16(0x2f2)}, + {input: `0xff`, want: uint16(0xff)}, + {input: `0x12af`, want: uint16(0x12af)}, + {input: `0xbbb`, want: uint16(0xbbb)}, + {input: `0xffff`, want: uint16(0xffff)}, + } +) + +func TestEncodeUint16(t *testing.T) { + for _, test := range encodeUint16Tests { + enc := EncodeUint16(test.input.(uint16)) + if enc != test.want { + t.Errorf("input %x: wrong encoding %s", test.input, enc) + } + } +} + +func TestDecodeUint16(t *testing.T) { + for _, test := range decodeUint16Tests { + dec, err := DecodeUint16(test.input) + if !checkError(t, test.input, err, test.wantErr) { + continue + } + if dec != test.want.(uint16) { + t.Errorf("input %s: value mismatch: got %x, want %x", test.input, dec, test.want) + continue + } + } +} diff --git a/common/hexutil/hexutil_test.go b/common/hexutil/hexutil_test.go index 7829a256415..f2b800d82c9 100644 --- a/common/hexutil/hexutil_test.go +++ b/common/hexutil/hexutil_test.go @@ -57,13 +57,6 @@ var ( {uint64(0x1122334455667788), "0x1122334455667788"}, } - encodeUint16Tests = []marshalTest{ - {uint16(0), "0x0"}, - {uint16(1), "0x1"}, - {uint16(0xff), "0xff"}, - {uint16(0x1122), "0x1122"}, - } - encodeUintTests = []marshalTest{ {uint(0), "0x0"}, {uint(1), "0x1"}, @@ -141,24 +134,6 @@ var ( {input: `0xbbb`, want: uint64(0xbbb)}, {input: `0xffffffffffffffff`, want: uint64(0xffffffffffffffff)}, } - - decodeUint16Tests = []unmarshalTest{ - // invalid - {input: `0`, wantErr: ErrMissingPrefix}, - {input: `0x`, wantErr: ErrEmptyNumber}, - {input: `0x01`, wantErr: ErrLeadingZero}, - {input: `0xfffff`, wantErr: ErrUint16Range}, - {input: `0xz1`, wantErr: ErrSyntax}, - // valid - {input: `0x0`, want: uint16(0)}, - {input: `0x2`, want: uint16(0x2)}, - {input: `0x2F2`, want: uint16(0x2f2)}, - {input: `0X2F2`, want: uint16(0x2f2)}, - {input: `0xff`, want: uint16(0xff)}, - {input: `0x12af`, want: uint16(0x12af)}, - {input: `0xbbb`, want: uint16(0xbbb)}, - {input: `0xffff`, want: uint16(0xffff)}, - } ) func TestEncode(t *testing.T) { @@ -214,15 +189,6 @@ func TestEncodeUint64(t *testing.T) { } } -func TestEncodeUint16(t *testing.T) { - for _, test := range encodeUint16Tests { - enc := EncodeUint16(test.input.(uint16)) - if enc != test.want { - t.Errorf("input %x: wrong encoding %s", test.input, enc) - } - } -} - func TestDecodeUint64(t *testing.T) { for _, test := range decodeUint64Tests { dec, err := DecodeUint64(test.input) @@ -236,19 +202,6 @@ func TestDecodeUint64(t *testing.T) { } } -func TestDecodeUint16(t *testing.T) { - for _, test := range decodeUint16Tests { - dec, err := DecodeUint16(test.input) - if !checkError(t, test.input, err, test.wantErr) { - continue - } - if dec != test.want.(uint16) { - t.Errorf("input %s: value mismatch: got %x, want %x", test.input, dec, test.want) - continue - } - } -} - func BenchmarkEncodeBig(b *testing.B) { for _, bench := range encodeBigTests { b.Run(bench.want, func(b *testing.B) { diff --git a/common/hexutil/json.go b/common/hexutil/json.go index 9a217059ee5..e0ac98f52d1 100644 --- a/common/hexutil/json.go +++ b/common/hexutil/json.go @@ -31,7 +31,6 @@ var ( bytesT = reflect.TypeOf(Bytes(nil)) bigT = reflect.TypeOf((*Big)(nil)) uintT = reflect.TypeOf(Uint(0)) - uint16T = reflect.TypeOf(Uint16(0)) uint64T = reflect.TypeOf(Uint64(0)) u256T = reflect.TypeOf((*uint256.Int)(nil)) ) @@ -370,71 +369,6 @@ func (b Uint) String() string { return EncodeUint64(uint64(b)) } -// Uint16 marshals/unmarshals as a JSON string with 0x prefix. -// The zero value marshals as "0x0". -type Uint16 uint16 - -// MarshalText implements encoding.TextMarshaler. -func (b Uint16) MarshalText() ([]byte, error) { - buf := make([]byte, 2, 6) - copy(buf, `0x`) - buf = strconv.AppendUint(buf, uint64(b), 16) - return buf, nil -} - -// UnmarshalJSON implements json.Unmarshaler. -func (b *Uint16) UnmarshalJSON(input []byte) error { - if !isString(input) { - return errNonString(uint16T) - } - return wrapTypeError(b.UnmarshalText(input[1:len(input)-1]), uint16T) -} - -// UnmarshalText implements encoding.TextUnmarshaler. -func (b *Uint16) UnmarshalText(input []byte) error { - raw, err := checkNumberText(input) - if err != nil { - return err - } - if len(raw) > 4 { - return ErrUint16Range - } - var dec uint16 - for _, byte := range raw { - nib := decodeNibble(byte) - if nib == badNibble { - return ErrSyntax - } - dec *= 16 - dec += uint16(nib) - } - - *b = Uint16(dec) - return nil -} - -// String returns the hex encoding of b. -func (b Uint16) String() string { - return EncodeUint16(uint16(b)) -} - -// ImplementsGraphQLType returns true if Uint16 implements the provided GraphQL type. -func (b Uint16) ImplementsGraphQLType(name string) bool { return name == "Int" } - -// UnmarshalGraphQL unmarshals the provided GraphQL query data. -func (b *Uint16) UnmarshalGraphQL(input interface{}) error { - var err error - switch input := input.(type) { - case string: - return b.UnmarshalText([]byte(input)) - case int32: - *b = Uint16(input) - default: - err = fmt.Errorf("unexpected type %T for Int", input) - } - return err -} - func isString(input []byte) bool { return len(input) >= 2 && input[0] == '"' && input[len(input)-1] == '"' } diff --git a/common/hexutil/json.libevm.go b/common/hexutil/json.libevm.go new file mode 100644 index 00000000000..f35efb6b28b --- /dev/null +++ b/common/hexutil/json.libevm.go @@ -0,0 +1,90 @@ +// Copyright 2025 the libevm authors. +// +// The libevm additions to go-ethereum are free software: you can redistribute +// them and/or modify them under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The libevm additions are distributed in the hope that they will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see +// . + +package hexutil + +import ( + "fmt" + "reflect" + "strconv" +) + +var uint16T = reflect.TypeOf(Uint16(0)) + +// Uint16 marshals/unmarshals as a JSON string with 0x prefix. +// The zero value marshals as "0x0". +type Uint16 uint16 + +// MarshalText implements encoding.TextMarshaler. +func (b Uint16) MarshalText() ([]byte, error) { + buf := make([]byte, 2, 6) + copy(buf, `0x`) + buf = strconv.AppendUint(buf, uint64(b), 16) + return buf, nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (b *Uint16) UnmarshalJSON(input []byte) error { + if !isString(input) { + return errNonString(uint16T) + } + return wrapTypeError(b.UnmarshalText(input[1:len(input)-1]), uint16T) +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (b *Uint16) UnmarshalText(input []byte) error { + raw, err := checkNumberText(input) + if err != nil { + return err + } + if len(raw) > 4 { + return ErrUint16Range + } + var dec uint16 + for _, byte := range raw { + nib := decodeNibble(byte) + if nib == badNibble { + return ErrSyntax + } + dec *= 16 + dec += uint16(nib) + } + + *b = Uint16(dec) + return nil +} + +// String returns the hex encoding of b. +func (b Uint16) String() string { + return EncodeUint16(uint16(b)) +} + +// ImplementsGraphQLType returns true if Uint16 implements the provided GraphQL type. +func (b Uint16) ImplementsGraphQLType(name string) bool { return name == "Int" } + +// UnmarshalGraphQL unmarshals the provided GraphQL query data. +func (b *Uint16) UnmarshalGraphQL(input interface{}) error { + var err error + switch input := input.(type) { + case string: + return b.UnmarshalText([]byte(input)) + case int32: + *b = Uint16(input) + default: + err = fmt.Errorf("unexpected type %T for Int", input) + } + return err +} diff --git a/common/hexutil/json.libevm_test.go b/common/hexutil/json.libevm_test.go new file mode 100644 index 00000000000..fdea14be544 --- /dev/null +++ b/common/hexutil/json.libevm_test.go @@ -0,0 +1,96 @@ +// Copyright 2025 the libevm authors. +// +// The libevm additions to go-ethereum are free software: you can redistribute +// them and/or modify them under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The libevm additions are distributed in the hope that they will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see +// . + +package hexutil + +import ( + "encoding/json" + "testing" +) + +var unmarshalUint16Tests = []unmarshalTest{ + // invalid encoding + {input: "", wantErr: errJSONEOF}, + {input: "null", wantErr: errNonString(uint16T)}, + {input: "10", wantErr: errNonString(uint16T)}, + {input: `"0"`, wantErr: wrapTypeError(ErrMissingPrefix, uint16T)}, + {input: `"0x"`, wantErr: wrapTypeError(ErrEmptyNumber, uint16T)}, + {input: `"0x01"`, wantErr: wrapTypeError(ErrLeadingZero, uint16T)}, + {input: `"0x10000"`, wantErr: wrapTypeError(ErrUint16Range, uint16T)}, + {input: `"0xx"`, wantErr: wrapTypeError(ErrSyntax, uint16T)}, + {input: `"0xz1"`, wantErr: wrapTypeError(ErrSyntax, uint16T)}, + + // valid encoding + {input: `""`, want: uint16(0)}, + {input: `"0x0"`, want: uint16(0)}, + {input: `"0x2"`, want: uint16(0x2)}, + {input: `"0x2F2"`, want: uint16(0x2f2)}, + {input: `"0X2F2"`, want: uint16(0x2f2)}, + {input: `"0x1122"`, want: uint16(0x1122)}, + {input: `"0xbbb"`, want: uint16(0xbbb)}, + {input: `"0xffff"`, want: uint16(0xffff)}, +} + +func TestUnmarshalUint16(t *testing.T) { + for _, test := range unmarshalUint16Tests { + var v Uint16 + err := json.Unmarshal([]byte(test.input), &v) + if !checkError(t, test.input, err, test.wantErr) { + continue + } + if uint16(v) != test.want.(uint16) { + t.Errorf("input %s: value mismatch: got %d, want %d", test.input, v, test.want) + continue + } + } +} + +func BenchmarkUnmarshalUint16(b *testing.B) { + input := []byte(`"0x1234"`) + for i := 0; i < b.N; i++ { + var v Uint16 + v.UnmarshalJSON(input) + } +} + +func TestMarshalUint16(t *testing.T) { + tests := []struct { + input uint16 + want string + }{ + {0, "0x0"}, + {1, "0x1"}, + {0xff, "0xff"}, + {0x1122, "0x1122"}, + {0xffff, "0xffff"}, + } + + for _, test := range tests { + out, err := json.Marshal(Uint16(test.input)) + if err != nil { + t.Errorf("%d: %v", test.input, err) + continue + } + if want := `"` + test.want + `"`; string(out) != want { + t.Errorf("%d: MarshalJSON output mismatch: got %q, want %q", test.input, out, want) + continue + } + if out := Uint16(test.input).String(); out != test.want { + t.Errorf("%d: String mismatch: got %q, want %q", test.input, out, test.want) + continue + } + } +} diff --git a/common/hexutil/json_test.go b/common/hexutil/json_test.go index 76a219fd77d..7cca300951c 100644 --- a/common/hexutil/json_test.go +++ b/common/hexutil/json_test.go @@ -398,80 +398,6 @@ func TestUnmarshalUint(t *testing.T) { } } -var unmarshalUint16Tests = []unmarshalTest{ - // invalid encoding - {input: "", wantErr: errJSONEOF}, - {input: "null", wantErr: errNonString(uint16T)}, - {input: "10", wantErr: errNonString(uint16T)}, - {input: `"0"`, wantErr: wrapTypeError(ErrMissingPrefix, uint16T)}, - {input: `"0x"`, wantErr: wrapTypeError(ErrEmptyNumber, uint16T)}, - {input: `"0x01"`, wantErr: wrapTypeError(ErrLeadingZero, uint16T)}, - {input: `"0x10000"`, wantErr: wrapTypeError(ErrUint16Range, uint16T)}, - {input: `"0xx"`, wantErr: wrapTypeError(ErrSyntax, uint16T)}, - {input: `"0xz1"`, wantErr: wrapTypeError(ErrSyntax, uint16T)}, - - // valid encoding - {input: `""`, want: uint16(0)}, - {input: `"0x0"`, want: uint16(0)}, - {input: `"0x2"`, want: uint16(0x2)}, - {input: `"0x2F2"`, want: uint16(0x2f2)}, - {input: `"0X2F2"`, want: uint16(0x2f2)}, - {input: `"0x1122"`, want: uint16(0x1122)}, - {input: `"0xbbb"`, want: uint16(0xbbb)}, - {input: `"0xffff"`, want: uint16(0xffff)}, -} - -func TestUnmarshalUint16(t *testing.T) { - for _, test := range unmarshalUint16Tests { - var v Uint16 - err := json.Unmarshal([]byte(test.input), &v) - if !checkError(t, test.input, err, test.wantErr) { - continue - } - if uint16(v) != test.want.(uint16) { - t.Errorf("input %s: value mismatch: got %d, want %d", test.input, v, test.want) - continue - } - } -} - -func BenchmarkUnmarshalUint16(b *testing.B) { - input := []byte(`"0x1234"`) - for i := 0; i < b.N; i++ { - var v Uint16 - v.UnmarshalJSON(input) - } -} - -func TestMarshalUint16(t *testing.T) { - tests := []struct { - input uint16 - want string - }{ - {0, "0x0"}, - {1, "0x1"}, - {0xff, "0xff"}, - {0x1122, "0x1122"}, - {0xffff, "0xffff"}, - } - - for _, test := range tests { - out, err := json.Marshal(Uint16(test.input)) - if err != nil { - t.Errorf("%d: %v", test.input, err) - continue - } - if want := `"` + test.want + `"`; string(out) != want { - t.Errorf("%d: MarshalJSON output mismatch: got %q, want %q", test.input, out, want) - continue - } - if out := Uint16(test.input).String(); out != test.want { - t.Errorf("%d: String mismatch: got %q, want %q", test.input, out, test.want) - continue - } - } -} - func TestUnmarshalFixedUnprefixedText(t *testing.T) { tests := []struct { input string From 7c945dd609062eaefbdfb3cc3deeaed366919f59 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 27 Aug 2025 19:16:36 +0300 Subject: [PATCH 4/4] fix linter --- common/hexutil/hexutil.libevm.go | 2 +- common/hexutil/hexutil.libevm_test.go | 14 +++++++++++--- common/hexutil/json.libevm.go | 20 +------------------- common/hexutil/json.libevm_test.go | 13 ++++++++++--- 4 files changed, 23 insertions(+), 26 deletions(-) diff --git a/common/hexutil/hexutil.libevm.go b/common/hexutil/hexutil.libevm.go index b21a766bcd2..5dbd228e71e 100644 --- a/common/hexutil/hexutil.libevm.go +++ b/common/hexutil/hexutil.libevm.go @@ -33,7 +33,7 @@ func DecodeUint16(input string) (uint16, error) { return 0, ErrUint16Range } } - return uint16(dec), err + return uint16(dec), err //nolint:gosec // G115 won't overflow uint16 as ParseUint uses 16 bits } // EncodeUint16 encodes i as a hex string with 0x prefix. diff --git a/common/hexutil/hexutil.libevm_test.go b/common/hexutil/hexutil.libevm_test.go index d62a99e3561..febfd8e9c5d 100644 --- a/common/hexutil/hexutil.libevm_test.go +++ b/common/hexutil/hexutil.libevm_test.go @@ -47,7 +47,11 @@ var ( func TestEncodeUint16(t *testing.T) { for _, test := range encodeUint16Tests { - enc := EncodeUint16(test.input.(uint16)) + input, ok := test.input.(uint16) + if !ok { + t.Errorf("input %v: not a uint16", test.input) + } + enc := EncodeUint16(input) if enc != test.want { t.Errorf("input %x: wrong encoding %s", test.input, enc) } @@ -60,8 +64,12 @@ func TestDecodeUint16(t *testing.T) { if !checkError(t, test.input, err, test.wantErr) { continue } - if dec != test.want.(uint16) { - t.Errorf("input %s: value mismatch: got %x, want %x", test.input, dec, test.want) + want, ok := test.want.(uint16) + if !ok { + t.Errorf("want %v: not a uint16", test.want) + } + if dec != want { + t.Errorf("input %s: value mismatch: got %x, want %x", test.input, dec, want) continue } } diff --git a/common/hexutil/json.libevm.go b/common/hexutil/json.libevm.go index f35efb6b28b..0dcdd33f821 100644 --- a/common/hexutil/json.libevm.go +++ b/common/hexutil/json.libevm.go @@ -17,7 +17,6 @@ package hexutil import ( - "fmt" "reflect" "strconv" ) @@ -60,7 +59,7 @@ func (b *Uint16) UnmarshalText(input []byte) error { return ErrSyntax } dec *= 16 - dec += uint16(nib) + dec += uint16(nib) //nolint:gosec // G115 won't overflow uint16 as decodeNibble uses 4 bits } *b = Uint16(dec) @@ -71,20 +70,3 @@ func (b *Uint16) UnmarshalText(input []byte) error { func (b Uint16) String() string { return EncodeUint16(uint16(b)) } - -// ImplementsGraphQLType returns true if Uint16 implements the provided GraphQL type. -func (b Uint16) ImplementsGraphQLType(name string) bool { return name == "Int" } - -// UnmarshalGraphQL unmarshals the provided GraphQL query data. -func (b *Uint16) UnmarshalGraphQL(input interface{}) error { - var err error - switch input := input.(type) { - case string: - return b.UnmarshalText([]byte(input)) - case int32: - *b = Uint16(input) - default: - err = fmt.Errorf("unexpected type %T for Int", input) - } - return err -} diff --git a/common/hexutil/json.libevm_test.go b/common/hexutil/json.libevm_test.go index fdea14be544..6cf4f53658a 100644 --- a/common/hexutil/json.libevm_test.go +++ b/common/hexutil/json.libevm_test.go @@ -51,8 +51,12 @@ func TestUnmarshalUint16(t *testing.T) { if !checkError(t, test.input, err, test.wantErr) { continue } - if uint16(v) != test.want.(uint16) { - t.Errorf("input %s: value mismatch: got %d, want %d", test.input, v, test.want) + want, ok := test.want.(uint16) + if !ok { + t.Errorf("want %v: not a uint16", test.want) + } + if uint16(v) != want { + t.Errorf("input %s: value mismatch: got %d, want %d", test.input, v, want) continue } } @@ -62,7 +66,10 @@ func BenchmarkUnmarshalUint16(b *testing.B) { input := []byte(`"0x1234"`) for i := 0; i < b.N; i++ { var v Uint16 - v.UnmarshalJSON(input) + err := v.UnmarshalJSON(input) + if err != nil { + b.Errorf("error unmarshalling: %v", err) + } } }