common: improve printing of Hash and Address (#21834)
Both Hash and Address have a String method, which returns the value as hex with 0x prefix. They also had a Format method which tried to print the value using printf of []byte. The way Format worked was at odds with String though, leading to a situation where fmt.Sprintf("%v", hash) returned the decimal notation and hash.String() returned a hex string. This commit makes it consistent again. Both types now support the %v, %s, %q format verbs for 0x-prefixed hex output. %x, %X creates unprefixed hex output. %d is also supported and returns the decimal notation "[1 2 3...]". For Address, the case of hex characters in %v, %s, %q output is determined using the EIP-55 checksum. Using %x, %X with Address disables checksumming. Co-authored-by: Felix Lange <fjl@twurst.com>
This commit is contained in:
106
common/types.go
106
common/types.go
@ -17,6 +17,7 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql/driver"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
@ -84,10 +85,34 @@ func (h Hash) String() string {
|
||||
return h.Hex()
|
||||
}
|
||||
|
||||
// Format implements fmt.Formatter, forcing the byte slice to be formatted as is,
|
||||
// without going through the stringer interface used for logging.
|
||||
// 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) {
|
||||
fmt.Fprintf(s, "%"+string(c), h[:])
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalText parses a hash in hex syntax.
|
||||
@ -208,24 +233,7 @@ func (a Address) Hash() Hash { return BytesToHash(a[:]) }
|
||||
|
||||
// Hex returns an EIP55-compliant hex string representation of the address.
|
||||
func (a Address) Hex() string {
|
||||
unchecksummed := hex.EncodeToString(a[:])
|
||||
sha := sha3.NewLegacyKeccak256()
|
||||
sha.Write([]byte(unchecksummed))
|
||||
hash := sha.Sum(nil)
|
||||
|
||||
result := []byte(unchecksummed)
|
||||
for i := 0; i < len(result); i++ {
|
||||
hashByte := hash[i/2]
|
||||
if i%2 == 0 {
|
||||
hashByte = hashByte >> 4
|
||||
} else {
|
||||
hashByte &= 0xf
|
||||
}
|
||||
if result[i] > '9' && hashByte > 7 {
|
||||
result[i] -= 32
|
||||
}
|
||||
}
|
||||
return "0x" + string(result)
|
||||
return string(a.checksumHex())
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
@ -233,10 +241,60 @@ func (a Address) String() string {
|
||||
return a.Hex()
|
||||
}
|
||||
|
||||
// Format implements fmt.Formatter, forcing the byte slice to be formatted as is,
|
||||
// without going through the stringer interface used for logging.
|
||||
func (a *Address) checksumHex() []byte {
|
||||
buf := a.hex()
|
||||
|
||||
// compute checksum
|
||||
sha := sha3.NewLegacyKeccak256()
|
||||
sha.Write(buf[2:])
|
||||
hash := sha.Sum(nil)
|
||||
for i := 2; i < len(buf); i++ {
|
||||
hashByte := hash[(i-2)/2]
|
||||
if i%2 == 0 {
|
||||
hashByte = hashByte >> 4
|
||||
} else {
|
||||
hashByte &= 0xf
|
||||
}
|
||||
if buf[i] > '9' && hashByte > 7 {
|
||||
buf[i] -= 32
|
||||
}
|
||||
}
|
||||
return buf[:]
|
||||
}
|
||||
|
||||
func (a Address) hex() []byte {
|
||||
var buf [len(a)*2 + 2]byte
|
||||
copy(buf[:2], "0x")
|
||||
hex.Encode(buf[2:], a[:])
|
||||
return buf[:]
|
||||
}
|
||||
|
||||
// Format implements fmt.Formatter.
|
||||
// Address supports the %v, %s, %v, %x, %X and %d format verbs.
|
||||
func (a Address) Format(s fmt.State, c rune) {
|
||||
fmt.Fprintf(s, "%"+string(c), a[:])
|
||||
switch c {
|
||||
case 'v', 's':
|
||||
s.Write(a.checksumHex())
|
||||
case 'q':
|
||||
q := []byte{'"'}
|
||||
s.Write(q)
|
||||
s.Write(a.checksumHex())
|
||||
s.Write(q)
|
||||
case 'x', 'X':
|
||||
// %x disables the checksum.
|
||||
hex := a.hex()
|
||||
if !s.Flag('#') {
|
||||
hex = hex[2:]
|
||||
}
|
||||
if c == 'X' {
|
||||
hex = bytes.ToUpper(hex)
|
||||
}
|
||||
s.Write(hex)
|
||||
case 'd':
|
||||
fmt.Fprint(s, ([len(a)]byte)(a))
|
||||
default:
|
||||
fmt.Fprintf(s, "%%!%c(address=%x)", c, a)
|
||||
}
|
||||
}
|
||||
|
||||
// SetBytes sets the address to the value of b.
|
||||
|
Reference in New Issue
Block a user