status-go/abi-spec/core.go

161 lines
5.1 KiB
Go

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
}