Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions common/hexutil/hexutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"}
Expand Down Expand Up @@ -111,13 +112,37 @@ 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)
copy(enc, "0x")
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() {
Expand Down
47 changes: 47 additions & 0 deletions common/hexutil/hexutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
Expand Down Expand Up @@ -134,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) {
Expand Down Expand Up @@ -189,6 +214,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)
Expand All @@ -202,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) {
Expand Down
66 changes: 66 additions & 0 deletions common/hexutil/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
)
Expand Down Expand Up @@ -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] == '"'
}
Expand Down
74 changes: 74 additions & 0 deletions common/hexutil/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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: `"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
Expand Down
Loading