From f382c88500fc6c8566dacd37822be71ea4bb0973 Mon Sep 17 00:00:00 2001 From: yperbasis Date: Thu, 2 Jul 2026 21:11:27 +0200 Subject: [PATCH 1/3] common: fix SetBytes on fixed-size byte types The Bytes4/48/64/96 copies of Hash.SetBytes kept length.Hash (32) in the crop and right-align arithmetic, so SetBytes panicked or misplaced bytes for most input lengths. Use the receiver's own length, matching Hash.SetBytes semantics (crop from the left, right-align short inputs). --- common/bytes4.go | 4 +-- common/bytes48.go | 4 +-- common/bytes64.go | 4 +-- common/bytes96.go | 4 +-- common/bytesn_test.go | 78 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 86 insertions(+), 8 deletions(-) create mode 100644 common/bytesn_test.go diff --git a/common/bytes4.go b/common/bytes4.go index 04a3345b500..2146ae2e42d 100644 --- a/common/bytes4.go +++ b/common/bytes4.go @@ -96,10 +96,10 @@ func (b Bytes4) String() string { // If b is larger than len(h), b will be cropped from the left. func (b *Bytes4) SetBytes(i []byte) { if len(i) > len(b) { - i = i[len(i)-length.Hash:] + i = i[len(i)-len(b):] } - copy(b[length.Hash-len(i):], i) + copy(b[len(b)-len(i):], i) } // Generate implements testing/quick.Generator. diff --git a/common/bytes48.go b/common/bytes48.go index b6b27e734fb..4e36668d33f 100644 --- a/common/bytes48.go +++ b/common/bytes48.go @@ -96,10 +96,10 @@ func (b Bytes48) String() string { // If b is larger than len(h), b will be cropped from the left. func (b *Bytes48) SetBytes(i []byte) { if len(i) > len(b) { - i = i[len(i)-length.Hash:] + i = i[len(i)-len(b):] } - copy(b[length.Hash-len(i):], i) + copy(b[len(b)-len(i):], i) } // Generate implements testing/quick.Generator. diff --git a/common/bytes64.go b/common/bytes64.go index 40da3a4ca75..be0c60ee2f0 100644 --- a/common/bytes64.go +++ b/common/bytes64.go @@ -95,10 +95,10 @@ func (b Bytes64) String() string { // If b is larger than len(h), b will be cropped from the left. func (b *Bytes64) SetBytes(i []byte) { if len(i) > len(b) { - i = i[len(i)-length.Hash:] + i = i[len(i)-len(b):] } - copy(b[length.Hash-len(i):], i) + copy(b[len(b)-len(i):], i) } // Value implements valuer for database/sql. diff --git a/common/bytes96.go b/common/bytes96.go index ee5a92241be..9c3d194fc1b 100644 --- a/common/bytes96.go +++ b/common/bytes96.go @@ -96,10 +96,10 @@ func (b Bytes96) String() string { // If b is larger than len(h), b will be cropped from the left. func (b *Bytes96) SetBytes(i []byte) { if len(i) > len(b) { - i = i[len(i)-length.Hash:] + i = i[len(i)-len(b):] } - copy(b[length.Hash-len(i):], i) + copy(b[len(b)-len(i):], i) } // Generate implements testing/quick.Generator. diff --git a/common/bytesn_test.go b/common/bytesn_test.go new file mode 100644 index 00000000000..96db82e9181 --- /dev/null +++ b/common/bytesn_test.go @@ -0,0 +1,78 @@ +// Copyright 2026 The Erigon Authors +// This file is part of Erigon. +// +// Erigon 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. +// +// Erigon 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 Erigon. If not, see . + +package common + +import ( + "bytes" + "testing" +) + +// SetBytes on all fixed-size byte types must follow Hash.SetBytes semantics: +// inputs longer than the type keep the last N bytes, shorter inputs are +// right-aligned with zero padding. +func TestBytesNSetBytes(t *testing.T) { + seq := func(n int) []byte { + b := make([]byte, n) + for i := range b { + b[i] = byte(i + 1) + } + return b + } + + check := func(t *testing.T, got, in []byte) { + t.Helper() + n := len(got) + want := make([]byte, n) + if len(in) >= n { + copy(want, in[len(in)-n:]) + } else { + copy(want[n-len(in):], in) + } + if !bytes.Equal(got, want) { + t.Fatalf("SetBytes(%d bytes) = %x, want %x", len(in), got, want) + } + } + + t.Run("Bytes4", func(t *testing.T) { + for _, n := range []int{2, 4, 6} { + var b Bytes4 + b.SetBytes(seq(n)) + check(t, b[:], seq(n)) + } + }) + t.Run("Bytes48", func(t *testing.T) { + for _, n := range []int{40, 48, 60} { + var b Bytes48 + b.SetBytes(seq(n)) + check(t, b[:], seq(n)) + } + }) + t.Run("Bytes64", func(t *testing.T) { + for _, n := range []int{40, 64, 70} { + var b Bytes64 + b.SetBytes(seq(n)) + check(t, b[:], seq(n)) + } + }) + t.Run("Bytes96", func(t *testing.T) { + for _, n := range []int{40, 96, 100} { + var b Bytes96 + b.SetBytes(seq(n)) + check(t, b[:], seq(n)) + } + }) +} From 9a6ac1c2b24a6fb30bbefb772387a5c028b6ba2f Mon Sep 17 00:00:00 2001 From: yperbasis Date: Thu, 2 Jul 2026 22:02:49 +0200 Subject: [PATCH 2/3] common: share fixed-size byte type helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bytes4/48/64/96, Hash, and Address carried near-identical copies of Format, SetBytes, TerminalString, MarshalText, and the testing/quick Generate loop. Move the shared bodies to unexported []byte helpers in bytesn.go (fixedFormat, fixedSetBytes, fixedTerminalString, fixedGenerate) and reuse hexutil.Bytes.MarshalText instead of the hand-rolled hex encoding; each method becomes a one-liner passing b[:]. Kept bespoke: Address Hex/String/Format EIP-55 checksum paths, Bytes4.TerminalString (whole-array %x; the head…tail form would overlap at 4 bytes), Hash.Generate (math/rand/v2), and the Hash/Address-only methods (Scan, Value, Cmp, Big, GraphQL). TestFixedBytesGolden pins byte-for-byte output of every affected method per type, with goldens captured from the pre-refactor implementations. --- common/address.go | 15 +- common/bytes4.go | 63 +----- common/bytes48.go | 63 +----- common/bytes64.go | 58 +----- common/bytes96.go | 63 +----- common/bytesn.go | 70 +++++++ common/bytesn_golden_test.go | 377 +++++++++++++++++++++++++++++++++++ common/hash.go | 41 +--- 8 files changed, 479 insertions(+), 271 deletions(-) create mode 100644 common/bytesn.go create mode 100644 common/bytesn_golden_test.go diff --git a/common/address.go b/common/address.go index 33412fab616..a83e12287af 100644 --- a/common/address.go +++ b/common/address.go @@ -130,21 +130,10 @@ func (a Address) Format(s fmt.State, c rune) { // SetBytes sets the address to the value of b. // If b is larger than len(a), b will be cropped from the left. -func (a *Address) SetBytes(b []byte) { - if len(b) > len(a) { - b = b[len(b)-length.Addr:] - } - copy(a[length.Addr-len(b):], b) -} +func (a *Address) SetBytes(b []byte) { fixedSetBytes(a[:], b) } // MarshalText returns the hex representation of a. -func (a Address) MarshalText() ([]byte, error) { - b := a[:] - result := make([]byte, len(b)*2+2) - copy(result, hexutil.HexPrefix) - hex.Encode(result[2:], b) - return result, nil -} +func (a Address) MarshalText() ([]byte, error) { return hexutil.Bytes(a[:]).MarshalText() } // UnmarshalText parses a hash in hex syntax. func (a *Address) UnmarshalText(input []byte) error { diff --git a/common/bytes4.go b/common/bytes4.go index 2146ae2e42d..5b2087b11e8 100644 --- a/common/bytes4.go +++ b/common/bytes4.go @@ -17,9 +17,7 @@ package common import ( - "bytes" "database/sql/driver" - "encoding/hex" "fmt" "math/rand" "reflect" @@ -48,76 +46,29 @@ func (b *Bytes4) UnmarshalText(input []byte) error { } // MarshalText returns the hex representation of a. -func (b Bytes4) MarshalText() ([]byte, error) { - bl := b[:] - result := make([]byte, len(b)*2+2) - copy(result, hexutil.HexPrefix) - hex.Encode(result[2:], bl) - return result, nil -} +func (b Bytes4) MarshalText() ([]byte, error) { return hexutil.Bytes(b[:]).MarshalText() } // Format implements fmt.Formatter. // Hash supports the %v, %s, %v, %x, %X and %d format verbs. -func (b Bytes4) Format(s fmt.State, c rune) { - hexb := make([]byte, 2+len(b)*2) - copy(hexb, "0x") - hex.Encode(hexb[2:], b[:]) - - switch c { - case 'x', 'X': - if !s.Flag('#') { - hexb = hexb[2:] - } - if c == 'X' { - hexb = bytes.ToUpper(hexb) - } - fallthrough - case 'v', 's': - s.Write(hexb) - case 'q': - q := []byte{'"'} - s.Write(q) - s.Write(hexb) - s.Write(q) - case 'd': - fmt.Fprint(s, ([len(b)]byte)(b)) - default: - fmt.Fprintf(s, "%%!%c(hash=%x)", c, b) - } -} +func (b Bytes4) Format(s fmt.State, c rune) { fixedFormat(s, c, "hash", b[:]) } // String implements the stringer interface and is used also by the logger when // doing full logging into a file. -func (b Bytes4) String() string { - return b.Hex() -} +func (b Bytes4) String() string { return b.Hex() } // SetBytes sets the hash to the value of i. // If b is larger than len(h), b will be cropped from the left. -func (b *Bytes4) SetBytes(i []byte) { - if len(i) > len(b) { - i = i[len(i)-len(b):] - } - - copy(b[len(b)-len(i):], i) -} +func (b *Bytes4) SetBytes(i []byte) { fixedSetBytes(b[:], i) } // Generate implements testing/quick.Generator. func (b Bytes4) Generate(rand *rand.Rand, size int) reflect.Value { - m := rand.Intn(len(b)) - for i := len(b) - 1; i > m; i-- { - b[i] = byte(rand.Uint32()) - } + fixedGenerate(rand, b[:]) return reflect.ValueOf(b) } // Value implements valuer for database/sql. -func (b Bytes4) Value() (driver.Value, error) { - return b[:], nil -} +func (b Bytes4) Value() (driver.Value, error) { return b[:], nil } // TerminalString implements log.TerminalStringer, formatting a string for console // output during logging. -func (b Bytes4) TerminalString() string { - return fmt.Sprintf("%x", b) -} +func (b Bytes4) TerminalString() string { return fmt.Sprintf("%x", b) } diff --git a/common/bytes48.go b/common/bytes48.go index 4e36668d33f..f688cbe7163 100644 --- a/common/bytes48.go +++ b/common/bytes48.go @@ -17,9 +17,7 @@ package common import ( - "bytes" "database/sql/driver" - "encoding/hex" "fmt" "math/rand" "reflect" @@ -48,76 +46,29 @@ func (b *Bytes48) UnmarshalText(input []byte) error { } // MarshalText returns the hex representation of a. -func (b Bytes48) MarshalText() ([]byte, error) { - bl := b[:] - result := make([]byte, len(b)*2+2) - copy(result, hexutil.HexPrefix) - hex.Encode(result[2:], bl) - return result, nil -} +func (b Bytes48) MarshalText() ([]byte, error) { return hexutil.Bytes(b[:]).MarshalText() } // Format implements fmt.Formatter. // Hash supports the %v, %s, %v, %x, %X and %d format verbs. -func (b Bytes48) Format(s fmt.State, c rune) { - hexb := make([]byte, 2+len(b)*2) - copy(hexb, "0x") - hex.Encode(hexb[2:], b[:]) - - switch c { - case 'x', 'X': - if !s.Flag('#') { - hexb = hexb[2:] - } - if c == 'X' { - hexb = bytes.ToUpper(hexb) - } - fallthrough - case 'v', 's': - s.Write(hexb) - case 'q': - q := []byte{'"'} - s.Write(q) - s.Write(hexb) - s.Write(q) - case 'd': - fmt.Fprint(s, ([len(b)]byte)(b)) - default: - fmt.Fprintf(s, "%%!%c(hash=%x)", c, b) - } -} +func (b Bytes48) Format(s fmt.State, c rune) { fixedFormat(s, c, "hash", b[:]) } // String implements the stringer interface and is used also by the logger when // doing full logging into a file. -func (b Bytes48) String() string { - return b.Hex() -} +func (b Bytes48) String() string { return b.Hex() } // SetBytes sets the hash to the value of i. // If b is larger than len(h), b will be cropped from the left. -func (b *Bytes48) SetBytes(i []byte) { - if len(i) > len(b) { - i = i[len(i)-len(b):] - } - - copy(b[len(b)-len(i):], i) -} +func (b *Bytes48) SetBytes(i []byte) { fixedSetBytes(b[:], i) } // Generate implements testing/quick.Generator. func (b Bytes48) Generate(rand *rand.Rand, size int) reflect.Value { - m := rand.Intn(len(b)) - for i := len(b) - 1; i > m; i-- { - b[i] = byte(rand.Uint32()) - } + fixedGenerate(rand, b[:]) return reflect.ValueOf(b) } // Value implements valuer for database/sql. -func (b Bytes48) Value() (driver.Value, error) { - return b[:], nil -} +func (b Bytes48) Value() (driver.Value, error) { return b[:], nil } // TerminalString implements log.TerminalStringer, formatting a string for console // output during logging. -func (b Bytes48) TerminalString() string { - return fmt.Sprintf("%x…%x", b[:3], b[len(b)-3:]) -} +func (b Bytes48) TerminalString() string { return fixedTerminalString(b[:]) } diff --git a/common/bytes64.go b/common/bytes64.go index be0c60ee2f0..725527dc505 100644 --- a/common/bytes64.go +++ b/common/bytes64.go @@ -17,9 +17,7 @@ package common import ( - "bytes" "database/sql/driver" - "encoding/hex" "fmt" "reflect" @@ -47,67 +45,23 @@ func (b *Bytes64) UnmarshalText(input []byte) error { } // MarshalText returns the hex representation of a. -func (b Bytes64) MarshalText() ([]byte, error) { - bl := b[:] - result := make([]byte, len(b)*2+2) - copy(result, hexutil.HexPrefix) - hex.Encode(result[2:], bl) - return result, nil -} +func (b Bytes64) MarshalText() ([]byte, error) { return hexutil.Bytes(b[:]).MarshalText() } // Format implements fmt.Formatter. // Hash supports the %v, %s, %v, %x, %X and %d format verbs. -func (b Bytes64) Format(s fmt.State, c rune) { - hexb := make([]byte, 2+len(b)*2) - copy(hexb, "0x") - hex.Encode(hexb[2:], b[:]) - - switch c { - case 'x', 'X': - if !s.Flag('#') { - hexb = hexb[2:] - } - if c == 'X' { - hexb = bytes.ToUpper(hexb) - } - fallthrough - case 'v', 's': - s.Write(hexb) - case 'q': - q := []byte{'"'} - s.Write(q) - s.Write(hexb) - s.Write(q) - case 'd': - fmt.Fprint(s, ([len(b)]byte)(b)) - default: - fmt.Fprintf(s, "%%!%c(hash=%x)", c, b) - } -} +func (b Bytes64) Format(s fmt.State, c rune) { fixedFormat(s, c, "hash", b[:]) } // String implements the stringer interface and is used also by the logger when // doing full logging into a file. -func (b Bytes64) String() string { - return b.Hex() -} +func (b Bytes64) String() string { return b.Hex() } // SetBytes sets the hash to the value of i. // If b is larger than len(h), b will be cropped from the left. -func (b *Bytes64) SetBytes(i []byte) { - if len(i) > len(b) { - i = i[len(i)-len(b):] - } - - copy(b[len(b)-len(i):], i) -} +func (b *Bytes64) SetBytes(i []byte) { fixedSetBytes(b[:], i) } // Value implements valuer for database/sql. -func (b Bytes64) Value() (driver.Value, error) { - return b[:], nil -} +func (b Bytes64) Value() (driver.Value, error) { return b[:], nil } // TerminalString implements log.TerminalStringer, formatting a string for console // output during logging. -func (b Bytes64) TerminalString() string { - return fmt.Sprintf("%x…%x", b[:3], b[len(b)-3:]) -} +func (b Bytes64) TerminalString() string { return fixedTerminalString(b[:]) } diff --git a/common/bytes96.go b/common/bytes96.go index 9c3d194fc1b..f7606914988 100644 --- a/common/bytes96.go +++ b/common/bytes96.go @@ -17,9 +17,7 @@ package common import ( - "bytes" "database/sql/driver" - "encoding/hex" "fmt" "math/rand" "reflect" @@ -48,76 +46,29 @@ func (b *Bytes96) UnmarshalText(input []byte) error { } // MarshalText returns the hex representation of a. -func (b Bytes96) MarshalText() ([]byte, error) { - bl := b[:] - result := make([]byte, len(b)*2+2) - copy(result, hexutil.HexPrefix) - hex.Encode(result[2:], bl) - return result, nil -} +func (b Bytes96) MarshalText() ([]byte, error) { return hexutil.Bytes(b[:]).MarshalText() } // Format implements fmt.Formatter. // Hash supports the %v, %s, %v, %x, %X and %d format verbs. -func (b Bytes96) Format(s fmt.State, c rune) { - hexb := make([]byte, 2+len(b)*2) - copy(hexb, "0x") - hex.Encode(hexb[2:], b[:]) - - switch c { - case 'x', 'X': - if !s.Flag('#') { - hexb = hexb[2:] - } - if c == 'X' { - hexb = bytes.ToUpper(hexb) - } - fallthrough - case 'v', 's': - s.Write(hexb) - case 'q': - q := []byte{'"'} - s.Write(q) - s.Write(hexb) - s.Write(q) - case 'd': - fmt.Fprint(s, ([len(b)]byte)(b)) - default: - fmt.Fprintf(s, "%%!%c(hash=%x)", c, b) - } -} +func (b Bytes96) Format(s fmt.State, c rune) { fixedFormat(s, c, "hash", b[:]) } // String implements the stringer interface and is used also by the logger when // doing full logging into a file. -func (b Bytes96) String() string { - return b.Hex() -} +func (b Bytes96) String() string { return b.Hex() } // SetBytes sets the hash to the value of i. // If b is larger than len(h), b will be cropped from the left. -func (b *Bytes96) SetBytes(i []byte) { - if len(i) > len(b) { - i = i[len(i)-len(b):] - } - - copy(b[len(b)-len(i):], i) -} +func (b *Bytes96) SetBytes(i []byte) { fixedSetBytes(b[:], i) } // Generate implements testing/quick.Generator. func (b Bytes96) Generate(rand *rand.Rand, size int) reflect.Value { - m := rand.Intn(len(b)) - for i := len(b) - 1; i > m; i-- { - b[i] = byte(rand.Uint32()) - } + fixedGenerate(rand, b[:]) return reflect.ValueOf(b) } // Value implements valuer for database/sql. -func (b Bytes96) Value() (driver.Value, error) { - return b[:], nil -} +func (b Bytes96) Value() (driver.Value, error) { return b[:], nil } // TerminalString implements log.TerminalStringer, formatting a string for console // output during logging. -func (b Bytes96) TerminalString() string { - return fmt.Sprintf("%x…%x", b[:3], b[len(b)-3:]) -} +func (b Bytes96) TerminalString() string { return fixedTerminalString(b[:]) } diff --git a/common/bytesn.go b/common/bytesn.go new file mode 100644 index 00000000000..4b8884ea872 --- /dev/null +++ b/common/bytesn.go @@ -0,0 +1,70 @@ +// Copyright 2026 The Erigon Authors +// This file is part of Erigon. +// +// Erigon 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. +// +// Erigon 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 Erigon. If not, see . + +package common + +import ( + "bytes" + "encoding/hex" + "fmt" + "math/rand" +) + +func fixedFormat(s fmt.State, c rune, typeName string, b []byte) { + hexb := make([]byte, 2+len(b)*2) + copy(hexb, "0x") + hex.Encode(hexb[2:], b) + + switch c { + case 'x', 'X': + if !s.Flag('#') { + hexb = hexb[2:] + } + if c == 'X' { + hexb = bytes.ToUpper(hexb) + } + fallthrough + case 'v', 's': + s.Write(hexb) + case 'q': + q := []byte{'"'} + s.Write(q) + s.Write(hexb) + s.Write(q) + case 'd': + fmt.Fprint(s, b) + default: + fmt.Fprintf(s, "%%!%c(%s=%x)", c, typeName, b) + } +} + +func fixedSetBytes(dst, src []byte) { + if len(src) > len(dst) { + src = src[len(src)-len(dst):] + } + copy(dst[len(dst)-len(src):], src) +} + +func fixedTerminalString(b []byte) string { + return fmt.Sprintf("%x…%x", b[:3], b[len(b)-3:]) +} + +func fixedGenerate(rnd *rand.Rand, b []byte) { + m := rnd.Intn(len(b)) + for i := len(b) - 1; i > m; i-- { + b[i] = byte(rnd.Uint32()) + } +} diff --git a/common/bytesn_golden_test.go b/common/bytesn_golden_test.go new file mode 100644 index 00000000000..0f769f8877a --- /dev/null +++ b/common/bytesn_golden_test.go @@ -0,0 +1,377 @@ +// Copyright 2026 The Erigon Authors +// This file is part of Erigon. +// +// Erigon 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. +// +// Erigon 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 Erigon. If not, see . + +package common + +import ( + "database/sql/driver" + "encoding" + "encoding/json" + "errors" + "fmt" + mrand "math/rand" + randv2 "math/rand/v2" + "testing" +) + +var fixedBytesGoldens = map[string]map[string]string{ + "Bytes4": { + "String": "0x01020304", + "MarshalText": "0x01020304", + "Value": "01020304", + "%v": "0x01020304", + "%s": "0x01020304", + "%q": "\"0x01020304\"", + "%x": "01020304", + "%X": "01020304", + "%#x": "0x01020304", + "%#X": "0X01020304", + "%d": "[1 2 3 4]", + "%t": "%!t(hash=01020304)", + "Hex": "0x01020304", + "TerminalString": "01020304", + "SetBytesShort": "00deadbe", + "SetBytesLong": "06070809", + "UnmarshalTextErr": "hex string has length 4, want 8 for Bytes4", + "UnmarshalJSONErrType": "common.Bytes4", + "Generate": "010203dd", + }, + "Bytes48": { + "String": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30", + "MarshalText": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30", + "Value": "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30", + "%v": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30", + "%s": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30", + "%q": "\"0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30\"", + "%x": "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30", + "%X": "0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30", + "%#x": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30", + "%#X": "0X0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30", + "%d": "[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48]", + "%t": "%!t(hash=0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30)", + "Hex": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30", + "TerminalString": "010203…2e2f30", + "SetBytesShort": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000deadbe", + "SetBytesLong": "060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435", + "UnmarshalTextErr": "hex string has length 4, want 96 for Bytes48", + "UnmarshalJSONErrType": "common.Bytes48", + "Generate": "0102030405060708090a0b0c0d0e0ff6b16be9bf8ac39fba6a4f2665389c9b5ce0b0eee5f5a2f97870cc80e9897f5add", + }, + "Bytes64": { + "String": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40", + "MarshalText": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40", + "Value": "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40", + "%v": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40", + "%s": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40", + "%q": "\"0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40\"", + "%x": "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40", + "%X": "0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F40", + "%#x": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40", + "%#X": "0X0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F40", + "%d": "[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64]", + "%t": "%!t(hash=0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40)", + "Hex": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40", + "TerminalString": "010203…3e3f40", + "SetBytesShort": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000deadbe", + "SetBytesLong": "060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445", + "UnmarshalTextErr": "hex string has length 4, want 128 for Bytes64", + "UnmarshalJSONErrType": "common.Bytes64", + }, + "Bytes96": { + "String": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f60", + "MarshalText": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f60", + "Value": "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f60", + "%v": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f60", + "%s": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f60", + "%q": "\"0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f60\"", + "%x": "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f60", + "%X": "0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F60", + "%#x": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f60", + "%#X": "0X0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F60", + "%d": "[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96]", + "%t": "%!t(hash=0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f60)", + "Hex": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f60", + "TerminalString": "010203…5e5f60", + "SetBytesShort": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000deadbe", + "SetBytesLong": "060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465", + "UnmarshalTextErr": "hex string has length 4, want 192 for BLSSignature", + "UnmarshalJSONErrType": "common.Bytes96", + "Generate": "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3ff6b16be9bf8ac39fba6a4f2665389c9b5ce0b0eee5f5a2f97870cc80e9897f5add", + }, + "Hash": { + "String": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", + "MarshalText": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", + "Value": "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", + "%v": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", + "%s": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", + "%q": "\"0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20\"", + "%x": "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", + "%X": "0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20", + "%#x": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", + "%#X": "0X0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20", + "%d": "[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32]", + "%t": "%!t(hash=0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20)", + "Hex": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", + "TerminalString": "010203…1e1f20", + "SetBytesShort": "0000000000000000000000000000000000000000000000000000000000deadbe", + "SetBytesLong": "060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425", + "UnmarshalTextErr": "hex string has length 4, want 64 for Hash", + "UnmarshalJSONErrType": "common.Hash", + "Generate": "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f80", + }, + "Address": { + "String": "0x0102030405060708090a0B0c0d0e0f1011121314", + "MarshalText": "0x0102030405060708090a0b0c0d0e0f1011121314", + "Value": "0102030405060708090a0b0c0d0e0f1011121314", + "%v": "0x0102030405060708090a0B0c0d0e0f1011121314", + "%s": "0x0102030405060708090a0B0c0d0e0f1011121314", + "%q": "\"0x0102030405060708090a0B0c0d0e0f1011121314\"", + "%x": "0102030405060708090a0b0c0d0e0f1011121314", + "%X": "0102030405060708090A0B0C0D0E0F1011121314", + "%#x": "0x0102030405060708090a0b0c0d0e0f1011121314", + "%#X": "0X0102030405060708090A0B0C0D0E0F1011121314", + "%d": "[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20]", + "%t": "%!t(address=0102030405060708090a0b0c0d0e0f1011121314)", + "Hex": "0x0102030405060708090a0B0c0d0e0f1011121314", + "HexEIP55": "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", + "SetBytesShort": "0000000000000000000000000000000000deadbe", + "SetBytesLong": "060708090a0b0c0d0e0f10111213141516171819", + "UnmarshalTextErr": "hex string has length 4, want 40 for Address", + "UnmarshalJSONErrType": "common.Address", + }, +} + +// TestFixedBytesGolden pins the exact formatting, marshalling, and SetBytes +// output of every fixed-size byte type. +func TestFixedBytesGolden(t *testing.T) { + for _, tc := range fixedBytesGoldenCases(t) { + t.Run(tc.name, func(t *testing.T) { + want := fixedBytesGoldens[tc.name] + if len(tc.rows) != len(want) { + t.Errorf("have %d rows, want %d goldens", len(tc.rows), len(want)) + } + for _, r := range tc.rows { + w, ok := want[r.name] + if !ok { + t.Errorf("%s: no golden", r.name) + continue + } + if r.got != w { + t.Errorf("%s = %q, want %q", r.name, r.got, w) + } + } + }) + } +} + +type goldenRow struct { + name string + got string +} + +type goldenCase struct { + name string + rows []goldenRow +} + +func fixedBytesGoldenCases(t *testing.T) []goldenCase { + t.Helper() + return []goldenCase{ + {"Bytes4", bytes4Goldens(t)}, + {"Bytes48", bytes48Goldens(t)}, + {"Bytes64", bytes64Goldens(t)}, + {"Bytes96", bytes96Goldens(t)}, + {"Hash", hashGoldens(t)}, + {"Address", addressGoldens(t)}, + } +} + +func goldenSeq(n int) []byte { + b := make([]byte, n) + for i := range b { + b[i] = byte(i + 1) + } + return b +} + +type fixedBytesValue interface { + fmt.Stringer + encoding.TextMarshaler + driver.Valuer +} + +func formatGoldens(t *testing.T, v fixedBytesValue) []goldenRow { + t.Helper() + text, err := v.MarshalText() + if err != nil { + t.Fatalf("MarshalText: %v", err) + } + value, err := v.Value() + if err != nil { + t.Fatalf("Value: %v", err) + } + valueBytes, ok := value.([]byte) + if !ok { + t.Fatalf("Value returned %T, want []byte", value) + } + rows := []goldenRow{ + {"String", v.String()}, + {"MarshalText", string(text)}, + {"Value", fmt.Sprintf("%x", valueBytes)}, + } + for _, verb := range []string{"%v", "%s", "%q", "%x", "%X", "%#x", "%#X", "%d", "%t"} { + rows = append(rows, goldenRow{verb, fmt.Sprintf(verb, v)}) + } + return rows +} + +func errString(err error) string { + if err == nil { + return "" + } + return err.Error() +} + +func jsonErrType(t *testing.T, err error) string { + t.Helper() + var typeErr *json.UnmarshalTypeError + if !errors.As(err, &typeErr) { + t.Fatalf("expected *json.UnmarshalTypeError, got %v", err) + } + return typeErr.Type.String() +} + +func bytes4Goldens(t *testing.T) []goldenRow { + t.Helper() + var b Bytes4 + b.SetBytes(goldenSeq(4)) + + var short, long, u, j Bytes4 + short.SetBytes([]byte{0xde, 0xad, 0xbe}) + long.SetBytes(goldenSeq(9)) + gen := b.Generate(mrand.New(mrand.NewSource(7)), 10).Interface().(Bytes4) + + return append(formatGoldens(t, b), + goldenRow{"Hex", b.Hex()}, + goldenRow{"TerminalString", b.TerminalString()}, + goldenRow{"SetBytesShort", fmt.Sprintf("%x", short)}, + goldenRow{"SetBytesLong", fmt.Sprintf("%x", long)}, + goldenRow{"UnmarshalTextErr", errString(u.UnmarshalText([]byte("0x1234")))}, + goldenRow{"UnmarshalJSONErrType", jsonErrType(t, j.UnmarshalJSON([]byte("5")))}, + goldenRow{"Generate", fmt.Sprintf("%x", gen)}, + ) +} + +func bytes48Goldens(t *testing.T) []goldenRow { + t.Helper() + var b Bytes48 + b.SetBytes(goldenSeq(48)) + + var short, long, u, j Bytes48 + short.SetBytes([]byte{0xde, 0xad, 0xbe}) + long.SetBytes(goldenSeq(53)) + gen := b.Generate(mrand.New(mrand.NewSource(7)), 10).Interface().(Bytes48) + + return append(formatGoldens(t, b), + goldenRow{"Hex", b.Hex()}, + goldenRow{"TerminalString", b.TerminalString()}, + goldenRow{"SetBytesShort", fmt.Sprintf("%x", short)}, + goldenRow{"SetBytesLong", fmt.Sprintf("%x", long)}, + goldenRow{"UnmarshalTextErr", errString(u.UnmarshalText([]byte("0x1234")))}, + goldenRow{"UnmarshalJSONErrType", jsonErrType(t, j.UnmarshalJSON([]byte("5")))}, + goldenRow{"Generate", fmt.Sprintf("%x", gen)}, + ) +} + +func bytes64Goldens(t *testing.T) []goldenRow { + t.Helper() + var b Bytes64 + b.SetBytes(goldenSeq(64)) + + var short, long, u, j Bytes64 + short.SetBytes([]byte{0xde, 0xad, 0xbe}) + long.SetBytes(goldenSeq(69)) + + return append(formatGoldens(t, b), + goldenRow{"Hex", b.Hex()}, + goldenRow{"TerminalString", b.TerminalString()}, + goldenRow{"SetBytesShort", fmt.Sprintf("%x", short)}, + goldenRow{"SetBytesLong", fmt.Sprintf("%x", long)}, + goldenRow{"UnmarshalTextErr", errString(u.UnmarshalText([]byte("0x1234")))}, + goldenRow{"UnmarshalJSONErrType", jsonErrType(t, j.UnmarshalJSON([]byte("5")))}, + ) +} + +func bytes96Goldens(t *testing.T) []goldenRow { + t.Helper() + var b Bytes96 + b.SetBytes(goldenSeq(96)) + + var short, long, u, j Bytes96 + short.SetBytes([]byte{0xde, 0xad, 0xbe}) + long.SetBytes(goldenSeq(101)) + gen := b.Generate(mrand.New(mrand.NewSource(7)), 10).Interface().(Bytes96) + + return append(formatGoldens(t, b), + goldenRow{"Hex", b.Hex()}, + goldenRow{"TerminalString", b.TerminalString()}, + goldenRow{"SetBytesShort", fmt.Sprintf("%x", short)}, + goldenRow{"SetBytesLong", fmt.Sprintf("%x", long)}, + goldenRow{"UnmarshalTextErr", errString(u.UnmarshalText([]byte("0x1234")))}, + goldenRow{"UnmarshalJSONErrType", jsonErrType(t, j.UnmarshalJSON([]byte("5")))}, + goldenRow{"Generate", fmt.Sprintf("%x", gen)}, + ) +} + +func hashGoldens(t *testing.T) []goldenRow { + t.Helper() + var h Hash + h.SetBytes(goldenSeq(32)) + + var short, long, u, j Hash + short.SetBytes([]byte{0xde, 0xad, 0xbe}) + long.SetBytes(goldenSeq(37)) + gen := h.Generate(randv2.New(randv2.NewPCG(7, 7)), 10).Interface().(Hash) + + return append(formatGoldens(t, h), + goldenRow{"Hex", h.Hex()}, + goldenRow{"TerminalString", h.TerminalString()}, + goldenRow{"SetBytesShort", fmt.Sprintf("%x", short)}, + goldenRow{"SetBytesLong", fmt.Sprintf("%x", long)}, + goldenRow{"UnmarshalTextErr", errString(u.UnmarshalText([]byte("0x1234")))}, + goldenRow{"UnmarshalJSONErrType", jsonErrType(t, j.UnmarshalJSON([]byte("5")))}, + goldenRow{"Generate", fmt.Sprintf("%x", gen)}, + ) +} + +func addressGoldens(t *testing.T) []goldenRow { + t.Helper() + var a Address + a.SetBytes(goldenSeq(20)) + + var short, long, u, j Address + short.SetBytes([]byte{0xde, 0xad, 0xbe}) + long.SetBytes(goldenSeq(25)) + + return append(formatGoldens(t, a), + goldenRow{"Hex", a.Hex()}, + goldenRow{"HexEIP55", HexToAddress("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed").Hex()}, + goldenRow{"SetBytesShort", fmt.Sprintf("%x", short)}, + goldenRow{"SetBytesLong", fmt.Sprintf("%x", long)}, + goldenRow{"UnmarshalTextErr", errString(u.UnmarshalText([]byte("0x1234")))}, + goldenRow{"UnmarshalJSONErrType", jsonErrType(t, j.UnmarshalJSON([]byte("5")))}, + ) +} diff --git a/common/hash.go b/common/hash.go index 45b124ba05c..794d904fd46 100644 --- a/common/hash.go +++ b/common/hash.go @@ -19,7 +19,6 @@ package common import ( "bytes" "database/sql/driver" - "encoding/hex" "fmt" "math/big" "math/rand/v2" @@ -71,9 +70,7 @@ func (h Hash) Hex() string { return hexutil.Encode(h[:]) } // TerminalString implements log.TerminalStringer, formatting a string for console // output during logging. -func (h Hash) TerminalString() string { - return fmt.Sprintf("%x…%x", h[:3], h[29:]) -} +func (h Hash) TerminalString() string { return fixedTerminalString(h[:]) } // String implements the stringer interface and is used also by the logger when // doing full logging into a file. @@ -83,33 +80,7 @@ func (h Hash) String() string { // Format implements fmt.Formatter. // Hash supports the %v, %s, %v, %x, %X and %d format verbs. -func (h Hash) Format(s fmt.State, c rune) { - hexb := make([]byte, 2+len(h)*2) - copy(hexb, "0x") - hex.Encode(hexb[2:], h[:]) - - switch c { - case 'x', 'X': - if !s.Flag('#') { - hexb = hexb[2:] - } - if c == 'X' { - hexb = bytes.ToUpper(hexb) - } - fallthrough - case 'v', 's': - s.Write(hexb) - case 'q': - q := []byte{'"'} - s.Write(q) - s.Write(hexb) - s.Write(q) - case 'd': - fmt.Fprint(s, ([len(h)]byte)(h)) - default: - fmt.Fprintf(s, "%%!%c(hash=%x)", c, h) - } -} +func (h Hash) Format(s fmt.State, c rune) { fixedFormat(s, c, "hash", h[:]) } // UnmarshalText parses a hash in hex syntax. func (h *Hash) UnmarshalText(input []byte) error { @@ -128,13 +99,7 @@ func (h Hash) MarshalText() ([]byte, error) { // SetBytes sets the hash to the value of b. // If b is larger than len(h), b will be cropped from the left. -func (h *Hash) SetBytes(b []byte) { - if len(b) > len(h) { - b = b[len(b)-length.Hash:] - } - - copy(h[length.Hash-len(b):], b) -} +func (h *Hash) SetBytes(b []byte) { fixedSetBytes(h[:], b) } // Generate implements testing/quick.Generator. func (h Hash) Generate(rand *rand.Rand, size int) reflect.Value { From cfbc58c369a31a241c8ac5c52ecaad08c12ef12c Mon Sep 17 00:00:00 2001 From: awskii Date: Fri, 3 Jul 2026 11:08:18 +0700 Subject: [PATCH 3/3] common: fold Hash.Generate into the shared helper, cover Hash/Address SetBytes fixedGenerate took a *math/rand.Rand, so Hash.Generate (math/rand/v2) kept its own byte-identical loop. Pass the intn/u32 primitives instead and all four types plus Hash share one body. TestBytesNSetBytes now also exercises Hash and Address. --- common/bytes4.go | 2 +- common/bytes48.go | 2 +- common/bytes96.go | 2 +- common/bytesn.go | 11 +++++++---- common/bytesn_test.go | 14 ++++++++++++++ common/hash.go | 5 +---- 6 files changed, 25 insertions(+), 11 deletions(-) diff --git a/common/bytes4.go b/common/bytes4.go index 5b2087b11e8..1f2041f28d4 100644 --- a/common/bytes4.go +++ b/common/bytes4.go @@ -62,7 +62,7 @@ func (b *Bytes4) SetBytes(i []byte) { fixedSetBytes(b[:], i) } // Generate implements testing/quick.Generator. func (b Bytes4) Generate(rand *rand.Rand, size int) reflect.Value { - fixedGenerate(rand, b[:]) + fixedGenerate(b[:], rand.Intn, rand.Uint32) return reflect.ValueOf(b) } diff --git a/common/bytes48.go b/common/bytes48.go index f688cbe7163..53a82126f2d 100644 --- a/common/bytes48.go +++ b/common/bytes48.go @@ -62,7 +62,7 @@ func (b *Bytes48) SetBytes(i []byte) { fixedSetBytes(b[:], i) } // Generate implements testing/quick.Generator. func (b Bytes48) Generate(rand *rand.Rand, size int) reflect.Value { - fixedGenerate(rand, b[:]) + fixedGenerate(b[:], rand.Intn, rand.Uint32) return reflect.ValueOf(b) } diff --git a/common/bytes96.go b/common/bytes96.go index f7606914988..73da73cee98 100644 --- a/common/bytes96.go +++ b/common/bytes96.go @@ -62,7 +62,7 @@ func (b *Bytes96) SetBytes(i []byte) { fixedSetBytes(b[:], i) } // Generate implements testing/quick.Generator. func (b Bytes96) Generate(rand *rand.Rand, size int) reflect.Value { - fixedGenerate(rand, b[:]) + fixedGenerate(b[:], rand.Intn, rand.Uint32) return reflect.ValueOf(b) } diff --git a/common/bytesn.go b/common/bytesn.go index 4b8884ea872..6ab360da38e 100644 --- a/common/bytesn.go +++ b/common/bytesn.go @@ -20,7 +20,6 @@ import ( "bytes" "encoding/hex" "fmt" - "math/rand" ) func fixedFormat(s fmt.State, c rune, typeName string, b []byte) { @@ -62,9 +61,13 @@ func fixedTerminalString(b []byte) string { return fmt.Sprintf("%x…%x", b[:3], b[len(b)-3:]) } -func fixedGenerate(rnd *rand.Rand, b []byte) { - m := rnd.Intn(len(b)) +// fixedGenerate fills a random-length tail of b with random bytes, the shared +// body of every fixed-size type's testing/quick Generator. intn and u32 are +// supplied by the caller so this works with both math/rand and math/rand/v2, +// whose Rand types differ only in Intn vs IntN. +func fixedGenerate(b []byte, intn func(int) int, u32 func() uint32) { + m := intn(len(b)) for i := len(b) - 1; i > m; i-- { - b[i] = byte(rnd.Uint32()) + b[i] = byte(u32()) } } diff --git a/common/bytesn_test.go b/common/bytesn_test.go index 96db82e9181..7e3ba10e59c 100644 --- a/common/bytesn_test.go +++ b/common/bytesn_test.go @@ -75,4 +75,18 @@ func TestBytesNSetBytes(t *testing.T) { check(t, b[:], seq(n)) } }) + t.Run("Hash", func(t *testing.T) { + for _, n := range []int{20, 32, 40} { + var h Hash + h.SetBytes(seq(n)) + check(t, h[:], seq(n)) + } + }) + t.Run("Address", func(t *testing.T) { + for _, n := range []int{12, 20, 28} { + var a Address + a.SetBytes(seq(n)) + check(t, a[:], seq(n)) + } + }) } diff --git a/common/hash.go b/common/hash.go index 794d904fd46..fc0b95ff530 100644 --- a/common/hash.go +++ b/common/hash.go @@ -103,10 +103,7 @@ func (h *Hash) SetBytes(b []byte) { fixedSetBytes(h[:], b) } // Generate implements testing/quick.Generator. func (h Hash) Generate(rand *rand.Rand, size int) reflect.Value { - m := rand.IntN(len(h)) - for i := len(h) - 1; i > m; i-- { - h[i] = byte(rand.Uint32()) - } + fixedGenerate(h[:], rand.IntN, rand.Uint32) return reflect.ValueOf(h) }