diff --git a/conversion.go b/conversion.go index c7da0c73..ad25895f 100644 --- a/conversion.go +++ b/conversion.go @@ -77,7 +77,7 @@ func FromBig(b *big.Int) (*Int, bool) { // MustFromBig is a convenience-constructor from big.Int. // Returns a new Int and panics if overflow occurred. -// OBS: If b is `nil`, this method does _not_ panic, but +// OBS: If b is `nil`, this method does _not_ panic, but // instead returns `nil` func MustFromBig(b *big.Int) *Int { if b == nil { @@ -135,7 +135,6 @@ func (z *Int) Float64() float64 { // - This method does not accept negative zero as valid, e.g "-0x0", // - (this method does not accept any negative input as valid) func (z *Int) SetFromHex(hex string) error { - z.Clear() return z.fromHex(hex) } @@ -147,6 +146,7 @@ func (z *Int) fromHex(hex string) error { if len(hex) > 66 { return ErrBig256Range } + z.Clear() end := len(hex) for i := 0; i < 4; i++ { start := end - 16 @@ -188,10 +188,14 @@ func MustFromHex(hex string) *Int { return &z } -// UnmarshalText implements encoding.TextUnmarshaler +// UnmarshalText implements encoding.TextUnmarshaler. This method +// can unmarshal either hexadecimal or decimal. +// - For hexadecimal, the input _must_ be prefixed with 0x or 0X func (z *Int) UnmarshalText(input []byte) error { - z.Clear() - return z.fromHex(string(input)) + if len(input) >= 2 && input[0] == '0' && (input[1] == 'x' || input[1] == 'X') { + return z.fromHex(string(input)) + } + return z.fromDecimal(string(input)) } // SetFromBig converts a big.Int to Int and sets the value to z. @@ -613,26 +617,36 @@ func (z *Int) EncodeRLP(w io.Writer) error { } // MarshalText implements encoding.TextMarshaler +// MarshalText marshals using the decimal representation (compatible with big.Int) func (z *Int) MarshalText() ([]byte, error) { - return []byte(z.Hex()), nil + return []byte(z.Dec()), nil } // MarshalJSON implements json.Marshaler. +// MarshalJSON marshals using the 'decimal string' representation. This is _not_ compatible +// with big.Int: big.Int marshals into JSON 'native' numeric format. +// +// The JSON native format is, on some platforms, (e.g. javascript), limited to 53-bit large +// integer space. Thus, U256 uses string-format, which is not compatible with +// big.int (big.Int refuses to unmarshal a string representation). func (z *Int) MarshalJSON() ([]byte, error) { - return []byte(`"` + z.Hex() + `"`), nil + return []byte(`"` + z.Dec() + `"`), nil } -// UnmarshalJSON implements json.Unmarshaler. +// UnmarshalJSON implements json.Unmarshaler. UnmarshalJSON accepts either +// - Quoted string: either hexadecimal OR decimal +// - Not quoted string: only decimal func (z *Int) UnmarshalJSON(input []byte) error { if len(input) < 2 || input[0] != '"' || input[len(input)-1] != '"' { - return ErrNonString + // if not quoted, it must be decimal + return z.fromDecimal(string(input)) } return z.UnmarshalText(input[1 : len(input)-1]) } -// String returns the hex encoding of b. +// String returns the decimal encoding of b. func (z *Int) String() string { - return z.Hex() + return z.Dec() } const ( @@ -738,7 +752,6 @@ var ( ErrEmptyNumber = errors.New("hex string \"0x\"") ErrLeadingZero = errors.New("hex number with leading zero digits") ErrBig256Range = errors.New("hex number > 256 bits") - ErrNonString = errors.New("non-string") ErrBadBufferLength = errors.New("bad ssz buffer length") ErrBadEncodedLength = errors.New("bad ssz encoded length") ) diff --git a/conversion_test.go b/conversion_test.go index 05451add..0f8864e2 100644 --- a/conversion_test.go +++ b/conversion_test.go @@ -1196,10 +1196,12 @@ func TestDecode(t *testing.T) { Foo *Int } var jsonDecoded jsonStruct - if err := json.Unmarshal([]byte(`{"Foo":0x1}`), &jsonDecoded); err == nil { - t.Fatal("Expected error") + // This test was previously an "expected error", The U256 behaviour has now + // changed, to be compatible with big.Int + if err := json.Unmarshal([]byte(`{"Foo":1}`), &jsonDecoded); err != nil { + t.Fatalf("Expected no error, have %v", err) } - if err := json.Unmarshal([]byte(`{"Foo":1}`), &jsonDecoded); err == nil { + if err := json.Unmarshal([]byte(`{"Foo":0x1}`), &jsonDecoded); err == nil { t.Fatal("Expected error") } if err := json.Unmarshal([]byte(`{"Foo":""}`), &jsonDecoded); err == nil { @@ -1216,38 +1218,61 @@ func TestEnDecode(t *testing.T) { type jsonStruct struct { Foo *Int } + type jsonBigStruct struct { + Foo *big.Int + } var testSample = func(i int, bigSample big.Int, intSample Int) { // Encoding wantHex := fmt.Sprintf("0x%s", bigSample.Text(16)) wantDec := bigSample.Text(10) - if got := intSample.Hex(); wantHex != got { - t.Fatalf("test %d #1, got %v, exp %v", i, got, wantHex) + if have, want := intSample.Hex(), fmt.Sprintf("0x%s", bigSample.Text(16)); have != want { + t.Fatalf("test %d #1, have %v, want %v", i, have, want) } - if got := intSample.String(); wantHex != got { - t.Fatalf("test %d #2, got %v, exp %v", i, got, wantHex) + if have, want := intSample.String(), bigSample.String(); have != want { + t.Fatalf("test %d String(), have %v, want %v", i, have, want) } - if got, _ := intSample.MarshalText(); wantHex != string(got) { - t.Fatalf("test %d #3, got %v, exp %v", i, got, wantHex) + { + have, _ := intSample.MarshalText() + want, _ := bigSample.MarshalText() + if !bytes.Equal(have, want) { + t.Fatalf("test %d MarshalText, have %q, want %q", i, have, want) + } } - if got, _ := intSample.Value(); wantDec != got.(string) { - t.Fatalf("test %d #4, got %v, exp %v", i, got, wantHex) + { + have, _ := intSample.MarshalJSON() + want := []byte(fmt.Sprintf(`"%s"`, bigSample.Text(10))) + if !bytes.Equal(have, want) { + t.Fatalf("test %d MarshalJSON, have %q, want %q", i, have, want) + } + } + if have, _ := intSample.Value(); wantDec != have.(string) { + t.Fatalf("test %d #4, got %v, exp %v", i, have, wantHex) } - if got := intSample.Dec(); wantDec != got { - t.Fatalf("test %d #5, got %v, exp %v", i, got, wantHex) + if have, want := intSample.Dec(), wantDec; have != want { + t.Fatalf("test %d Dec(), have %v, want %v", i, have, want) } { // Json jsonEncoded, err := json.Marshal(&jsonStruct{&intSample}) if err != nil { - t.Fatalf("test %d #6, err: %v", i, err) + t.Fatalf("test %d: json encoding err: %v", i, err) } + jsonEncodedBig, _ := json.Marshal(&jsonBigStruct{&bigSample}) var jsonDecoded jsonStruct err = json.Unmarshal(jsonEncoded, &jsonDecoded) if err != nil { - t.Fatalf("test %d #7, err: %v", i, err) + t.Fatalf("test %d error unmarshaling: %v", i, err) + } + if jsonDecoded.Foo.Cmp(&intSample) != 0 { + t.Fatalf("test %d #8, have %v, want %v", i, jsonDecoded.Foo, intSample) + } + // See if we can also unmarshal from big.Int's non-string format + err = json.Unmarshal(jsonEncodedBig, &jsonDecoded) + if err != nil { + t.Fatalf("test %d unmarshalling from big.Int err: %v", i, err) } if jsonDecoded.Foo.Cmp(&intSample) != 0 { - t.Fatalf("test %d #8, got %v, exp %v", i, jsonDecoded.Foo, intSample) + t.Fatalf("test %d have %v, want %v", i, jsonDecoded.Foo, intSample) } } // Decoding