diff --git a/VERSION b/VERSION index ced503bc8..1b6917370 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.109.1 +0.109.2 diff --git a/abi-spec/core.go b/abi-spec/core.go new file mode 100644 index 000000000..22c63a855 --- /dev/null +++ b/abi-spec/core.go @@ -0,0 +1,161 @@ +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), ¶msRaw); 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) + } + + if strings.HasPrefix(bytesString, "0x") { + bytesString = bytesString[2:] + } + 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 +} diff --git a/abi-spec/core_test.go b/abi-spec/core_test.go new file mode 100644 index 000000000..15a1e5448 --- /dev/null +++ b/abi-spec/core_test.go @@ -0,0 +1,133 @@ +package abispec + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestMethodPattern(t *testing.T) { + results := methodPattern.FindStringSubmatch("sam(bytes,bool,uint256[])") + require.Equal(t, "sam(bytes,bool,uint256[])", results[0]) + require.Equal(t, "sam", results[1]) + require.Equal(t, "bytes,bool,uint256[]", results[2]) + + results = methodPattern.FindStringSubmatch("s(bytes,bool,uint256[])") + require.Equal(t, "s(bytes,bool,uint256[])", results[0]) + require.Equal(t, "s", results[1]) + require.Equal(t, "bytes,bool,uint256[]", results[2]) + + results = methodPattern.FindStringSubmatch("s1(bytes,bool,uint256[])") + require.Equal(t, "s1(bytes,bool,uint256[])", results[0]) + require.Equal(t, "s1", results[1]) + require.Equal(t, "bytes,bool,uint256[]", results[2]) + + results = methodPattern.FindStringSubmatch("1s(bytes,bool,uint256[])") + require.Len(t, results, 0) +} + +func TestEncodeTransfer(t *testing.T) { + result, err := EncodeTransfer("0x6f5f90fb1dD8E406F233442935F689bA7D5701b2", "10000") + require.NoError(t, err) + require.Equal(t, "0xa9059cbb0000000000000000000000006f5f90fb1dd8e406f233442935f689ba7d5701b20000000000000000000000000000000000000000000000000000000000002710", result) + + result, err = EncodeTransfer("0x6f5f90fb1dD8E406F233442935F689bA7D5701b2", "0x2710") + require.NoError(t, err) + require.Equal(t, "0xa9059cbb0000000000000000000000006f5f90fb1dd8e406f233442935f689ba7d5701b20000000000000000000000000000000000000000000000000000000000002710", result) +} + +func TestEncode(t *testing.T) { + result, err := Encode("baz(uint32,bool)", "[69,true]") + require.NoError(t, err) + require.Equal(t, "0xcdcd77c000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001", result) + + result, err = Encode("baz(uint256,bool)", "[69,true]") + require.NoError(t, err) + require.Equal(t, "0x72ed38b600000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001", result) + + result, err = Encode("bar(bytes3[2])", `[["abc","def"]]`) + require.NoError(t, err) + require.Equal(t, "0xfce353f661626300000000000000000000000000000000000000000000000000000000006465660000000000000000000000000000000000000000000000000000000000", result) + + result, err = Encode("sam(bytes)", `["dave"]`) + require.NoError(t, err) + require.Equal(t, "0x05e73fb9000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000046461766500000000000000000000000000000000000000000000000000000000", result) + + result, err = Encode("sam(bytes,bool,uint256[])", `["dave",true,[1,2,3]]`) + require.NoError(t, err) + require.Equal(t, "0xa5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003", result) + + result, err = Encode("f(bytes10)", `["1234567890"]`) + require.NoError(t, err) + require.Equal(t, "0x6350865e3132333435363738393000000000000000000000000000000000000000000000", result) + + result, err = Encode("f(uint256,uint32[],bytes10,bytes)", `[291,[1110,1929],"1234567890","Hello, world!"]`) + require.NoError(t, err) + require.Equal(t, "0x8be6524600000000000000000000000000000000000000000000000000000000000001230000000000000000000000000000000000000000000000000000000000000080313233343536373839300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000004560000000000000000000000000000000000000000000000000000000000000789000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20776f726c642100000000000000000000000000000000000000", result) + + result, err = Encode("getExpectedRate(address,address,uint256)", `["0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",1000]`) + require.NoError(t, err) + require.Equal(t, "0x809a9e55000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee00000000000000000000000000000000000000000000000000000000000003e8", result) + + result, err = Encode("f(uint256)", `["115792089237316195423570985008687907853269984665640564039457584007913129639935"]`) + require.NoError(t, err) + require.Equal(t, "0xb3de648bffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", result) + + //TODO following case would fail cause go-ethereum does not support type uint + //result, err = Encode("g(uint[][],string[])", `[[[1,2],[3]],["one","two","three"]]`) + //require.NoError(t, err) + //require.Equal(t, "0xad6a3446000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000036f6e650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000374776f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000057468726565000000000000000000000000000000000000000000000000000000", result) + +} + +func TestDecode(t *testing.T) { + out, err := Decode("0x000000000000000000000000000000000000000000000000000000005bc741cd00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000013b86dbf1a83c9e6a492914a0ee39e8a5b7eb60700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002e516d533152484e4a57414b356e426f6f57454d34654d644268707a35666e325764557473457357754a4b79356147000000000000000000000000000000000000", + []string{"uint256", "bytes", "address", "uint256", "uint256"}) + require.NoError(t, err) + bytes, err := json.Marshal(out) + require.NoError(t, err) + require.JSONEq(t, `[1539785165,"0x516d533152484e4a57414b356e426f6f57454d34654d644268707a35666e325764557473457357754a4b79356147","0x13b86dbf1a83c9e6a492914a0ee39e8a5b7eb607",0,0]`, string(bytes)) + + out, err = Decode("0x0000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000d7621dc58210001", + []string{"uint256", "uint256"}) + require.NoError(t, err) + bytes, err = json.Marshal(out) + require.NoError(t, err) + require.JSONEq(t, `["1000000000000000000","970000000000000001"]`, string(bytes)) + + out, err = Decode("0x000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee00000000000000000000000000000000000000000000000000000000000003e8", + []string{"address", "address", "uint256"}) + require.NoError(t, err) + bytes, err = json.Marshal(out) + require.NoError(t, err) + require.JSONEq(t, `["0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",1000]`, string(bytes)) + + out, err = Decode("0x00000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001", + []string{"uint32", "bool"}) + require.NoError(t, err) + bytes, err = json.Marshal(out) + require.NoError(t, err) + require.JSONEq(t, `[69,true]`, string(bytes)) + + out, err = Decode("0x61626300000000000000000000000000000000000000000000000000000000006465660000000000000000000000000000000000000000000000000000000000", + []string{"bytes3[2]"}) + require.NoError(t, err) + bytes, err = json.Marshal(out[0]) + require.NoError(t, err) + require.JSONEq(t, `["616263","646566"]`, string(bytes)) + + out, err = Decode("0x0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003", + []string{"string", "bool", "uint256[]"}) + require.NoError(t, err) + bytes, err = json.Marshal(out) + require.NoError(t, err) + require.JSONEq(t, `["dave",true,[1,2,3]]`, string(bytes)) + + out, err = Decode("0x00000000000000000000000000000000000000000000000000000000000001230000000000000000000000000000000000000000000000000000000000000080313233343536373839300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000004560000000000000000000000000000000000000000000000000000000000000789000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20776f726c642100000000000000000000000000000000000000", + []string{"uint256", "uint32[]", "bytes10", "bytes"}) + require.NoError(t, err) + bytes, err = json.Marshal(out) + require.NoError(t, err) + require.JSONEq(t, `[291,[1110,1929],"31323334353637383930","0x48656c6c6f2c20776f726c6421"]`, string(bytes)) +} diff --git a/abi-spec/types.go b/abi-spec/types.go new file mode 100644 index 000000000..5d6ec992a --- /dev/null +++ b/abi-spec/types.go @@ -0,0 +1,190 @@ +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 +} diff --git a/abi-spec/types_test.go b/abi-spec/types_test.go new file mode 100644 index 000000000..031211e79 --- /dev/null +++ b/abi-spec/types_test.go @@ -0,0 +1,64 @@ +package abispec + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestToGoTypeValue(t *testing.T) { + var raw json.RawMessage + err := json.Unmarshal([]byte(`"dave"`), &raw) + require.NoError(t, err) + val, err := toGoTypeValue("bytes", raw) + require.NoError(t, err) + require.Equal(t, []byte("dave"), val.Elem().Bytes()) + + err = json.Unmarshal([]byte(`true`), &raw) + require.NoError(t, err) + val, err = toGoTypeValue("bool", raw) + require.NoError(t, err) + require.True(t, val.Elem().Bool()) +} + +func TestToGoType(t *testing.T) { + var raws []json.RawMessage + err := json.Unmarshal([]byte("[8]"), &raws) + require.NoError(t, err) + value, err := toGoTypeValue("uint8", raws[0]) + require.NoError(t, err) + require.Equal(t, uint8(8), *value.Interface().(*uint8)) + + goType, err := toGoType("uint256[][3][]") + require.NoError(t, err) + require.Equal(t, "[][3][]*big.Int", goType.String()) + + goType, err = toGoType("uint256[][][3]") + require.NoError(t, err) + require.Equal(t, "[3][][]*big.Int", goType.String()) + + goType, err = toGoType("uint256[3][][]") + require.NoError(t, err) + require.Equal(t, "[][][3]*big.Int", goType.String()) + + goType, err = toGoType("bytes3[2]") + require.NoError(t, err) + require.Equal(t, "[2][3]uint8", goType.String()) + +} + +func TestArrayTypePattern(t *testing.T) { + require.True(t, arrayTypePattern.MatchString(`uint8[]`)) + require.False(t, arrayTypePattern.MatchString(`uint8`)) + + s := "uint8[][2][1][]" + matches := arrayTypePattern.FindAllStringSubmatch(s, -1) + require.Equal(t, 3, len(matches[0])) + require.Equal(t, "", matches[0][2]) + require.Equal(t, "2", matches[1][2]) + + index := arrayTypePattern.FindStringIndex(s)[0] + require.Equal(t, 5, index) + require.Equal(t, "uint8", s[0:index]) +} diff --git a/abi-spec/utils.go b/abi-spec/utils.go new file mode 100644 index 000000000..b609f24e9 --- /dev/null +++ b/abi-spec/utils.go @@ -0,0 +1,22 @@ +package abispec + +import ( + "fmt" + "math/big" +) + +func HexToNumber(hex string) string { + num, success := big.NewInt(0).SetString(hex, 16) + if success { + return num.String() + } + return "" +} + +func NumberToHex(numString string) string { + num, success := big.NewInt(0).SetString(numString, 0) + if success { + return fmt.Sprintf("%x", num) + } + return "" +} diff --git a/abi-spec/utils_test.go b/abi-spec/utils_test.go new file mode 100644 index 000000000..05c4d4e04 --- /dev/null +++ b/abi-spec/utils_test.go @@ -0,0 +1,35 @@ +package abispec + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestHexToNumber(t *testing.T) { + //hex number is less than 53 bits, it returns a number + num := HexToNumber("9") + bytes, err := json.Marshal(num) + require.NoError(t, err) + require.JSONEq(t, `"9"`, string(bytes)) + + num = HexToNumber("99999999") + bytes, err = json.Marshal(num) + require.NoError(t, err) + require.JSONEq(t, `"2576980377"`, string(bytes)) + + num = HexToNumber("1fffffffffffff") + bytes, err = json.Marshal(num) + require.NoError(t, err) + require.JSONEq(t, `"9007199254740991"`, string(bytes)) + + num = HexToNumber("9999999999999999") + bytes, err = json.Marshal(num) + require.NoError(t, err) + require.JSONEq(t, `"11068046444225730969"`, string(bytes)) +} + +func TestNumberToHex(t *testing.T) { + require.Equal(t, "20000000000002", NumberToHex("9007199254740994")) +} diff --git a/mobile/status.go b/mobile/status.go index 8472ff27f..1c8f2993c 100644 --- a/mobile/status.go +++ b/mobile/status.go @@ -16,6 +16,7 @@ import ( "github.com/status-im/zxcvbn-go" "github.com/status-im/zxcvbn-go/scoring" + abi_spec "github.com/status-im/status-go/abi-spec" "github.com/status-im/status-go/api" "github.com/status-im/status-go/api/multiformat" "github.com/status-im/status-go/eth-node/types" @@ -937,3 +938,51 @@ func InputConnectionStringForBootstrapping(cs, configJSON string) string { err := server.StartUpPairingClient(statusBackend.GetMultiaccountDB(), cs, configJSON) return makeJSONResponse(err) } + +func EncodeTransfer(to string, value string) string { + result, err := abi_spec.EncodeTransfer(to, value) + if err != nil { + log.Error("failed to encode transfer", "to", to, "value", value, "error", err) + return "" + } + return result +} + +func EncodeFunctionCall(method string, paramsJSON string) string { + result, err := abi_spec.Encode(method, paramsJSON) + if err != nil { + log.Error("failed to encode function call", "method", method, "paramsJSON", paramsJSON, "error", err) + } + return result +} + +func DecodeParameters(decodeParamJSON string) string { + decodeParam := struct { + BytesString string `json:"bytesString"` + Types []string `json:"types"` + }{} + err := json.Unmarshal([]byte(decodeParamJSON), &decodeParam) + if err != nil { + log.Error("failed to unmarshal json when decoding parameters", "decodeParamJSON", decodeParamJSON, "error", err) + return "" + } + result, err := abi_spec.Decode(decodeParam.BytesString, decodeParam.Types) + if err != nil { + log.Error("failed to decode parameters", "decodeParamJSON", decodeParamJSON, "error", err) + return "" + } + bytes, err := json.Marshal(result) + if err != nil { + log.Error("failed to marshal result", "result", result, "decodeParamJSON", decodeParamJSON, "error", err) + return "" + } + return string(bytes) +} + +func HexToNumber(hex string) string { + return abi_spec.HexToNumber(hex) +} + +func NumberToHex(numString string) string { + return abi_spec.NumberToHex(numString) +}