status-im/status-go

View on GitHub
abi-spec/core.go

Summary

Maintainability
A
0 mins
Test Coverage
B
80%
package abispec

import (
    "encoding/hex"
    "encoding/json"
    "fmt"
    "math/big"
    "reflect"
    "regexp"
    "strings"

    "github.com/ethereum/go-ethereum/accounts/abi"
    "github.com/ethereum/go-ethereum/common"
)

var methodPattern = regexp.MustCompile(`(^[a-zA-Z].*)\((.*)\)`)

var maxSafeInteger = big.NewInt(int64(9007199254740991))

const transferInputs = `[{"type":"address"},{"type":"uint256"}]`
const transferFunctionName = "transfer"

var transferInDef = fmt.Sprintf(`[{ "name" : "%s", "type": "function", "inputs": %s}]`, transferFunctionName, transferInputs)
var transferAbi, _ = abi.JSON(strings.NewReader(transferInDef))

func EncodeTransfer(to string, value string) (string, error) {
    amount, success := big.NewInt(0).SetString(value, 0)
    if !success {
        return "", fmt.Errorf("failed to convert %s to big.Int", value)
    }
    address := common.HexToAddress(to)
    result, err := transferAbi.Pack(transferFunctionName, address, amount)
    if err != nil {
        return "", fmt.Errorf("pack failed: %v", err)
    }
    return "0x" + hex.EncodeToString(result), nil
}

func Encode(method string, paramsJSON string) (string, error) {
    matches := methodPattern.FindStringSubmatch(method)
    if len(matches) != 3 {
        return "", fmt.Errorf("unrecognized method: %s", method)
    }
    methodName := matches[1]
    paramTypesString := strings.TrimSpace(matches[2])

    // value of inputs looks like: `[{ "type": "uint32" },{ "type": "bool" }]`
    inputs := "["
    var params []interface{}
    if len(paramTypesString) > 0 {
        var paramsRaw []json.RawMessage
        if err := json.Unmarshal([]byte(paramsJSON), &paramsRaw); err != nil {
            return "", fmt.Errorf("unable to unmarshal params: %v", err)
        }
        types := strings.Split(paramTypesString, ",")
        if len(paramsRaw) != len(types) {
            return "", fmt.Errorf("num of param type should equal to num of param value, actual value: %d, %d", len(types), len(paramsRaw))
        }

        for i, typeName := range types {
            if i != 0 {
                inputs += ","
            }
            inputs += fmt.Sprintf(`{"type":"%s"}`, typeName)

            param, err := toGoTypeValue(typeName, paramsRaw[i])
            if err != nil {
                return "", err
            }
            params = append(params, param.Interface())
        }
    }
    inputs += "]"

    inDef := fmt.Sprintf(`[{ "name" : "%s", "type": "function", "inputs": %s}]`, methodName, inputs)
    inAbi, err := abi.JSON(strings.NewReader(inDef))
    if err != nil {
        return "", fmt.Errorf("invalid ABI definition %s, %v", inDef, err)
    }
    var result []byte
    result, err = inAbi.Pack(methodName, params...)

    if err != nil {
        return "", fmt.Errorf("Pack failed: %v", err)
    }

    return "0x" + hex.EncodeToString(result), nil
}

// override result to make it looks like what status-mobile need
func overrideResult(out []interface{}) []interface{} {
    for i, v := range out {
        outType := reflect.TypeOf(v)
        switch outType.String() {
        case "[]uint8":
            out[i] = "0x" + common.Bytes2Hex(v.([]uint8))
        case bigIntType:
            vv := v.(*big.Int)
            if vv.Cmp(maxSafeInteger) == 1 {
                out[i] = vv.String()
            }
        }

        if outType.Kind() == reflect.Array && outType.Elem().Kind() == reflect.Array && outType.Elem().Elem().Kind() == reflect.Uint8 { //case e.g. [2][3]uint8
            val := reflect.ValueOf(v)
            rowNum := val.Len()
            colNum := val.Index(0).Len()
            var ss = make([]string, rowNum)
            for i := 0; i < rowNum; i++ {
                bytes := make([]uint8, colNum)
                for j := 0; j < colNum; j++ {
                    bytes[j] = uint8(val.Index(i).Index(j).Uint())
                }
                ss[i] = common.Bytes2Hex(bytes)
            }
            out[i] = ss
        } else if outType.String() != "common.Address" && outType.Kind() == reflect.Array && outType.Elem().Kind() == reflect.Uint8 {
            val := reflect.ValueOf(v)
            len := val.Len()
            bytes := make([]uint8, len)
            for i := 0; i < len; i++ {
                bytes[i] = uint8(val.Index(i).Uint())
            }
            out[i] = common.Bytes2Hex(bytes)
        }

    }
    return out
}

// bytesString e.g. 0x000000000000000000000000000000000000000000000000000000005bc741cd00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000013b86dbf1a83c9e6a492914a0ee39e8a5b7eb60700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002e516d533152484e4a57414b356e426f6f57454d34654d644268707a35666e325764557473457357754a4b79356147000000000000000000000000000000000000
// types e.g. []string{"uint256","bytes","address","uint256","uint256"}
func Decode(bytesString string, types []string) ([]interface{}, error) {
    outputs := "["
    for i, typeName := range types {
        if i != 0 {
            outputs += ","
        }
        outputs += fmt.Sprintf(`{"type":"%s"}`, typeName)
    }
    outputs += "]"
    def := fmt.Sprintf(`[{ "name" : "method", "type": "function", "outputs": %s}]`, outputs)
    abi, err := abi.JSON(strings.NewReader(def))
    if err != nil {
        return nil, fmt.Errorf("invalid ABI definition %s: %v", def, err)
    }

    bytesString = strings.TrimPrefix(bytesString, "0x")

    bytes, err := hex.DecodeString(bytesString)
    if err != nil {
        return nil, fmt.Errorf("invalid hex %s: %v", bytesString, err)
    }
    out, err := abi.Unpack("method", bytes)
    if err != nil {
        return nil, fmt.Errorf("unpack failed: %v", err)
    }

    return overrideResult(out), nil
}