Skip to content

Commit

Permalink
Merge pull request #4 from Cleverse/feature/package-address
Browse files Browse the repository at this point in the history
add Ethereum Address utilitues package
  • Loading branch information
Planxnx authored Oct 23, 2023
2 parents 63cf4ad + 09e641f commit 5e150be
Show file tree
Hide file tree
Showing 9 changed files with 549 additions and 0 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,9 @@ Optimized common generic utilities for Cleverse Golang projects.
Package errors adds stacktrace support to errors in go.

[See here](errors/README.md).

## address

[go-ethereum](https://github.com/ethereum/go-ethereum) address utilities package.

[See here](address/README.md).
12 changes: 12 additions & 0 deletions address/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[![GoDoc](https://godoc.org/github.com/Cleverse/go-utilities/address?status.svg)](http://godoc.org/github.com/Cleverse/go-utilities/address)
[![Report card](https://goreportcard.com/badge/github.com/Cleverse/go-utilities/address)](https://goreportcard.com/report/github.com/Cleverse/go-utilities/address)

# address

[go-ethereum](https://github.com/ethereum/go-ethereum) address utilities package.

## Installation

```shell
go get github.com/Cleverse/go-utilities/address
```
138 changes: 138 additions & 0 deletions address/address.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package address

import (
"crypto/rand"
"encoding/hex"
"strings"

"github.com/Cleverse/go-utilities/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)

const (
// AddressLength is the expected length of the address
AddressLength = common.AddressLength
)

var (
// Ether is the default address of the Ethereum's native currency.
Ether = common.HexToAddress("0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE")

// Zero is zero value of common.Address.
Zero = common.Address{}

// DeadWalletAddress is the defailt address of the dead wallet.
DeadWalletAddress = common.HexToAddress("0x000000000000000000000000000000000000dead")

// DeadAddresseses is the list of dead addresses.
DeadAddresseses = map[common.Address]interface{}{
Zero: nil, // 0x0000000000000000000000000000000000000000
DeadWalletAddress: nil, // 0x000000000000000000000000000000000000dead
common.HexToAddress("0x0000000000000000000000000000000000000001"): nil, // Safemoon, GALA, AWC, NOW
common.HexToAddress("0xdEAD000000000000000042069420694206942069"): nil, // SHIB, BOHR, HEGIC, etc in Ethereum
common.HexToAddress("0xdead000000000000000000000000000000000000"): nil,
}

// RandomGenerator is the address random generator function and used by address.Random() function.
//
// Default address random generator is cryptographically secure random number generator.
RandomGenerator func() (addr common.Address) = RandomFromBytes
)

// FromHex safely converts a hex string to a common.Address.
func FromHex(address string) (addr common.Address) {
address = strings.TrimSpace(address)
if len(address) == 0 {
return Zero
}
return common.HexToAddress(address)
}

// FromString safely converts a string to a common.Address.
func FromString(address string) (addr common.Address) {
return FromHex(address)
}

// FromHexes safely converts hex string slice to a common.Address slice.
func FromHexes(src []string) (dst []common.Address) {
dst = make([]common.Address, 0, len(src))
for _, addr := range src {
dst = append(dst, FromHex(addr))
}
return dst
}

// FromStrings alias of FromHexes. safely converts string slice to a common.Address slice.
func FromStrings(src []string) (dst []common.Address) {
return FromHexes(src)
}

// ToLower converts an address to a lower-case string withouth checksum.
func ToLower(a common.Address) string {
buf := [common.AddressLength*2 + 2]byte{}
copy(buf[:2], []byte("0x"))
_ = hex.Encode(buf[2:], a[:])
return string(buf[:])
}

// ToString alias of ToLower. converts an address to a lower-case string withouth checksum.
func ToString(a common.Address) string {
return ToLower(a)
}

// ToLowers converts an addresses to a lower-case strings withouth checksum.
func ToLowers(src []common.Address) []string {
dst := make([]string, 0, len(src))
for _, addr := range src {
dst = append(dst, ToLower(addr))
}
return dst
}

// ToStrings alias of ToLowers. converts an addresses to a lower-case strings withouth checksum.
func ToStrings(src []common.Address) []string {
return ToLowers(src)
}

// IsZero returns `true` if the address is zero value.
func IsZero(a common.Address) bool {
return a == Zero
}

// IsEmpty returns `true` if the address is empty (alias of IsZero)
func IsEmpty(a common.Address) bool {
return a == Zero
}

// IsDead returns `true` if the address is dead address.
func IsDead(a common.Address) bool {
_, ok := DeadAddresseses[a]
return ok
}

// IsZero returns `true` if the address is can't be used. (zero value or dead address)
func IsValid(a common.Address) bool {
return !IsZero(a) && !IsDead(a)
}

// Random returns a random common.Address. can be changed address random generator by RandomGenerator variable.
//
// Default address random generator is use cryptographically secure random number generator.
func Random() (addr common.Address) {
if RandomGenerator == nil {
RandomGenerator = RandomFromBytes
}
return RandomGenerator()
}

// RandomFromPrivateKey returns a random address from a random private key
func RandomFromPrivateKey() common.Address {
return crypto.PubkeyToAddress(utils.Must(crypto.GenerateKey()).PublicKey)
}

// RandomFromBytes returns a random address from a random byte slice (via crypto/rand)
func RandomFromBytes() (addr common.Address) {
_, _ = rand.Read(addr[:])
return addr
}
187 changes: 187 additions & 0 deletions address/address_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package address

import (
"crypto/rand"
"encoding/hex"
"fmt"
"strings"
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/assert"
)

func BenchmarkRandomAddress(b *testing.B) {
b.Run("rand/privatekey", func(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = RandomFromPrivateKey()
b.SetBytes(AddressLength)
}
})
b.Run("crypto/rand", func(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
buf := make([]byte, AddressLength)
_, _ = rand.Read(buf)
_ = common.HexToAddress(hex.EncodeToString(buf))
b.SetBytes(AddressLength)
}
})
b.Run("crypto/rand /w optimize", func(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = RandomFromBytes()
b.SetBytes(AddressLength)
}
})
}

func BenchmarkToLower(b *testing.B) {
address := common.HexToAddress("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
var sizeAddressString int64 = 42
toStrings := map[string]func(common.Address) string{
"Strings.ToLower": func(a common.Address) string {
return strings.ToLower(a.String())
},
"Old": func(a common.Address) string {
hexEncoding := hex.EncodeToString(a.Bytes())
return fmt.Sprintf("0x%s", hexEncoding)
},
"StringBuilder_1": func(a common.Address) string {
var s strings.Builder
s.WriteString("0x")
s.WriteString(hex.EncodeToString(a.Bytes()))
return s.String()
},
"StringBuilder_2": func(a common.Address) string {
var s strings.Builder
b := make([]byte, 40)
_ = hex.Encode(b, a.Bytes()) // Avoid conversion between []byte and string to reduce memory allocation.
s.WriteString("0x")
s.Write(b)
return s.String()
},
"StringBuilder_3": func(a common.Address) string {
var s strings.Builder
b := make([]byte, 40)
_ = hex.Encode(b, a.Bytes()) // Avoid conversion between []byte and string to reduce memory allocation.
s.Write([]byte{48, 120}) // use Write instead of WriteString
s.Write(b)
return s.String()
},
"Now": ToLower,
}
ordered := []string{"Strings.ToLower", "Old", "StringBuilder_1", "StringBuilder_2", "StringBuilder_3", "Now"}
for _, name := range ordered {
toString := toStrings[name]
b.Run(name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = toString(address)
b.SetBytes(sizeAddressString)
}
})
}
/*
goos: darwin
goarch: arm64
BenchmarkToLower/Strings.ToLower-8 1529884 808.3 ns/op 51.96 MB/s 1104 B/op 7 allocs/op
BenchmarkToLower/Old-8 10714300 104.1 ns/op 403.51 MB/s 160 B/op 4 allocs/op
BenchmarkToLower/StringBuilder_1-8 14235339 88.43 ns/op 474.93 MB/s 152 B/op 4 allocs/op
BenchmarkToLower/StringBuilder_2-8 24213360 49.09 ns/op 855.51 MB/s 56 B/op 2 allocs/op
BenchmarkToLower/StringBuilder_3-8 23675196 49.30 ns/op 851.92 MB/s 56 B/op 2 allocs/op
BenchmarkToLower/Now-8 39285494 30.81 ns/op 1363.39 MB/s 48 B/op 1 allocs/op
*/
}

func BenchmarkToString(b *testing.B) {
address := common.HexToAddress("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
var sizeAddressString int64 = 42

toStrings := map[string]func(common.Address) string{
"Address.String()": func(a common.Address) string {
return a.String()
},
"Old": func(a common.Address) string {
hexEncoding := hex.EncodeToString(a.Bytes())
return fmt.Sprintf("0x%s", hexEncoding)
},
"StringBuilder": func(a common.Address) string {
var s strings.Builder
s.WriteString("0x")
s.WriteString(hex.EncodeToString(a.Bytes()))
return s.String()
},
"Now": ToString,
}

for name, toString := range toStrings {
b.Run(name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = toString(address)
b.SetBytes(sizeAddressString)
}
})
}
/*
goos: darwin
goarch: arm64
BenchmarkToString/Address.String()-8 1906382 632.1 ns/op 66.44 MB/s 1056 B/op 6 allocs/op
BenchmarkToString/Old-8 10145156 103.6 ns/op 405.57 MB/s 160 B/op 4 allocs/op
BenchmarkToString/StringBuilder-8 14861654 81.47 ns/op 515.50 MB/s 152 B/op 4 allocs/op
BenchmarkToString/Now-8 39218355 30.88 ns/op 1359.94 MB/s 48 B/op 1 allocs/op
*/
}

func TestToLower(t *testing.T) {
address := "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
expected := strings.ToLower(address)
actual := ToLower(common.HexToAddress(expected))
assert.Equal(t, expected, actual)
}

func TestToLowers(t *testing.T) {
addresses := []common.Address{
common.HexToAddress("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"),
common.HexToAddress("0xABCDEFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
common.HexToAddress("0xdEAD000000000000000042069420694206942069"),
}

actuals := ToLowers(addresses)
for i, actual := range actuals {
assert.Equal(t, strings.ToLower(addresses[i].String()), actual)
}
}

func TestToString(t *testing.T) {
expected := "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
actual := ToString(common.HexToAddress(expected))
assert.Equal(t, strings.ToLower(expected), actual)
}

func TestToStrings(t *testing.T) {
addresses := []common.Address{
common.HexToAddress("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"),
common.HexToAddress("0xABCDEFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
common.HexToAddress("0xdEAD000000000000000042069420694206942069"),
}

actuals := ToStrings(addresses)
for i, actual := range actuals {
assert.Equal(t, strings.ToLower(addresses[i].String()), actual)
}
}

func TestFromStrings(t *testing.T) {
expected := []common.Address{
common.HexToAddress("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"),
common.HexToAddress("0xABCDEFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
common.HexToAddress("0xdEAD000000000000000042069420694206942069"),
}
expectedString := ToStrings(expected)

actuals := FromStrings(expectedString)
for i, actual := range actuals {
assert.Equal(t, expected[i], actual)
}
}
22 changes: 22 additions & 0 deletions address/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module github.com/Cleverse/go-utilities/address

go 1.21

require (
github.com/Cleverse/go-utilities/utils v0.0.0-20231023121154-63cf4ad4e8a2
github.com/ethereum/go-ethereum v1.13.4
github.com/stretchr/testify v1.8.4
)

require (
github.com/Cleverse/go-utilities/errors v0.0.0-20231019072721-442842e3dc09 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/holiman/uint256 v1.2.3 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/sys v0.13.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading

0 comments on commit 5e150be

Please sign in to comment.