Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

conversion: behavioural changes in String, MarshalText and MarshalJSON #144

Merged
merged 3 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
37 changes: 25 additions & 12 deletions conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}

Expand All @@ -147,6 +146,7 @@ func (z *Int) fromHex(hex string) error {
if len(hex) > 66 {
return ErrBig256Range
}
z.Clear()
holiman marked this conversation as resolved.
Show resolved Hide resolved
end := len(hex)
for i := 0; i < 4; i++ {
start := end - 16
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -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")
)
Expand Down
57 changes: 41 additions & 16 deletions conversion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down