replace web3-utils methods usage by status-go (#2840)

This commit is contained in:
frank 2022-10-18 21:36:54 +08:00 committed by GitHub
parent 69e84b5673
commit 94fea4725d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 545 additions and 2 deletions

View File

@ -1 +1 @@
0.111.4
0.111.5

View File

@ -74,7 +74,7 @@ func TestEncode(t *testing.T) {
require.NoError(t, err)
require.Equal(t, "0xb3de648bffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", result)
//TODO following case would fail cause go-ethereum does not support type uint
//TODO following case would fail cause go-ethereum does not support type uint, should we fix this?
//result, err = Encode("g(uint[][],string[])", `[[[1,2],[3]],["one","two","three"]]`)
//require.NoError(t, err)
//require.Equal(t, "0xad6a3446000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000036f6e650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000374776f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000057468726565000000000000000000000000000000000000000000000000000000", result)

255
abi-spec/utf8.go Normal file
View File

@ -0,0 +1,255 @@
package abispec
import (
"fmt"
"unicode/utf8"
"github.com/ethereum/go-ethereum/common/hexutil"
)
func stringToRunes(str string) []rune {
var runes []rune
bytes := []byte(str)
for len(bytes) > 0 {
r, size := utf8.DecodeRune(bytes)
if r == utf8.RuneError {
for i := 0; i < size; i++ {
runes = append(runes, rune(bytes[i]))
}
} else {
runes = append(runes, r)
}
bytes = bytes[size:]
}
return runes
}
// Taken from https://mths.be/punycode
func ucs2decode(str string) []rune {
var runes = stringToRunes(str)
var output []rune
var counter = 0
var length = len(runes)
var value rune
var extra rune
for counter < length {
value = runes[counter]
counter++
if value >= 0xD800 && value <= 0xDBFF && counter < length {
// high surrogate, and there is a next character
extra = runes[counter]
counter++
if (extra & 0xFC00) == 0xDC00 { // low surrogate
output = append(output, ((value&0x3FF)<<10)+(extra&0x3FF)+0x10000)
} else {
// unmatched surrogate; only append this code unit, in case the next
// code unit is the high surrogate of a surrogate pair
output = append(output, value)
counter--
}
} else {
output = append(output, value)
}
}
return output
}
// Taken from https://mths.be/punycode
func ucs2encode(array []rune) []byte {
var length = len(array)
var index = 0
var value rune
var output []byte
for index < length {
value = array[index]
if value > 0xFFFF {
value -= 0x10000
codePoint := rune(uint32(value)>>10&0x3FF | 0xD800)
output = appendBytes(output, stringFromCharCode(codePoint))
value = 0xDC00 | value&0x3FF
}
output = appendBytes(output, stringFromCharCode(value))
index++
}
return output
}
func appendBytes(dest []byte, bytes []byte) []byte {
for i := 0; i < len(bytes); i++ {
dest = append(dest, bytes[i])
}
return dest
}
func checkScalarValue(codePoint rune) error {
if codePoint >= 0xD800 && codePoint <= 0xDFFF {
return fmt.Errorf("lone surrogate U+%s is not a scalar value", hexutil.EncodeUint64(uint64(codePoint)))
}
return nil
}
func stringFromCharCode(codePoint rune) []byte {
var buf = make([]byte, 4)
n := utf8.EncodeRune(buf, codePoint)
return buf[0:n]
}
func createByte(codePoint rune, shift uint32) []byte {
return stringFromCharCode(((codePoint >> shift) & 0x3F) | 0x80)
}
func encodeCodePoint(codePoint rune) ([]byte, error) {
if (uint32(codePoint) & uint32(0xFFFFFF80)) == 0 { // 1-byte sequence
return stringFromCharCode(codePoint), nil
}
var symbol []byte
if uint32(codePoint)&0xFFFFF800 == 0 { // 2-byte sequence
symbol = stringFromCharCode(((codePoint >> 6) & 0x1F) | 0xC0)
} else if (uint32(codePoint) & 0xFFFF0000) == 0 { // 3-byte sequence
err := checkScalarValue(codePoint)
if err != nil {
return nil, err
}
symbol = stringFromCharCode(((codePoint >> 12) & 0x0F) | 0xE0)
symbol = appendBytes(symbol, createByte(codePoint, 6))
} else if (uint32(codePoint) & 0xFFE00000) == 0 { // 4-byte sequence
symbol = stringFromCharCode(((codePoint >> 18) & 0x07) | 0xF0)
symbol = appendBytes(symbol, createByte(codePoint, 12))
symbol = appendBytes(symbol, createByte(codePoint, 6))
}
symbol = appendBytes(symbol, stringFromCharCode((codePoint&0x3F)|0x80))
return symbol, nil
}
// implementation referenced from https://github.com/mathiasbynens/utf8.js/blob/2ce09544b62f2a274dbcd249473c0986e3660849/utf8.js
func Utf8encode(str string) (string, error) {
var codePoints = ucs2decode(str)
var length = len(codePoints)
var index = 0
var codePoint rune
var bytes []byte
for index < length {
codePoint = codePoints[index]
cps, err := encodeCodePoint(codePoint)
if err != nil {
return "", err
}
bytes = appendBytes(bytes, cps)
index++
}
return string(bytes), nil
}
func readContinuationByte(byteArray []rune, byteCount int, pByteIndex *int) (rune, error) {
if *pByteIndex >= byteCount {
return utf8.RuneError, fmt.Errorf("invalid byte index")
}
var continuationByte = byteArray[*pByteIndex] & 0xFF
*pByteIndex = *pByteIndex + 1
if (continuationByte & 0xC0) == 0x80 {
return continuationByte & 0x3F, nil
}
// If we end up here, its not a continuation byte
return utf8.RuneError, fmt.Errorf("invalid continuation byte")
}
func decodeSymbol(byteArray []rune, byteCount int, pByteIndex *int) (rune, bool, error) {
var byte1 rune
var codePoint rune
if *pByteIndex > byteCount {
return utf8.RuneError, false, fmt.Errorf("invalid byte index")
}
if *pByteIndex == byteCount {
return utf8.RuneError, false, nil
}
// Read first byte
byte1 = byteArray[*pByteIndex] & 0xFF
*pByteIndex = *pByteIndex + 1
// 1-byte sequence (no continuation bytes)
if (byte1 & 0x80) == 0 {
return byte1, true, nil
}
// 2-byte sequence
if (byte1 & 0xE0) == 0xC0 {
byte2, err := readContinuationByte(byteArray, byteCount, pByteIndex)
if err != nil {
return utf8.RuneError, false, err
}
codePoint = ((byte1 & 0x1F) << 6) | byte2
if codePoint >= 0x80 {
return codePoint, true, nil
}
return utf8.RuneError, false, fmt.Errorf("invalid continuation byte")
}
// 3-byte sequence (may include unpaired surrogates)
if (byte1 & 0xF0) == 0xE0 {
byte2, err := readContinuationByte(byteArray, byteCount, pByteIndex)
if err != nil {
return utf8.RuneError, false, err
}
byte3, err := readContinuationByte(byteArray, byteCount, pByteIndex)
if err != nil {
return utf8.RuneError, false, err
}
codePoint = ((byte1 & 0x0F) << 12) | (byte2 << 6) | byte3
if codePoint >= 0x0800 {
err := checkScalarValue(codePoint)
if err != nil {
return utf8.RuneError, false, err
}
return codePoint, true, nil
}
return utf8.RuneError, false, fmt.Errorf("invalid continuation byte")
}
// 4-byte sequence
if (byte1 & 0xF8) == 0xF0 {
byte2, err := readContinuationByte(byteArray, byteCount, pByteIndex)
if err != nil {
return utf8.RuneError, false, err
}
byte3, err := readContinuationByte(byteArray, byteCount, pByteIndex)
if err != nil {
return utf8.RuneError, false, err
}
byte4, err := readContinuationByte(byteArray, byteCount, pByteIndex)
if err != nil {
return utf8.RuneError, false, err
}
codePoint = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0C) |
(byte3 << 0x06) | byte4
if codePoint >= 0x010000 && codePoint <= 0x10FFFF {
return codePoint, true, nil
}
}
return utf8.RuneError, false, fmt.Errorf("invalid UTF-8 detected")
}
// implementation referenced from https://github.com/mathiasbynens/utf8.js/blob/2ce09544b62f2a274dbcd249473c0986e3660849/utf8.js
func Utf8decode(str string) ([]byte, error) {
byteArray := ucs2decode(str)
byteCount := len(byteArray)
byteIndex := 0
var codePoints []rune
for true {
codePoint, goOn, err := decodeSymbol(byteArray, byteCount, &byteIndex)
if err != nil {
return nil, err
}
if !goOn {
break
}
codePoints = append(codePoints, codePoint)
}
return ucs2encode(codePoints), nil
}

View File

@ -3,8 +3,23 @@ package abispec
import (
"fmt"
"math/big"
"regexp"
"strings"
"unicode"
"github.com/ethereum/go-ethereum/common"
"github.com/status-im/status-go/eth-node/crypto"
)
var unicodeZeroPattern = regexp.MustCompile("^(?:\u0000)*")
var hexZeroPattern = regexp.MustCompile("^(?:00)*")
var hexStringPattern = regexp.MustCompile("(?i)^[0-9a-f]+$")
var hexPrefixPattern = regexp.MustCompile("(?i)^0x")
var addressBasicPattern = regexp.MustCompile("(?i)^(0x)?[0-9a-f]{40}$")
var addressLowerCasePattern = regexp.MustCompile("^(0x|0X)?[0-9a-f]{40}$")
var addressUpperCasePattern = regexp.MustCompile("^(0x|0X)?[0-9A-F]{40}$")
func HexToNumber(hex string) string {
num, success := big.NewInt(0).SetString(hex, 16)
if success {
@ -20,3 +35,158 @@ func NumberToHex(numString string) string {
}
return ""
}
func Sha3(str string) string {
bytes := crypto.Keccak256([]byte(str))
return common.Bytes2Hex(bytes)
}
func reverse(str string) string {
bytes := []byte(str)
var out []byte
for i := len(bytes) - 1; i >= 0; i-- {
out = append(out, bytes[i])
}
return string(out)
}
// remove \u0000 padding from either side
func removeUnicodeZeroPadding(str string) string {
found := unicodeZeroPattern.FindString(str)
str = strings.Replace(str, found, "", 1)
str = reverse(str)
found = unicodeZeroPattern.FindString(str)
str = strings.Replace(str, found, "", 1)
return reverse(str)
}
// remove 00 padding from either side
func removeHexZeroPadding(str string) string {
found := hexZeroPattern.FindString(str)
str = strings.Replace(str, found, "", 1)
str = reverse(str)
found = hexZeroPattern.FindString(str)
str = strings.Replace(str, found, "", 1)
return reverse(str)
}
// implementation referenced from https://github.com/ChainSafe/web3.js/blob/edcd215bf657a4bba62fabaafd08e6e70040976e/packages/web3-utils/src/utils.js#L165
func Utf8ToHex(str string) (string, error) {
str, err := Utf8encode(str)
if err != nil {
return "", err
}
str = removeUnicodeZeroPadding(str)
var hex = ""
for _, r := range str {
n := fmt.Sprintf("%x", r)
if len(n) < 2 {
hex += "0" + n
} else {
hex += n
}
}
return "0x" + hex, nil
}
// implementation referenced from https://github.com/ChainSafe/web3.js/blob/edcd215bf657a4bba62fabaafd08e6e70040976e/packages/web3-utils/src/utils.js#L193
func HexToUtf8(hexString string) (string, error) {
hexString = removeHexPrefix(hexString)
if !hexStringPattern.MatchString(hexString) {
return "", fmt.Errorf("the parameter '%s' must be a valid HEX string", hexString)
}
if len(hexString)%2 != 0 {
return "", fmt.Errorf("the parameter '%s' must have a even number of digits", hexString)
}
hexString = removeHexZeroPadding(hexString)
n := big.NewInt(0)
var bytes []byte
for i := 0; i < len(hexString); i += 2 {
hex := hexString[i : i+2]
n, success := n.SetString(hex, 16)
if !success {
return "", fmt.Errorf("invalid hex value %s", hex)
}
r := rune(n.Int64())
bs := stringFromCharCode(r)
bytes = appendBytes(bytes, bs)
}
bytes, err := Utf8decode(string(bytes))
if err != nil {
return "", err
}
return string(bytes), nil
}
func removeHexPrefix(str string) string {
found := hexPrefixPattern.FindString(str)
return strings.Replace(str, found, "", 1)
}
// implementation referenced from https://github.com/ChainSafe/web3.js/blob/edcd215bf657a4bba62fabaafd08e6e70040976e/packages/web3-utils/src/utils.js#L107
func CheckAddressChecksum(address string) (bool, error) {
address = removeHexPrefix(address)
addressHash := Sha3(strings.ToLower(address))
n := big.NewInt(0)
for i := 0; i < 40; i++ {
// the nth letter should be uppercase if the nth digit of casemap is 1
n, success := n.SetString(addressHash[i:i+1], 16)
if !success {
return false, fmt.Errorf("failed to convert hex value '%s' to int", addressHash[i:i+1])
}
v := n.Int64()
if (v > 7 && uint8(unicode.ToUpper(rune(address[i]))) != address[i]) || (v <= 7 && uint8(unicode.ToLower(rune(address[i]))) != address[i]) {
return false, nil
}
}
return true, nil
}
// implementation referenced from https://github.com/ChainSafe/web3.js/blob/edcd215bf657a4bba62fabaafd08e6e70040976e/packages/web3-utils/src/utils.js#L85
func IsAddress(address string) (bool, error) {
// check if it has the basic requirements of an address
if !addressBasicPattern.MatchString(address) {
return false, nil
} else if addressLowerCasePattern.MatchString(address) || addressUpperCasePattern.MatchString(address) {
return true, nil
} else {
return CheckAddressChecksum(address)
}
}
// implementation referenced from https://github.com/ChainSafe/web3.js/blob/2022b17d52d31ce95559d18d5530d18c83eb4d1c/packages/web3-utils/src/index.js#L283
func ToChecksumAddress(address string) (string, error) {
if strings.Trim(address, "") == "" {
return "", nil
}
if !addressBasicPattern.MatchString(address) {
return "", fmt.Errorf("given address '%s' is not a valid Ethereum address", address)
}
address = strings.ToLower(address)
address = removeHexPrefix(address)
addressHash := Sha3(address)
var checksumAddress = []rune("0x")
var n = big.NewInt(0)
for i := 0; i < len(address); i++ {
// If ith character is 9 to f then make it uppercase
n, success := n.SetString(addressHash[i:i+1], 16)
if !success {
return "", fmt.Errorf("failed to convert hex value '%s' to int", addressHash[i:i+1])
}
if n.Int64() > 7 {
upper := unicode.ToUpper(rune(address[i]))
checksumAddress = append(checksumAddress, upper)
} else {
checksumAddress = append(checksumAddress, rune(address[i]))
}
}
return string(checksumAddress), nil
}

View File

@ -33,3 +33,75 @@ func TestHexToNumber(t *testing.T) {
func TestNumberToHex(t *testing.T) {
require.Equal(t, "20000000000002", NumberToHex("9007199254740994"))
}
func TestSha3(t *testing.T) {
require.Equal(t, "48bed44d1bcd124a28c27f343a817e5f5243190d3c52bf347daf876de1dbbf77", Sha3("abcd"))
}
func TestHexToUtf8(t *testing.T) {
str, err := HexToUtf8("0x49206861766520313030e282ac")
require.NoError(t, err)
require.Equal(t, "I have 100€", str)
str, err = HexToUtf8("0xe4bda0e5a5bd")
require.NoError(t, err)
require.Equal(t, "你好", str)
_, err = HexToUtf8("0xfffefd")
require.ErrorContains(t, err, "invalid UTF-8 detected")
}
func TestUtf8ToHex(t *testing.T) {
str, err := Utf8ToHex("\xff")
require.NoError(t, err)
require.Equal(t, "0xc3bf", str)
str, err = Utf8ToHex("你好")
require.NoError(t, err)
require.Equal(t, "0xe4bda0e5a5bd", str)
}
const (
address1 = "0x0eD343df16A5327aC13B689072804A2705a26F47"
address2 = "0x0eD343df16A5327aC13B689072804A2705a26F47a"
address3 = "0x0eD343df16A5327aC13B689072804A2705a26f47"
)
func TestIsAddress(t *testing.T) {
valid, err := IsAddress(address1)
require.NoError(t, err)
require.True(t, valid)
valid, err = IsAddress(address2)
require.NoError(t, err)
require.False(t, valid)
valid, err = IsAddress(address3)
require.NoError(t, err)
require.False(t, valid)
}
func TestCheckAddressChecksum(t *testing.T) {
valid, err := CheckAddressChecksum(address1)
require.NoError(t, err)
require.True(t, valid)
valid, err = CheckAddressChecksum(address2)
require.NoError(t, err)
require.False(t, valid)
valid, err = CheckAddressChecksum(address3)
require.NoError(t, err)
require.False(t, valid)
}
func TestToChecksumAddress(t *testing.T) {
checksumAddress, err := ToChecksumAddress(address3)
require.NoError(t, err)
require.Equal(t, address1, checksumAddress)
address := "0x0ed343df16A5327aC13B689072804A2705A26f47"
checksumAddress, err = ToChecksumAddress(address)
require.NoError(t, err)
require.Equal(t, address1, checksumAddress)
}

View File

@ -986,3 +986,49 @@ func HexToNumber(hex string) string {
func NumberToHex(numString string) string {
return abi_spec.NumberToHex(numString)
}
func Sha3(str string) string {
return "0x" + abi_spec.Sha3(str)
}
func Utf8ToHex(str string) string {
hexString, err := abi_spec.Utf8ToHex(str)
if err != nil {
log.Error("failed to convert utf8 to hex", "str", str, "error", err)
}
return hexString
}
func HexToUtf8(hexString string) string {
str, err := abi_spec.HexToUtf8(hexString)
if err != nil {
log.Error("failed to convert hex to utf8", "hexString", hexString, "error", err)
}
return str
}
func CheckAddressChecksum(address string) string {
valid, err := abi_spec.CheckAddressChecksum(address)
if err != nil {
log.Error("failed to invoke check address checksum", "address", address, "error", err)
}
result, _ := json.Marshal(valid)
return string(result)
}
func IsAddress(address string) string {
valid, err := abi_spec.IsAddress(address)
if err != nil {
log.Error("failed to invoke IsAddress", "address", address, "error", err)
}
result, _ := json.Marshal(valid)
return string(result)
}
func ToChecksumAddress(address string) string {
address, err := abi_spec.ToChecksumAddress(address)
if err != nil {
log.Error("failed to convert to checksum address", "address", address, "error", err)
}
return address
}