This commit is contained in:
Vincent Serpoul 2017-11-21 18:47:19 +00:00 committed by GitHub
commit ae6ac1da46
2 changed files with 318 additions and 20 deletions

View file

@ -17,6 +17,7 @@
package common package common
import ( import (
"database/sql/driver"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"math/big" "math/big"
@ -28,7 +29,9 @@ import (
) )
const ( const (
HashLength = 32 // HashLength represents the byte length of a Hash
HashLength = 32
// AddressLength represents the byte length of an ethereum address
AddressLength = 20 AddressLength = 20
) )
@ -40,20 +43,33 @@ var (
// Hash represents the 32 byte Keccak256 hash of arbitrary data. // Hash represents the 32 byte Keccak256 hash of arbitrary data.
type Hash [HashLength]byte type Hash [HashLength]byte
// BytesToHash will copy a byte slice to Hash
func BytesToHash(b []byte) Hash { func BytesToHash(b []byte) Hash {
var h Hash var h Hash
h.SetBytes(b) h.SetBytes(b)
return h return h
} }
func StringToHash(s string) Hash { return BytesToHash([]byte(s)) }
func BigToHash(b *big.Int) Hash { return BytesToHash(b.Bytes()) }
func HexToHash(s string) Hash { return BytesToHash(FromHex(s)) }
// Get the string representation of the underlying hash // StringToHash will copy a string to a hash
func (h Hash) Str() string { return string(h[:]) } func StringToHash(s string) Hash { return BytesToHash([]byte(s)) }
// BigToHash will copy a Big to a hash
func BigToHash(b *big.Int) Hash { return BytesToHash(b.Bytes()) }
// HexToHash will turn a hex string into a Hash
func HexToHash(s string) Hash { return BytesToHash(FromHex(s)) }
// Str gets the string representation of the underlying hash
func (h Hash) Str() string { return string(h[:]) }
// Bytes turns a hash into a byte slice
func (h Hash) Bytes() []byte { return h[:] } func (h Hash) Bytes() []byte { return h[:] }
// Big turns hash into a big.Int
func (h Hash) Big() *big.Int { return new(big.Int).SetBytes(h[:]) } func (h Hash) Big() *big.Int { return new(big.Int).SetBytes(h[:]) }
func (h Hash) Hex() string { return hexutil.Encode(h[:]) }
// Hex turn a hash into a hex string
func (h Hash) Hex() string { return hexutil.Encode(h[:]) }
// TerminalString implements log.TerminalStringer, formatting a string for console // TerminalString implements log.TerminalStringer, formatting a string for console
// output during logging. // output during logging.
@ -88,7 +104,7 @@ func (h Hash) MarshalText() ([]byte, error) {
return hexutil.Bytes(h[:]).MarshalText() return hexutil.Bytes(h[:]).MarshalText()
} }
// Sets the hash to the value of b. If b is larger than len(h), 'b' will be cropped (from the left). // 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) { func (h *Hash) SetBytes(b []byte) {
if len(b) > len(h) { if len(b) > len(h) {
b = b[len(b)-HashLength:] b = b[len(b)-HashLength:]
@ -97,10 +113,10 @@ func (h *Hash) SetBytes(b []byte) {
copy(h[HashLength-len(b):], b) copy(h[HashLength-len(b):], b)
} }
// Set string `s` to h. If s is larger than len(h) s will be cropped (from left) to fit. // SetString Set string `s` to h. If s is larger than len(h) s will be cropped (from left) to fit.
func (h *Hash) SetString(s string) { h.SetBytes([]byte(s)) } func (h *Hash) SetString(s string) { h.SetBytes([]byte(s)) }
// Sets h to other // Set Sets h to other
func (h *Hash) Set(other Hash) { func (h *Hash) Set(other Hash) {
for i, v := range other { for i, v := range other {
h[i] = v h[i] = v
@ -116,10 +132,38 @@ func (h Hash) Generate(rand *rand.Rand, size int) reflect.Value {
return reflect.ValueOf(h) return reflect.ValueOf(h)
} }
// EmptyHash returns if a hash is empty or not
func EmptyHash(h Hash) bool { func EmptyHash(h Hash) bool {
return h == Hash{} return h == Hash{}
} }
// Value converts the Hash into a SQL driver value which can be used to
// directly use the HASH as parameter to a SQL query.
func (h Hash) Value() (driver.Value, error) {
return h.Bytes(), nil
}
// Scan allows scanning from (byte slice) to *Hash
func (h *Hash) Scan(src interface{}) error {
switch v := src.(type) {
case []byte:
return h.scan(v)
case string:
return h.scan([]byte(v))
default:
return fmt.Errorf("Scan: unable to scan type %T into Hash", v)
}
}
func (h *Hash) scan(b []byte) error {
if len(b) != HashLength {
return fmt.Errorf("Scan: unable to scan into Hash, wrong size %d", len(b))
}
*h = BytesToHash(b)
return nil
}
// UnprefixedHash allows marshaling a Hash without 0x prefix. // UnprefixedHash allows marshaling a Hash without 0x prefix.
type UnprefixedHash Hash type UnprefixedHash Hash
@ -138,14 +182,21 @@ func (h UnprefixedHash) MarshalText() ([]byte, error) {
// Address represents the 20 byte address of an Ethereum account. // Address represents the 20 byte address of an Ethereum account.
type Address [AddressLength]byte type Address [AddressLength]byte
// BytesToAddress will returns an address from a byte slice
func BytesToAddress(b []byte) Address { func BytesToAddress(b []byte) Address {
var a Address var a Address
a.SetBytes(b) a.SetBytes(b)
return a return a
} }
// StringToAddress will turn a string into an address
func StringToAddress(s string) Address { return BytesToAddress([]byte(s)) } func StringToAddress(s string) Address { return BytesToAddress([]byte(s)) }
func BigToAddress(b *big.Int) Address { return BytesToAddress(b.Bytes()) }
func HexToAddress(s string) Address { return BytesToAddress(FromHex(s)) } // BigToAddress will turn a big.Int into an address
func BigToAddress(b *big.Int) Address { return BytesToAddress(b.Bytes()) }
// HexToAddress will turn an hex string into an address
func HexToAddress(s string) Address { return BytesToAddress(FromHex(s)) }
// IsHexAddress verifies whether a string can represent a valid hex-encoded // IsHexAddress verifies whether a string can represent a valid hex-encoded
// Ethereum address or not. // Ethereum address or not.
@ -159,17 +210,23 @@ func IsHexAddress(s string) bool {
return false return false
} }
// Get the string representation of the underlying address // Str gets the string representation of the underlying address
func (a Address) Str() string { return string(a[:]) } func (a Address) Str() string { return string(a[:]) }
// Bytes will return a byte slice
func (a Address) Bytes() []byte { return a[:] } func (a Address) Bytes() []byte { return a[:] }
// Big will return a *big.Int from an address
func (a Address) Big() *big.Int { return new(big.Int).SetBytes(a[:]) } func (a Address) Big() *big.Int { return new(big.Int).SetBytes(a[:]) }
func (a Address) Hash() Hash { return BytesToHash(a[:]) }
// Hash will return a hash from an address
func (a Address) Hash() Hash { return BytesToHash(a[:]) }
// Hex returns an EIP55-compliant hex string representation of the address. // Hex returns an EIP55-compliant hex string representation of the address.
func (a Address) Hex() string { func (a Address) Hex() string {
unchecksummed := hex.EncodeToString(a[:]) unchecksummed := hex.EncodeToString(a[:])
sha := sha3.NewKeccak256() sha := sha3.NewKeccak256()
sha.Write([]byte(unchecksummed)) _, _ = sha.Write([]byte(unchecksummed))
hash := sha.Sum(nil) hash := sha.Sum(nil)
result := []byte(unchecksummed) result := []byte(unchecksummed)
@ -198,7 +255,7 @@ func (a Address) Format(s fmt.State, c rune) {
fmt.Fprintf(s, "%"+string(c), a[:]) fmt.Fprintf(s, "%"+string(c), a[:])
} }
// Sets the address to the value of b. If b is larger than len(a) it will panic // SetBytes Sets the address to the value of b. If b is larger than len(a) it will panic
func (a *Address) SetBytes(b []byte) { func (a *Address) SetBytes(b []byte) {
if len(b) > len(a) { if len(b) > len(a) {
b = b[len(b)-AddressLength:] b = b[len(b)-AddressLength:]
@ -206,10 +263,10 @@ func (a *Address) SetBytes(b []byte) {
copy(a[AddressLength-len(b):], b) copy(a[AddressLength-len(b):], b)
} }
// Set string `s` to a. If s is larger than len(a) it will panic // SetString Set string `s` to a. If s is larger than len(a) it will panic
func (a *Address) SetString(s string) { a.SetBytes([]byte(s)) } func (a *Address) SetString(s string) { a.SetBytes([]byte(s)) }
// Sets a to other // Set Sets a to other
func (a *Address) Set(other Address) { func (a *Address) Set(other Address) {
for i, v := range other { for i, v := range other {
a[i] = v a[i] = v
@ -231,7 +288,34 @@ func (a *Address) UnmarshalJSON(input []byte) error {
return hexutil.UnmarshalFixedJSON(addressT, input, a[:]) return hexutil.UnmarshalFixedJSON(addressT, input, a[:])
} }
// UnprefixedHash allows marshaling an Address without 0x prefix. // Value converts the Address into a SQL driver value which can be used to
// directly use the Address as parameter to a SQL query.
func (a Address) Value() (driver.Value, error) {
return a.Bytes(), nil
}
// Scan allows scanning from (byte slice) to *Address
func (a *Address) Scan(src interface{}) error {
switch v := src.(type) {
case []byte:
return a.scan(v)
case string:
return a.scan([]byte(v))
default:
return fmt.Errorf("Scan: unable to scan type %T into Address", v)
}
}
func (a *Address) scan(b []byte) error {
if len(b) != AddressLength {
return fmt.Errorf("Scan: unable to scan into Address, wrong size %d", len(b))
}
*a = BytesToAddress(b)
return nil
}
// UnprefixedAddress allows marshaling an Address without 0x prefix.
type UnprefixedAddress Address type UnprefixedAddress Address
// UnmarshalText decodes the address from hex. The 0x prefix is optional. // UnmarshalText decodes the address from hex. The 0x prefix is optional.

View file

@ -125,3 +125,217 @@ func BenchmarkAddressHex(b *testing.B) {
testAddr.Hex() testAddr.Hex()
} }
} }
func TestHash_Scan(t *testing.T) {
tests := []struct {
name string
src interface{}
wantErr bool
}{
{
name: "working scan",
src: []byte{
0xb2, 0x6f, 0x2b, 0x34, 0x2a, 0xab, 0x24, 0xbc, 0xf6, 0x3e,
0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15,
0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15,
0x10, 0x00,
},
wantErr: false,
},
{
name: "non working scan, int",
src: int64(1234567890),
wantErr: true,
},
{
name: "non working scan, wrong size",
src: []byte{
0xb2, 0x6f, 0x2b, 0x34, 0x2a, 0xab, 0x24, 0xbc, 0xf6, 0x3e,
0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15,
0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15,
0x10,
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := &Hash{}
if err := h.Scan(tt.src); (err != nil) != tt.wantErr {
t.Errorf("Hash.Scan() error = %v, wantErr %v", err, tt.wantErr)
}
if !tt.wantErr {
for i := range h {
if h[i] != tt.src.([]byte)[i] {
t.Errorf(
"Hash.Scan() didn't scan the %d src correctly (have %X, want %X)",
i, h[i], tt.src.([]byte)[i],
)
}
}
}
})
}
}
func BenchmarkHash_Scan(b *testing.B) {
tst := []byte{
0xb2, 0x6f, 0x2b, 0x34, 0x2a, 0xab, 0x24, 0xbc, 0xf6, 0x3e,
0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15,
0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15,
0x10, 0x00,
}
for i := 0; i < b.N; i++ {
h := &Hash{}
_ = h.Scan(tst)
}
}
func TestHash_Value(t *testing.T) {
tests := []struct {
name string
h Hash
wantErr bool
}{
{
name: "Working hash",
h: Hash([HashLength]byte{
0xb2, 0x6f, 0x2b, 0x34, 0x2a, 0xab, 0x24, 0xbc, 0xf6, 0x3e,
0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15,
0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15,
0x10, 0x00,
}),
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.h.Value()
if (err != nil) != tt.wantErr {
t.Errorf("Hash.Value() error = %v, wantErr %v", err, tt.wantErr)
return
}
for i, gotI := range got.([]byte) {
if gotI != tt.h[i] {
t.Errorf("Hash.Value() non matching data")
return
}
}
})
}
}
func BenchmarkHash_Value(b *testing.B) {
h := Hash([HashLength]byte{
0xb2, 0x6f, 0x2b, 0x34, 0x2a, 0xab, 0x24, 0xbc, 0xf6, 0x3e,
0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15,
0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15,
0x10, 0x00,
})
for i := 0; i < b.N; i++ {
_, _ = h.Value()
}
}
func TestAddress_Scan(t *testing.T) {
tests := []struct {
name string
src interface{}
wantErr bool
}{
{
name: "working scan",
src: []byte{
0xb2, 0x6f, 0x2b, 0x34, 0x2a, 0xab, 0x24, 0xbc, 0xf6, 0x3e,
0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15,
},
wantErr: false,
},
{
name: "non working scan, int",
src: int64(1234567890),
wantErr: true,
},
{
name: "non working scan, wrong size",
src: []byte{
0xb2, 0x6f, 0x2b, 0x34, 0x2a, 0xab, 0x24, 0xbc, 0xf6, 0x3e,
0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a,
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := &Address{}
if err := a.Scan(tt.src); (err != nil) != tt.wantErr {
t.Errorf("Address.Scan() error = %v, wantErr %v", err, tt.wantErr)
}
if !tt.wantErr {
for i := range a {
if a[i] != tt.src.([]byte)[i] {
t.Errorf(
"Address.Scan() didn't scan the %d src correctly (have %X, want %X)",
i, a[i], tt.src.([]byte)[i],
)
}
}
}
})
}
}
func BenchmarkAddress_Scan(b *testing.B) {
tst := []byte{
0xb2, 0x6f, 0x2b, 0x34, 0x2a, 0xab, 0x24, 0xbc, 0xf6, 0x3e,
0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15,
}
for i := 0; i < b.N; i++ {
a := &Address{}
_ = a.Scan(tst)
}
}
func TestAddress_Value(t *testing.T) {
tests := []struct {
name string
a Address
wantErr bool
}{
{
name: "Working Address",
a: Address([AddressLength]byte{
0xb2, 0x6f, 0x2b, 0x34, 0x2a, 0xab, 0x24, 0xbc, 0xf6, 0x3e,
0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15,
}),
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.a.Value()
if (err != nil) != tt.wantErr {
t.Errorf("Address.Value() error = %v, wantErr %v", err, tt.wantErr)
return
}
for i, gotI := range got.([]byte) {
if gotI != tt.a[i] {
t.Errorf("Address.Value() non matching data")
return
}
}
})
}
}
func BenchmarkAddress_Value(b *testing.B) {
a := Address([AddressLength]byte{
0xb2, 0x6f, 0x2b, 0x34, 0x2a, 0xab, 0x24, 0xbc, 0xf6, 0x3e,
0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15,
})
for i := 0; i < b.N; i++ {
_, _ = a.Value()
}
}