Make Hash and Address implement JSON marshalling, add hexutils to restricted

This commit is contained in:
Austin Roberts 2021-09-10 11:32:49 -05:00
parent e94946bad0
commit 0d3400ee78
6 changed files with 1277 additions and 0 deletions

View File

@ -1,9 +1,48 @@
package core
import (
"strings"
"bytes"
"fmt"
"encoding/hex"
)
type Hash [32]byte
func (h Hash) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`"%#x"`, h[:])), nil
}
func (h Hash) UnmarshalJSON(data []byte) error {
_, err := hex.Decode(h[32-len(data):], bytes.TrimPrefix(bytes.Trim(data, `"`), []byte("0x")))
return err
}
func HexToHash(data string) Hash {
h := Hash{}
b, _ := hex.DecodeString(strings.TrimPrefix(strings.Trim(data, `"`), "0x"))
copy(h[32-len(data):], b)
return h
}
type Address [20]byte
func (h Address) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`"%#x"`, h[:])), nil
}
func (h Address) UnmarshalJSON(data []byte) error {
_, err := hex.Decode(h[20-len(data):], bytes.TrimPrefix(bytes.Trim(data, `"`), []byte("0x")))
return err
}
func HexToAddress(data string) Address {
h := Address{}
b, _ := hex.DecodeString(strings.TrimPrefix(strings.Trim(data, `"`), "0x"))
copy(h[20-len(data):], b)
return h
}
type ChainEvent struct {
Block []byte // RLP Encoded block

View File

@ -0,0 +1,240 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it 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 go-ethereum library is distributed in the hope that it 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 <http://www.gnu.org/licenses/>.
/*
Package hexutil implements hex encoding with 0x prefix.
This encoding is used by the Ethereum RPC API to transport binary data in JSON payloads.
Encoding Rules
All hex data must have prefix "0x".
For byte slices, the hex data must be of even length. An empty byte slice
encodes as "0x".
Integers are encoded using the least amount of digits (no leading zero digits). Their
encoding may be of uneven length. The number zero encodes as "0x0".
*/
package hexutil
import (
"encoding/hex"
"fmt"
"math/big"
"strconv"
)
const uintBits = 32 << (uint64(^uint(0)) >> 63)
// Errors
var (
ErrEmptyString = &decError{"empty hex string"}
ErrSyntax = &decError{"invalid hex string"}
ErrMissingPrefix = &decError{"hex string without 0x prefix"}
ErrOddLength = &decError{"hex string of odd length"}
ErrEmptyNumber = &decError{"hex string \"0x\""}
ErrLeadingZero = &decError{"hex number with leading zero digits"}
ErrUint64Range = &decError{"hex number > 64 bits"}
ErrUintRange = &decError{fmt.Sprintf("hex number > %d bits", uintBits)}
ErrBig256Range = &decError{"hex number > 256 bits"}
)
type decError struct{ msg string }
func (err decError) Error() string { return err.msg }
// Decode decodes a hex string with 0x prefix.
func Decode(input string) ([]byte, error) {
if len(input) == 0 {
return nil, ErrEmptyString
}
if !has0xPrefix(input) {
return nil, ErrMissingPrefix
}
b, err := hex.DecodeString(input[2:])
if err != nil {
err = mapError(err)
}
return b, err
}
// MustDecode decodes a hex string with 0x prefix. It panics for invalid input.
func MustDecode(input string) []byte {
dec, err := Decode(input)
if err != nil {
panic(err)
}
return dec
}
// Encode encodes b as a hex string with 0x prefix.
func Encode(b []byte) string {
enc := make([]byte, len(b)*2+2)
copy(enc, "0x")
hex.Encode(enc[2:], b)
return string(enc)
}
// DecodeUint64 decodes a hex string with 0x prefix as a quantity.
func DecodeUint64(input string) (uint64, error) {
raw, err := checkNumber(input)
if err != nil {
return 0, err
}
dec, err := strconv.ParseUint(raw, 16, 64)
if err != nil {
err = mapError(err)
}
return dec, err
}
// MustDecodeUint64 decodes a hex string with 0x prefix as a quantity.
// It panics for invalid input.
func MustDecodeUint64(input string) uint64 {
dec, err := DecodeUint64(input)
if err != nil {
panic(err)
}
return dec
}
// 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))
}
var bigWordNibbles int
func init() {
// This is a weird way to compute the number of nibbles required for big.Word.
// The usual way would be to use constant arithmetic but go vet can't handle that.
b, _ := new(big.Int).SetString("FFFFFFFFFF", 16)
switch len(b.Bits()) {
case 1:
bigWordNibbles = 16
case 2:
bigWordNibbles = 8
default:
panic("weird big.Word size")
}
}
// DecodeBig decodes a hex string with 0x prefix as a quantity.
// Numbers larger than 256 bits are not accepted.
func DecodeBig(input string) (*big.Int, error) {
raw, err := checkNumber(input)
if err != nil {
return nil, err
}
if len(raw) > 64 {
return nil, ErrBig256Range
}
words := make([]big.Word, len(raw)/bigWordNibbles+1)
end := len(raw)
for i := range words {
start := end - bigWordNibbles
if start < 0 {
start = 0
}
for ri := start; ri < end; ri++ {
nib := decodeNibble(raw[ri])
if nib == badNibble {
return nil, ErrSyntax
}
words[i] *= 16
words[i] += big.Word(nib)
}
end = start
}
dec := new(big.Int).SetBits(words)
return dec, nil
}
// MustDecodeBig decodes a hex string with 0x prefix as a quantity.
// It panics for invalid input.
func MustDecodeBig(input string) *big.Int {
dec, err := DecodeBig(input)
if err != nil {
panic(err)
}
return dec
}
// EncodeBig encodes bigint as a hex string with 0x prefix.
// The sign of the integer is ignored.
func EncodeBig(bigint *big.Int) string {
nbits := bigint.BitLen()
if nbits == 0 {
return "0x0"
}
return fmt.Sprintf("%#x", bigint)
}
func has0xPrefix(input string) bool {
return len(input) >= 2 && input[0] == '0' && (input[1] == 'x' || input[1] == 'X')
}
func checkNumber(input string) (raw string, err error) {
if len(input) == 0 {
return "", ErrEmptyString
}
if !has0xPrefix(input) {
return "", ErrMissingPrefix
}
input = input[2:]
if len(input) == 0 {
return "", ErrEmptyNumber
}
if len(input) > 1 && input[0] == '0' {
return "", ErrLeadingZero
}
return input, nil
}
const badNibble = ^uint64(0)
func decodeNibble(in byte) uint64 {
switch {
case in >= '0' && in <= '9':
return uint64(in - '0')
case in >= 'A' && in <= 'F':
return uint64(in - 'A' + 10)
case in >= 'a' && in <= 'f':
return uint64(in - 'a' + 10)
default:
return badNibble
}
}
func mapError(err error) error {
if err, ok := err.(*strconv.NumError); ok {
switch err.Err {
case strconv.ErrRange:
return ErrUint64Range
case strconv.ErrSyntax:
return ErrSyntax
}
}
if _, ok := err.(hex.InvalidByteError); ok {
return ErrSyntax
}
if err == hex.ErrLength {
return ErrOddLength
}
return err
}

View File

@ -0,0 +1,203 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it 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 go-ethereum library is distributed in the hope that it 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 <http://www.gnu.org/licenses/>.
package hexutil
import (
"bytes"
"math/big"
"testing"
)
type marshalTest struct {
input interface{}
want string
}
type unmarshalTest struct {
input string
want interface{}
wantErr error // if set, decoding must fail on any platform
wantErr32bit error // if set, decoding must fail on 32bit platforms (used for Uint tests)
}
var (
encodeBytesTests = []marshalTest{
{[]byte{}, "0x"},
{[]byte{0}, "0x00"},
{[]byte{0, 0, 1, 2}, "0x00000102"},
}
encodeBigTests = []marshalTest{
{referenceBig("0"), "0x0"},
{referenceBig("1"), "0x1"},
{referenceBig("ff"), "0xff"},
{referenceBig("112233445566778899aabbccddeeff"), "0x112233445566778899aabbccddeeff"},
{referenceBig("80a7f2c1bcc396c00"), "0x80a7f2c1bcc396c00"},
{referenceBig("-80a7f2c1bcc396c00"), "-0x80a7f2c1bcc396c00"},
}
encodeUint64Tests = []marshalTest{
{uint64(0), "0x0"},
{uint64(1), "0x1"},
{uint64(0xff), "0xff"},
{uint64(0x1122334455667788), "0x1122334455667788"},
}
encodeUintTests = []marshalTest{
{uint(0), "0x0"},
{uint(1), "0x1"},
{uint(0xff), "0xff"},
{uint(0x11223344), "0x11223344"},
}
decodeBytesTests = []unmarshalTest{
// invalid
{input: ``, wantErr: ErrEmptyString},
{input: `0`, wantErr: ErrMissingPrefix},
{input: `0x0`, wantErr: ErrOddLength},
{input: `0x023`, wantErr: ErrOddLength},
{input: `0xxx`, wantErr: ErrSyntax},
{input: `0x01zz01`, wantErr: ErrSyntax},
// valid
{input: `0x`, want: []byte{}},
{input: `0X`, want: []byte{}},
{input: `0x02`, want: []byte{0x02}},
{input: `0X02`, want: []byte{0x02}},
{input: `0xffffffffff`, want: []byte{0xff, 0xff, 0xff, 0xff, 0xff}},
{
input: `0xffffffffffffffffffffffffffffffffffff`,
want: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
},
}
decodeBigTests = []unmarshalTest{
// invalid
{input: `0`, wantErr: ErrMissingPrefix},
{input: `0x`, wantErr: ErrEmptyNumber},
{input: `0x01`, wantErr: ErrLeadingZero},
{input: `0xx`, wantErr: ErrSyntax},
{input: `0x1zz01`, wantErr: ErrSyntax},
{
input: `0x10000000000000000000000000000000000000000000000000000000000000000`,
wantErr: ErrBig256Range,
},
// valid
{input: `0x0`, want: big.NewInt(0)},
{input: `0x2`, want: big.NewInt(0x2)},
{input: `0x2F2`, want: big.NewInt(0x2f2)},
{input: `0X2F2`, want: big.NewInt(0x2f2)},
{input: `0x1122aaff`, want: big.NewInt(0x1122aaff)},
{input: `0xbBb`, want: big.NewInt(0xbbb)},
{input: `0xfffffffff`, want: big.NewInt(0xfffffffff)},
{
input: `0x112233445566778899aabbccddeeff`,
want: referenceBig("112233445566778899aabbccddeeff"),
},
{
input: `0xffffffffffffffffffffffffffffffffffff`,
want: referenceBig("ffffffffffffffffffffffffffffffffffff"),
},
{
input: `0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`,
want: referenceBig("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
},
}
decodeUint64Tests = []unmarshalTest{
// invalid
{input: `0`, wantErr: ErrMissingPrefix},
{input: `0x`, wantErr: ErrEmptyNumber},
{input: `0x01`, wantErr: ErrLeadingZero},
{input: `0xfffffffffffffffff`, wantErr: ErrUint64Range},
{input: `0xx`, wantErr: ErrSyntax},
{input: `0x1zz01`, wantErr: ErrSyntax},
// valid
{input: `0x0`, want: uint64(0)},
{input: `0x2`, want: uint64(0x2)},
{input: `0x2F2`, want: uint64(0x2f2)},
{input: `0X2F2`, want: uint64(0x2f2)},
{input: `0x1122aaff`, want: uint64(0x1122aaff)},
{input: `0xbbb`, want: uint64(0xbbb)},
{input: `0xffffffffffffffff`, want: uint64(0xffffffffffffffff)},
}
)
func TestEncode(t *testing.T) {
for _, test := range encodeBytesTests {
enc := Encode(test.input.([]byte))
if enc != test.want {
t.Errorf("input %x: wrong encoding %s", test.input, enc)
}
}
}
func TestDecode(t *testing.T) {
for _, test := range decodeBytesTests {
dec, err := Decode(test.input)
if !checkError(t, test.input, err, test.wantErr) {
continue
}
if !bytes.Equal(test.want.([]byte), dec) {
t.Errorf("input %s: value mismatch: got %x, want %x", test.input, dec, test.want)
continue
}
}
}
func TestEncodeBig(t *testing.T) {
for _, test := range encodeBigTests {
enc := EncodeBig(test.input.(*big.Int))
if enc != test.want {
t.Errorf("input %x: wrong encoding %s", test.input, enc)
}
}
}
func TestDecodeBig(t *testing.T) {
for _, test := range decodeBigTests {
dec, err := DecodeBig(test.input)
if !checkError(t, test.input, err, test.wantErr) {
continue
}
if dec.Cmp(test.want.(*big.Int)) != 0 {
t.Errorf("input %s: value mismatch: got %x, want %x", test.input, dec, test.want)
continue
}
}
}
func TestEncodeUint64(t *testing.T) {
for _, test := range encodeUint64Tests {
enc := EncodeUint64(test.input.(uint64))
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)
if !checkError(t, test.input, err, test.wantErr) {
continue
}
if dec != test.want.(uint64) {
t.Errorf("input %s: value mismatch: got %x, want %x", test.input, dec, test.want)
continue
}
}
}

376
restricted/hexutil/json.go Normal file
View File

@ -0,0 +1,376 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it 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 go-ethereum library is distributed in the hope that it 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 <http://www.gnu.org/licenses/>.
package hexutil
import (
"encoding/hex"
"encoding/json"
"fmt"
"math/big"
"reflect"
"strconv"
)
var (
bytesT = reflect.TypeOf(Bytes(nil))
bigT = reflect.TypeOf((*Big)(nil))
uintT = reflect.TypeOf(Uint(0))
uint64T = reflect.TypeOf(Uint64(0))
)
// Bytes marshals/unmarshals as a JSON string with 0x prefix.
// The empty slice marshals as "0x".
type Bytes []byte
// MarshalText implements encoding.TextMarshaler
func (b Bytes) MarshalText() ([]byte, error) {
result := make([]byte, len(b)*2+2)
copy(result, `0x`)
hex.Encode(result[2:], b)
return result, nil
}
// UnmarshalJSON implements json.Unmarshaler.
func (b *Bytes) UnmarshalJSON(input []byte) error {
if !isString(input) {
return errNonString(bytesT)
}
return wrapTypeError(b.UnmarshalText(input[1:len(input)-1]), bytesT)
}
// UnmarshalText implements encoding.TextUnmarshaler.
func (b *Bytes) UnmarshalText(input []byte) error {
raw, err := checkText(input, true)
if err != nil {
return err
}
dec := make([]byte, len(raw)/2)
if _, err = hex.Decode(dec, raw); err != nil {
err = mapError(err)
} else {
*b = dec
}
return err
}
// String returns the hex encoding of b.
func (b Bytes) String() string {
return Encode(b)
}
// ImplementsGraphQLType returns true if Bytes implements the specified GraphQL type.
func (b Bytes) ImplementsGraphQLType(name string) bool { return name == "Bytes" }
// UnmarshalGraphQL unmarshals the provided GraphQL query data.
func (b *Bytes) UnmarshalGraphQL(input interface{}) error {
var err error
switch input := input.(type) {
case string:
data, err := Decode(input)
if err != nil {
return err
}
*b = data
default:
err = fmt.Errorf("unexpected type %T for Bytes", input)
}
return err
}
// UnmarshalFixedJSON decodes the input as a string with 0x prefix. The length of out
// determines the required input length. This function is commonly used to implement the
// UnmarshalJSON method for fixed-size types.
func UnmarshalFixedJSON(typ reflect.Type, input, out []byte) error {
if !isString(input) {
return errNonString(typ)
}
return wrapTypeError(UnmarshalFixedText(typ.String(), input[1:len(input)-1], out), typ)
}
// UnmarshalFixedText decodes the input as a string with 0x prefix. The length of out
// determines the required input length. This function is commonly used to implement the
// UnmarshalText method for fixed-size types.
func UnmarshalFixedText(typname string, input, out []byte) error {
raw, err := checkText(input, true)
if err != nil {
return err
}
if len(raw)/2 != len(out) {
return fmt.Errorf("hex string has length %d, want %d for %s", len(raw), len(out)*2, typname)
}
// Pre-verify syntax before modifying out.
for _, b := range raw {
if decodeNibble(b) == badNibble {
return ErrSyntax
}
}
hex.Decode(out, raw)
return nil
}
// UnmarshalFixedUnprefixedText decodes the input as a string with optional 0x prefix. The
// length of out determines the required input length. This function is commonly used to
// implement the UnmarshalText method for fixed-size types.
func UnmarshalFixedUnprefixedText(typname string, input, out []byte) error {
raw, err := checkText(input, false)
if err != nil {
return err
}
if len(raw)/2 != len(out) {
return fmt.Errorf("hex string has length %d, want %d for %s", len(raw), len(out)*2, typname)
}
// Pre-verify syntax before modifying out.
for _, b := range raw {
if decodeNibble(b) == badNibble {
return ErrSyntax
}
}
hex.Decode(out, raw)
return nil
}
// Big marshals/unmarshals as a JSON string with 0x prefix.
// The zero value marshals as "0x0".
//
// Negative integers are not supported at this time. Attempting to marshal them will
// return an error. Values larger than 256bits are rejected by Unmarshal but will be
// marshaled without error.
type Big big.Int
// MarshalText implements encoding.TextMarshaler
func (b Big) MarshalText() ([]byte, error) {
return []byte(EncodeBig((*big.Int)(&b))), nil
}
// UnmarshalJSON implements json.Unmarshaler.
func (b *Big) UnmarshalJSON(input []byte) error {
if !isString(input) {
return errNonString(bigT)
}
return wrapTypeError(b.UnmarshalText(input[1:len(input)-1]), bigT)
}
// UnmarshalText implements encoding.TextUnmarshaler
func (b *Big) UnmarshalText(input []byte) error {
raw, err := checkNumberText(input)
if err != nil {
return err
}
if len(raw) > 64 {
return ErrBig256Range
}
words := make([]big.Word, len(raw)/bigWordNibbles+1)
end := len(raw)
for i := range words {
start := end - bigWordNibbles
if start < 0 {
start = 0
}
for ri := start; ri < end; ri++ {
nib := decodeNibble(raw[ri])
if nib == badNibble {
return ErrSyntax
}
words[i] *= 16
words[i] += big.Word(nib)
}
end = start
}
var dec big.Int
dec.SetBits(words)
*b = (Big)(dec)
return nil
}
// ToInt converts b to a big.Int.
func (b *Big) ToInt() *big.Int {
return (*big.Int)(b)
}
// String returns the hex encoding of b.
func (b *Big) String() string {
return EncodeBig(b.ToInt())
}
// ImplementsGraphQLType returns true if Big implements the provided GraphQL type.
func (b Big) ImplementsGraphQLType(name string) bool { return name == "BigInt" }
// UnmarshalGraphQL unmarshals the provided GraphQL query data.
func (b *Big) UnmarshalGraphQL(input interface{}) error {
var err error
switch input := input.(type) {
case string:
return b.UnmarshalText([]byte(input))
case int32:
var num big.Int
num.SetInt64(int64(input))
*b = Big(num)
default:
err = fmt.Errorf("unexpected type %T for BigInt", input)
}
return err
}
// Uint64 marshals/unmarshals as a JSON string with 0x prefix.
// The zero value marshals as "0x0".
type Uint64 uint64
// MarshalText implements encoding.TextMarshaler.
func (b Uint64) MarshalText() ([]byte, error) {
buf := make([]byte, 2, 10)
copy(buf, `0x`)
buf = strconv.AppendUint(buf, uint64(b), 16)
return buf, nil
}
// UnmarshalJSON implements json.Unmarshaler.
func (b *Uint64) UnmarshalJSON(input []byte) error {
if !isString(input) {
return errNonString(uint64T)
}
return wrapTypeError(b.UnmarshalText(input[1:len(input)-1]), uint64T)
}
// UnmarshalText implements encoding.TextUnmarshaler
func (b *Uint64) UnmarshalText(input []byte) error {
raw, err := checkNumberText(input)
if err != nil {
return err
}
if len(raw) > 16 {
return ErrUint64Range
}
var dec uint64
for _, byte := range raw {
nib := decodeNibble(byte)
if nib == badNibble {
return ErrSyntax
}
dec *= 16
dec += nib
}
*b = Uint64(dec)
return nil
}
// String returns the hex encoding of b.
func (b Uint64) String() string {
return EncodeUint64(uint64(b))
}
// ImplementsGraphQLType returns true if Uint64 implements the provided GraphQL type.
func (b Uint64) ImplementsGraphQLType(name string) bool { return name == "Long" }
// UnmarshalGraphQL unmarshals the provided GraphQL query data.
func (b *Uint64) UnmarshalGraphQL(input interface{}) error {
var err error
switch input := input.(type) {
case string:
return b.UnmarshalText([]byte(input))
case int32:
*b = Uint64(input)
default:
err = fmt.Errorf("unexpected type %T for Long", input)
}
return err
}
// Uint marshals/unmarshals as a JSON string with 0x prefix.
// The zero value marshals as "0x0".
type Uint uint
// MarshalText implements encoding.TextMarshaler.
func (b Uint) MarshalText() ([]byte, error) {
return Uint64(b).MarshalText()
}
// UnmarshalJSON implements json.Unmarshaler.
func (b *Uint) UnmarshalJSON(input []byte) error {
if !isString(input) {
return errNonString(uintT)
}
return wrapTypeError(b.UnmarshalText(input[1:len(input)-1]), uintT)
}
// UnmarshalText implements encoding.TextUnmarshaler.
func (b *Uint) UnmarshalText(input []byte) error {
var u64 Uint64
err := u64.UnmarshalText(input)
if u64 > Uint64(^uint(0)) || err == ErrUint64Range {
return ErrUintRange
} else if err != nil {
return err
}
*b = Uint(u64)
return nil
}
// String returns the hex encoding of b.
func (b Uint) String() string {
return EncodeUint64(uint64(b))
}
func isString(input []byte) bool {
return len(input) >= 2 && input[0] == '"' && input[len(input)-1] == '"'
}
func bytesHave0xPrefix(input []byte) bool {
return len(input) >= 2 && input[0] == '0' && (input[1] == 'x' || input[1] == 'X')
}
func checkText(input []byte, wantPrefix bool) ([]byte, error) {
if len(input) == 0 {
return nil, nil // empty strings are allowed
}
if bytesHave0xPrefix(input) {
input = input[2:]
} else if wantPrefix {
return nil, ErrMissingPrefix
}
if len(input)%2 != 0 {
return nil, ErrOddLength
}
return input, nil
}
func checkNumberText(input []byte) (raw []byte, err error) {
if len(input) == 0 {
return nil, nil // empty strings are allowed
}
if !bytesHave0xPrefix(input) {
return nil, ErrMissingPrefix
}
input = input[2:]
if len(input) == 0 {
return nil, ErrEmptyNumber
}
if len(input) > 1 && input[0] == '0' {
return nil, ErrLeadingZero
}
return input, nil
}
func wrapTypeError(err error, typ reflect.Type) error {
if _, ok := err.(*decError); ok {
return &json.UnmarshalTypeError{Value: err.Error(), Type: typ}
}
return err
}
func errNonString(typ reflect.Type) error {
return &json.UnmarshalTypeError{Value: "non-string", Type: typ}
}

View File

@ -0,0 +1,45 @@
// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it 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 go-ethereum library is distributed in the hope that it 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 <http://www.gnu.org/licenses/>.
package hexutil_test
import (
"encoding/json"
"fmt"
"github.com/ethereum/go-ethereum/common/hexutil"
)
type MyType [5]byte
func (v *MyType) UnmarshalText(input []byte) error {
return hexutil.UnmarshalFixedText("MyType", input, v[:])
}
func (v MyType) String() string {
return hexutil.Bytes(v[:]).String()
}
func ExampleUnmarshalFixedText() {
var v1, v2 MyType
fmt.Println("v1 error:", json.Unmarshal([]byte(`"0x01"`), &v1))
fmt.Println("v2 error:", json.Unmarshal([]byte(`"0x0101010101"`), &v2))
fmt.Println("v2:", v2)
// Output:
// v1 error: hex string has length 2, want 10 for MyType
// v2 error: <nil>
// v2: 0x0101010101
}

View File

@ -0,0 +1,374 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it 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 go-ethereum library is distributed in the hope that it 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 <http://www.gnu.org/licenses/>.
package hexutil
import (
"bytes"
"encoding/hex"
"encoding/json"
"errors"
"math/big"
"testing"
)
func checkError(t *testing.T, input string, got, want error) bool {
if got == nil {
if want != nil {
t.Errorf("input %s: got no error, want %q", input, want)
return false
}
return true
}
if want == nil {
t.Errorf("input %s: unexpected error %q", input, got)
} else if got.Error() != want.Error() {
t.Errorf("input %s: got error %q, want %q", input, got, want)
}
return false
}
func referenceBig(s string) *big.Int {
b, ok := new(big.Int).SetString(s, 16)
if !ok {
panic("invalid")
}
return b
}
func referenceBytes(s string) []byte {
b, err := hex.DecodeString(s)
if err != nil {
panic(err)
}
return b
}
var errJSONEOF = errors.New("unexpected end of JSON input")
var unmarshalBytesTests = []unmarshalTest{
// invalid encoding
{input: "", wantErr: errJSONEOF},
{input: "null", wantErr: errNonString(bytesT)},
{input: "10", wantErr: errNonString(bytesT)},
{input: `"0"`, wantErr: wrapTypeError(ErrMissingPrefix, bytesT)},
{input: `"0x0"`, wantErr: wrapTypeError(ErrOddLength, bytesT)},
{input: `"0xxx"`, wantErr: wrapTypeError(ErrSyntax, bytesT)},
{input: `"0x01zz01"`, wantErr: wrapTypeError(ErrSyntax, bytesT)},
// valid encoding
{input: `""`, want: referenceBytes("")},
{input: `"0x"`, want: referenceBytes("")},
{input: `"0x02"`, want: referenceBytes("02")},
{input: `"0X02"`, want: referenceBytes("02")},
{input: `"0xffffffffff"`, want: referenceBytes("ffffffffff")},
{
input: `"0xffffffffffffffffffffffffffffffffffff"`,
want: referenceBytes("ffffffffffffffffffffffffffffffffffff"),
},
}
func TestUnmarshalBytes(t *testing.T) {
for _, test := range unmarshalBytesTests {
var v Bytes
err := json.Unmarshal([]byte(test.input), &v)
if !checkError(t, test.input, err, test.wantErr) {
continue
}
if !bytes.Equal(test.want.([]byte), v) {
t.Errorf("input %s: value mismatch: got %x, want %x", test.input, &v, test.want)
continue
}
}
}
func BenchmarkUnmarshalBytes(b *testing.B) {
input := []byte(`"0x123456789abcdef123456789abcdef"`)
for i := 0; i < b.N; i++ {
var v Bytes
if err := v.UnmarshalJSON(input); err != nil {
b.Fatal(err)
}
}
}
func TestMarshalBytes(t *testing.T) {
for _, test := range encodeBytesTests {
in := test.input.([]byte)
out, err := json.Marshal(Bytes(in))
if err != nil {
t.Errorf("%x: %v", in, err)
continue
}
if want := `"` + test.want + `"`; string(out) != want {
t.Errorf("%x: MarshalJSON output mismatch: got %q, want %q", in, out, want)
continue
}
if out := Bytes(in).String(); out != test.want {
t.Errorf("%x: String mismatch: got %q, want %q", in, out, test.want)
continue
}
}
}
var unmarshalBigTests = []unmarshalTest{
// invalid encoding
{input: "", wantErr: errJSONEOF},
{input: "null", wantErr: errNonString(bigT)},
{input: "10", wantErr: errNonString(bigT)},
{input: `"0"`, wantErr: wrapTypeError(ErrMissingPrefix, bigT)},
{input: `"0x"`, wantErr: wrapTypeError(ErrEmptyNumber, bigT)},
{input: `"0x01"`, wantErr: wrapTypeError(ErrLeadingZero, bigT)},
{input: `"0xx"`, wantErr: wrapTypeError(ErrSyntax, bigT)},
{input: `"0x1zz01"`, wantErr: wrapTypeError(ErrSyntax, bigT)},
{
input: `"0x10000000000000000000000000000000000000000000000000000000000000000"`,
wantErr: wrapTypeError(ErrBig256Range, bigT),
},
// valid encoding
{input: `""`, want: big.NewInt(0)},
{input: `"0x0"`, want: big.NewInt(0)},
{input: `"0x2"`, want: big.NewInt(0x2)},
{input: `"0x2F2"`, want: big.NewInt(0x2f2)},
{input: `"0X2F2"`, want: big.NewInt(0x2f2)},
{input: `"0x1122aaff"`, want: big.NewInt(0x1122aaff)},
{input: `"0xbBb"`, want: big.NewInt(0xbbb)},
{input: `"0xfffffffff"`, want: big.NewInt(0xfffffffff)},
{
input: `"0x112233445566778899aabbccddeeff"`,
want: referenceBig("112233445566778899aabbccddeeff"),
},
{
input: `"0xffffffffffffffffffffffffffffffffffff"`,
want: referenceBig("ffffffffffffffffffffffffffffffffffff"),
},
{
input: `"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"`,
want: referenceBig("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
},
}
func TestUnmarshalBig(t *testing.T) {
for _, test := range unmarshalBigTests {
var v Big
err := json.Unmarshal([]byte(test.input), &v)
if !checkError(t, test.input, err, test.wantErr) {
continue
}
if test.want != nil && test.want.(*big.Int).Cmp((*big.Int)(&v)) != 0 {
t.Errorf("input %s: value mismatch: got %x, want %x", test.input, (*big.Int)(&v), test.want)
continue
}
}
}
func BenchmarkUnmarshalBig(b *testing.B) {
input := []byte(`"0x123456789abcdef123456789abcdef"`)
for i := 0; i < b.N; i++ {
var v Big
if err := v.UnmarshalJSON(input); err != nil {
b.Fatal(err)
}
}
}
func TestMarshalBig(t *testing.T) {
for _, test := range encodeBigTests {
in := test.input.(*big.Int)
out, err := json.Marshal((*Big)(in))
if err != nil {
t.Errorf("%d: %v", in, err)
continue
}
if want := `"` + test.want + `"`; string(out) != want {
t.Errorf("%d: MarshalJSON output mismatch: got %q, want %q", in, out, want)
continue
}
if out := (*Big)(in).String(); out != test.want {
t.Errorf("%x: String mismatch: got %q, want %q", in, out, test.want)
continue
}
}
}
var unmarshalUint64Tests = []unmarshalTest{
// invalid encoding
{input: "", wantErr: errJSONEOF},
{input: "null", wantErr: errNonString(uint64T)},
{input: "10", wantErr: errNonString(uint64T)},
{input: `"0"`, wantErr: wrapTypeError(ErrMissingPrefix, uint64T)},
{input: `"0x"`, wantErr: wrapTypeError(ErrEmptyNumber, uint64T)},
{input: `"0x01"`, wantErr: wrapTypeError(ErrLeadingZero, uint64T)},
{input: `"0xfffffffffffffffff"`, wantErr: wrapTypeError(ErrUint64Range, uint64T)},
{input: `"0xx"`, wantErr: wrapTypeError(ErrSyntax, uint64T)},
{input: `"0x1zz01"`, wantErr: wrapTypeError(ErrSyntax, uint64T)},
// valid encoding
{input: `""`, want: uint64(0)},
{input: `"0x0"`, want: uint64(0)},
{input: `"0x2"`, want: uint64(0x2)},
{input: `"0x2F2"`, want: uint64(0x2f2)},
{input: `"0X2F2"`, want: uint64(0x2f2)},
{input: `"0x1122aaff"`, want: uint64(0x1122aaff)},
{input: `"0xbbb"`, want: uint64(0xbbb)},
{input: `"0xffffffffffffffff"`, want: uint64(0xffffffffffffffff)},
}
func TestUnmarshalUint64(t *testing.T) {
for _, test := range unmarshalUint64Tests {
var v Uint64
err := json.Unmarshal([]byte(test.input), &v)
if !checkError(t, test.input, err, test.wantErr) {
continue
}
if uint64(v) != test.want.(uint64) {
t.Errorf("input %s: value mismatch: got %d, want %d", test.input, v, test.want)
continue
}
}
}
func BenchmarkUnmarshalUint64(b *testing.B) {
input := []byte(`"0x123456789abcdf"`)
for i := 0; i < b.N; i++ {
var v Uint64
v.UnmarshalJSON(input)
}
}
func TestMarshalUint64(t *testing.T) {
for _, test := range encodeUint64Tests {
in := test.input.(uint64)
out, err := json.Marshal(Uint64(in))
if err != nil {
t.Errorf("%d: %v", in, err)
continue
}
if want := `"` + test.want + `"`; string(out) != want {
t.Errorf("%d: MarshalJSON output mismatch: got %q, want %q", in, out, want)
continue
}
if out := (Uint64)(in).String(); out != test.want {
t.Errorf("%x: String mismatch: got %q, want %q", in, out, test.want)
continue
}
}
}
func TestMarshalUint(t *testing.T) {
for _, test := range encodeUintTests {
in := test.input.(uint)
out, err := json.Marshal(Uint(in))
if err != nil {
t.Errorf("%d: %v", in, err)
continue
}
if want := `"` + test.want + `"`; string(out) != want {
t.Errorf("%d: MarshalJSON output mismatch: got %q, want %q", in, out, want)
continue
}
if out := (Uint)(in).String(); out != test.want {
t.Errorf("%x: String mismatch: got %q, want %q", in, out, test.want)
continue
}
}
}
var (
// These are variables (not constants) to avoid constant overflow
// checks in the compiler on 32bit platforms.
maxUint33bits = uint64(^uint32(0)) + 1
maxUint64bits = ^uint64(0)
)
var unmarshalUintTests = []unmarshalTest{
// invalid encoding
{input: "", wantErr: errJSONEOF},
{input: "null", wantErr: errNonString(uintT)},
{input: "10", wantErr: errNonString(uintT)},
{input: `"0"`, wantErr: wrapTypeError(ErrMissingPrefix, uintT)},
{input: `"0x"`, wantErr: wrapTypeError(ErrEmptyNumber, uintT)},
{input: `"0x01"`, wantErr: wrapTypeError(ErrLeadingZero, uintT)},
{input: `"0x100000000"`, want: uint(maxUint33bits), wantErr32bit: wrapTypeError(ErrUintRange, uintT)},
{input: `"0xfffffffffffffffff"`, wantErr: wrapTypeError(ErrUintRange, uintT)},
{input: `"0xx"`, wantErr: wrapTypeError(ErrSyntax, uintT)},
{input: `"0x1zz01"`, wantErr: wrapTypeError(ErrSyntax, uintT)},
// valid encoding
{input: `""`, want: uint(0)},
{input: `"0x0"`, want: uint(0)},
{input: `"0x2"`, want: uint(0x2)},
{input: `"0x2F2"`, want: uint(0x2f2)},
{input: `"0X2F2"`, want: uint(0x2f2)},
{input: `"0x1122aaff"`, want: uint(0x1122aaff)},
{input: `"0xbbb"`, want: uint(0xbbb)},
{input: `"0xffffffff"`, want: uint(0xffffffff)},
{input: `"0xffffffffffffffff"`, want: uint(maxUint64bits), wantErr32bit: wrapTypeError(ErrUintRange, uintT)},
}
func TestUnmarshalUint(t *testing.T) {
for _, test := range unmarshalUintTests {
var v Uint
err := json.Unmarshal([]byte(test.input), &v)
if uintBits == 32 && test.wantErr32bit != nil {
checkError(t, test.input, err, test.wantErr32bit)
continue
}
if !checkError(t, test.input, err, test.wantErr) {
continue
}
if uint(v) != test.want.(uint) {
t.Errorf("input %s: value mismatch: got %d, want %d", test.input, v, test.want)
continue
}
}
}
func TestUnmarshalFixedUnprefixedText(t *testing.T) {
tests := []struct {
input string
want []byte
wantErr error
}{
{input: "0x2", wantErr: ErrOddLength},
{input: "2", wantErr: ErrOddLength},
{input: "4444", wantErr: errors.New("hex string has length 4, want 8 for x")},
{input: "4444", wantErr: errors.New("hex string has length 4, want 8 for x")},
// check that output is not modified for partially correct input
{input: "444444gg", wantErr: ErrSyntax, want: []byte{0, 0, 0, 0}},
{input: "0x444444gg", wantErr: ErrSyntax, want: []byte{0, 0, 0, 0}},
// valid inputs
{input: "44444444", want: []byte{0x44, 0x44, 0x44, 0x44}},
{input: "0x44444444", want: []byte{0x44, 0x44, 0x44, 0x44}},
}
for _, test := range tests {
out := make([]byte, 4)
err := UnmarshalFixedUnprefixedText("x", []byte(test.input), out)
switch {
case err == nil && test.wantErr != nil:
t.Errorf("%q: got no error, expected %q", test.input, test.wantErr)
case err != nil && test.wantErr == nil:
t.Errorf("%q: unexpected error %q", test.input, err)
case err != nil && err.Error() != test.wantErr.Error():
t.Errorf("%q: error mismatch: got %q, want %q", test.input, err, test.wantErr)
}
if test.want != nil && !bytes.Equal(out, test.want) {
t.Errorf("%q: output mismatch: got %x, want %x", test.input, out, test.want)
}
}
}