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 }