status-go/abi-spec/types.go
2022-09-15 15:18:32 +01:00

191 lines
5.2 KiB
Go

package abispec
import (
"encoding/json"
"fmt"
"math/big"
"reflect"
"regexp"
"strconv"
"strings"
"github.com/ethereum/go-ethereum/common"
)
const bigIntType = "*big.Int"
var zero = big.NewInt(0)
var arrayTypePattern = regexp.MustCompile(`(\[([\d]*)\])`)
var bytesType = reflect.TypeOf([]byte{})
var typeMap = map[string]reflect.Type{
"uint8": reflect.TypeOf(uint8(0)),
"int8": reflect.TypeOf(int8(0)),
"uint16": reflect.TypeOf(uint16(0)),
"int16": reflect.TypeOf(int16(0)),
"uint32": reflect.TypeOf(uint32(0)),
"int32": reflect.TypeOf(int32(0)),
"uint64": reflect.TypeOf(uint64(0)),
"int64": reflect.TypeOf(int64(0)),
"bytes": bytesType,
"bytes1": reflect.TypeOf([1]byte{}),
"bytes2": reflect.TypeOf([2]byte{}),
"bytes3": reflect.TypeOf([3]byte{}),
"bytes4": reflect.TypeOf([4]byte{}),
"bytes5": reflect.TypeOf([5]byte{}),
"bytes6": reflect.TypeOf([6]byte{}),
"bytes7": reflect.TypeOf([7]byte{}),
"bytes8": reflect.TypeOf([8]byte{}),
"bytes9": reflect.TypeOf([9]byte{}),
"bytes10": reflect.TypeOf([10]byte{}),
"bytes11": reflect.TypeOf([11]byte{}),
"bytes12": reflect.TypeOf([12]byte{}),
"bytes13": reflect.TypeOf([13]byte{}),
"bytes14": reflect.TypeOf([14]byte{}),
"bytes15": reflect.TypeOf([15]byte{}),
"bytes16": reflect.TypeOf([16]byte{}),
"bytes17": reflect.TypeOf([17]byte{}),
"bytes18": reflect.TypeOf([18]byte{}),
"bytes19": reflect.TypeOf([19]byte{}),
"bytes20": reflect.TypeOf([20]byte{}),
"bytes21": reflect.TypeOf([21]byte{}),
"bytes22": reflect.TypeOf([22]byte{}),
"bytes23": reflect.TypeOf([23]byte{}),
"bytes24": reflect.TypeOf([24]byte{}),
"bytes25": reflect.TypeOf([25]byte{}),
"bytes26": reflect.TypeOf([26]byte{}),
"bytes27": reflect.TypeOf([27]byte{}),
"bytes28": reflect.TypeOf([28]byte{}),
"bytes29": reflect.TypeOf([29]byte{}),
"bytes30": reflect.TypeOf([30]byte{}),
"bytes31": reflect.TypeOf([31]byte{}),
"bytes32": reflect.TypeOf([32]byte{}),
"address": reflect.TypeOf(common.Address{}),
"bool": reflect.TypeOf(false),
"string": reflect.TypeOf(""),
}
func toGoType(solidityType string) (reflect.Type, error) {
if t, ok := typeMap[solidityType]; ok {
return t, nil
}
if arrayTypePattern.MatchString(solidityType) { // type of array
index := arrayTypePattern.FindStringIndex(solidityType)[0]
arrayType, err := toGoType(solidityType[0:index])
if err != nil {
return nil, err
}
matches := arrayTypePattern.FindAllStringSubmatch(solidityType, -1)
for i := 0; i <= len(matches)-1; i++ {
sizeStr := matches[i][2]
if sizeStr == "" {
arrayType = reflect.SliceOf(arrayType)
} else {
length, err := strconv.Atoi(sizeStr)
if err != nil {
return nil, err
}
arrayType = reflect.ArrayOf(length, arrayType)
}
}
return arrayType, nil
}
// uint and int are aliases for uint256 and int256, respectively.
// source: https://docs.soliditylang.org/en/v0.8.11/types.html
//TODO should we support type: uint ?? currently, go-ethereum doesn't support type uint
if strings.HasPrefix(solidityType, "uint") || strings.HasPrefix(solidityType, "int") {
return reflect.TypeOf(zero), nil
}
return nil, fmt.Errorf("unsupported type: %s", solidityType)
}
func toGoTypeValue(solidityType string, raw json.RawMessage) (*reflect.Value, error) {
goType, err := toGoType(solidityType)
if err != nil {
return nil, err
}
value := reflect.New(goType)
if goType == bytesType { // to support case like: Encode("sam(bytes)", `["dave"]`)
var s string
err = json.Unmarshal(raw, &s)
if err != nil {
return nil, err
}
bytes := []byte(s)
value.Elem().SetBytes(bytes)
return &value, nil
}
err = json.Unmarshal(raw, value.Interface())
if err != nil {
if goType.String() == bigIntType {
var s string
err = json.Unmarshal(raw, &s)
if err != nil {
return nil, err
}
v, success := big.NewInt(0).SetString(s, 0)
if !success {
return nil, fmt.Errorf("convert to go type value failed, value: %s", s)
}
value = reflect.ValueOf(v)
} else if goType.Kind() == reflect.Array { // to support case like: Encode("f(bytes10)", `["1234567890"]`)
elemKind := goType.Elem().Kind()
if elemKind == reflect.Uint8 {
var s string
err = json.Unmarshal(raw, &s)
if err != nil {
return nil, err
}
bytes := []byte(s)
for i, b := range bytes {
value.Elem().Index(i).Set(reflect.ValueOf(b))
}
return &value, nil
}
if elemKind == reflect.Array { // to support case like: Encode("bar(bytes3[2])", `[["abc","def"]]`)
var ss []string
err = json.Unmarshal(raw, &ss)
if err != nil {
return nil, err
}
var bytes [][]byte
for _, s := range ss {
bytes = append(bytes, []byte(s))
}
// convert []byte to []int
// note: Array and slice values encode as JSON arrays, except that []byte encodes as a base64-encoded string, and a nil slice encodes as the null JSON object.
var ints = make([][]int, len(bytes))
for i, r := range bytes {
ints[i] = make([]int, len(r))
for j, b := range r {
ints[i][j] = int(b)
}
}
jsonString, err := json.Marshal(ints)
if err != nil {
return nil, err
}
if err = json.Unmarshal(jsonString, value.Interface()); err != nil {
return nil, err
}
}
}
}
return &value, err
}