diff --git a/services/wallet/api.go b/services/wallet/api.go index c814a84a8..98825b7df 100644 --- a/services/wallet/api.go +++ b/services/wallet/api.go @@ -115,7 +115,7 @@ func (api *API) GetTransfersForIdentities(ctx context.Context, identities []tran func (api *API) FetchDecodedTxData(ctx context.Context, data string) (*thirdparty.DataParsed, error) { log.Debug("[Wallet: FetchDecodedTxData]") - return api.s.decoder.Run(data) + return api.s.decoder.Decode(data) } // Deprecated: GetCachedBalances is deprecated. Use GetTokensBalances instead diff --git a/services/wallet/decoder.go b/services/wallet/decoder.go new file mode 100644 index 000000000..76af45d8f --- /dev/null +++ b/services/wallet/decoder.go @@ -0,0 +1,28 @@ +package wallet + +import ( + "github.com/status-im/status-go/services/wallet/thirdparty" + "github.com/status-im/status-go/services/wallet/thirdparty/fourbyte" + "github.com/status-im/status-go/services/wallet/thirdparty/fourbytegithub" +) + +type Decoder struct { + Main *fourbytegithub.Client + Fallback *fourbyte.Client +} + +func NewDecoder() *Decoder { + return &Decoder{ + Main: fourbytegithub.NewClient(), + Fallback: fourbyte.NewClient(), + } +} + +func (d *Decoder) Decode(data string) (*thirdparty.DataParsed, error) { + parsed, err := d.Main.Run(data) + if err == nil { + return parsed, nil + } + + return d.Fallback.Run(data) +} diff --git a/services/wallet/service.go b/services/wallet/service.go index 61999b9a4..283bf26cb 100644 --- a/services/wallet/service.go +++ b/services/wallet/service.go @@ -27,7 +27,6 @@ import ( "github.com/status-im/status-go/services/wallet/thirdparty/alchemy" "github.com/status-im/status-go/services/wallet/thirdparty/coingecko" "github.com/status-im/status-go/services/wallet/thirdparty/cryptocompare" - "github.com/status-im/status-go/services/wallet/thirdparty/fourbyte" "github.com/status-im/status-go/services/wallet/thirdparty/infura" "github.com/status-im/status-go/services/wallet/token" "github.com/status-im/status-go/services/wallet/transfer" @@ -127,7 +126,7 @@ func NewService( history: history, currency: currency, activity: activity, - decoder: fourbyte.NewClient(), + decoder: NewDecoder(), } } @@ -155,7 +154,7 @@ type Service struct { history *history.Service currency *currency.Service activity *activity.Service - decoder thirdparty.DecoderProvider + decoder *Decoder } // Start signals transmitter. diff --git a/services/wallet/thirdparty/fourbytegithub/client.go b/services/wallet/thirdparty/fourbytegithub/client.go new file mode 100644 index 000000000..f7f0150a0 --- /dev/null +++ b/services/wallet/thirdparty/fourbytegithub/client.go @@ -0,0 +1,103 @@ +package fourbytegithub + +import ( + "errors" + "fmt" + "io/ioutil" + "net/http" + "regexp" + "strings" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/status-im/status-go/services/wallet/thirdparty" +) + +type Signature struct { + ID int `json:"id"` + Text string `json:"text_signature"` +} + +type ByID []Signature + +func (s ByID) Len() int { return len(s) } +func (s ByID) Less(i, j int) bool { return s[i].ID > s[j].ID } +func (s ByID) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +type SignatureList struct { + Count int `json:"count"` + Results []Signature `json:"results"` +} + +type Client struct { + Client *http.Client + URL string +} + +func NewClient() *Client { + return &Client{Client: &http.Client{Timeout: time.Minute}, URL: "https://raw.githubusercontent.com"} +} + +func (c *Client) DoQuery(url string) (*http.Response, error) { + resp, err := c.Client.Get(url) + + if err != nil { + return nil, err + } + return resp, nil +} + +func (c *Client) Run(data string) (*thirdparty.DataParsed, error) { + if len(data) < 10 || !strings.HasPrefix(data, "0x") { + return nil, errors.New("input is badly formatted") + } + methodSigData := data[2:10] + url := fmt.Sprintf("%s/ethereum-lists/4bytes/master/signatures/%s", c.URL, methodSigData) + resp, err := c.DoQuery(url) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + signature := string(body) + rgx := regexp.MustCompile(`\((.*?)\)`) + + id := fmt.Sprintf("0x%s", methodSigData) + name := strings.Split(signature, "(")[0] + rs := rgx.FindStringSubmatch(signature) + + inputs := make([]string, 0) + rawInputs := strings.Split(rs[1], ",") + for index, typ := range rawInputs { + if index == len(rawInputs)-1 && typ == "bytes" { + continue + } + inputs = append(inputs, fmt.Sprintf("{\"name\":\"%d\",\"type\":\"%s\"}", index, typ)) + } + + functionABI := fmt.Sprintf("[{\"constant\":true,\"inputs\":[%s],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\", \"name\": \"%s\"}], ", strings.Join(inputs, ","), name) + contractABI, err := abi.JSON(strings.NewReader(functionABI)) + if err != nil { + return nil, err + } + method := contractABI.Methods[name] + inputsMap := make(map[string]interface{}) + if err := method.Inputs.UnpackIntoMap(inputsMap, []byte(data[10:])); err != nil { + return nil, err + } + inputsMapString := make(map[string]string) + for key, value := range inputsMap { + inputsMapString[key] = fmt.Sprintf("%v", value) + } + + return &thirdparty.DataParsed{ + Name: name, + ID: id, + Signature: signature, + Inputs: inputsMapString, + }, nil +} diff --git a/services/wallet/thirdparty/fourbytegithub/client_test.go b/services/wallet/thirdparty/fourbytegithub/client_test.go new file mode 100644 index 000000000..2e8bb0bea --- /dev/null +++ b/services/wallet/thirdparty/fourbytegithub/client_test.go @@ -0,0 +1,34 @@ +package fourbytegithub + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestRun(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + _, err := w.Write([]byte("transfer(address,uint256)")) + if err != nil { + return + } + })) + defer srv.Close() + + fb := NewClient() + fb.Client = srv.Client() + fb.URL = srv.URL + + res, err := fb.Run("0xa9059cbb000000000000000000000000e0e40d81121d41a7d85d8d2462b475074f9df5ec0000000000000000000000000000000000000000000000000000000077359400") + require.Nil(t, err) + require.Equal(t, res.Signature, "transfer(address,uint256)") + require.Equal(t, res.ID, "0xa9059cbb") + require.Equal(t, res.Name, "transfer") + require.Equal(t, res.Inputs, map[string]string{ + "0": "0x3030303030303030303030306530653430643831", + "1": "22252012820881184517742036120632151212095838186768864961872069019727748752739", + }) +}