status-im/status-go

View on GitHub
abi-spec/types.go

Summary

Maintainability
A
3 hrs
Test Coverage
C
77%
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
}