From 9d5b123c95d0bde9ff72815fc89e5d40b979d63b Mon Sep 17 00:00:00 2001 From: Anthony Laibe Date: Thu, 25 May 2023 15:12:54 +0200 Subject: [PATCH] feat: add api to parse tx input --- services/wallet/api.go | 6 + services/wallet/service.go | 3 + services/wallet/thirdparty/fourbyte/client.go | 111 ++++++++++++++++++ .../wallet/thirdparty/fourbyte/client_test.go | 25 ++++ services/wallet/thirdparty/types.go | 10 ++ 5 files changed, 155 insertions(+) create mode 100644 services/wallet/thirdparty/fourbyte/client.go create mode 100644 services/wallet/thirdparty/fourbyte/client_test.go diff --git a/services/wallet/api.go b/services/wallet/api.go index d6c3f3732..58b29c264 100644 --- a/services/wallet/api.go +++ b/services/wallet/api.go @@ -112,6 +112,12 @@ func (api *API) GetTransfersForIdentities(ctx context.Context, identities []tran return api.s.transferController.GetTransfersForIdentities(ctx, identities) } +func (api *API) FetchDecodedTxData(ctx context.Context, data string) (*thirdparty.DataParsed, error) { + log.Debug("[Wallet: FetchDecodedTxData]") + + return api.s.decoder.Run(data) +} + // Deprecated: GetCachedBalances is deprecated. Use GetTokensBalances instead func (api *API) GetCachedBalances(ctx context.Context, addresses []common.Address) ([]transfer.BlockView, error) { return api.s.transferController.GetCachedBalances(ctx, api.s.rpcClient.UpstreamChainID, addresses) diff --git a/services/wallet/service.go b/services/wallet/service.go index b9f31736a..3e6450efa 100644 --- a/services/wallet/service.go +++ b/services/wallet/service.go @@ -26,6 +26,7 @@ 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" @@ -121,6 +122,7 @@ func NewService( history: history, currency: currency, activity: activity, + decoder: fourbyte.NewClient(), } } @@ -148,6 +150,7 @@ type Service struct { history *history.Service currency *currency.Service activity *activity.Service + decoder thirdparty.DecoderProvider } // Start signals transmitter. diff --git a/services/wallet/thirdparty/fourbyte/client.go b/services/wallet/thirdparty/fourbyte/client.go new file mode 100644 index 000000000..18645d1ed --- /dev/null +++ b/services/wallet/thirdparty/fourbyte/client.go @@ -0,0 +1,111 @@ +package fourbyte + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "regexp" + "sort" + "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 +} + +func NewClient() *Client { + return &Client{client: &http.Client{Timeout: time.Minute}} +} + +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("https://www.4byte.directory/api/v1/signatures/?hex_signature=%s", 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 + } + var signatures SignatureList + err = json.Unmarshal(body, &signatures) + if err != nil { + return nil, err + } + + if signatures.Count == 0 { + return nil, err + } + rgx := regexp.MustCompile(`\((.*?)\)`) + results := signatures.Results + sort.Sort(ByID(results)) + for _, signature := range results { + name := strings.Split(signature.Text, "(")[0] + rs := rgx.FindStringSubmatch(signature.Text) + + inputs := make([]string, 0) + for index, typ := range strings.Split(rs[1], ",") { + 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 { + continue + } + method := contractABI.Methods[name] + inputsMap := make(map[string]interface{}) + if err := method.Inputs.UnpackIntoMap(inputsMap, []byte(data[10:])); err != nil { + continue + } + inputsMapString := make(map[string]string) + for key, value := range inputsMap { + inputsMapString[key] = fmt.Sprintf("%v", value) + } + + return &thirdparty.DataParsed{ + Name: name, + Signature: signature.Text, + Inputs: inputsMapString, + }, nil + } + + return nil, errors.New("couldn't find a corresponding signature") +} diff --git a/services/wallet/thirdparty/fourbyte/client_test.go b/services/wallet/thirdparty/fourbyte/client_test.go new file mode 100644 index 000000000..6167bbbf8 --- /dev/null +++ b/services/wallet/thirdparty/fourbyte/client_test.go @@ -0,0 +1,25 @@ +package fourbyte + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestRun(t *testing.T) { + client := NewClient() + res, err := client.Run("0x40e8d703000000000000000000000000670dca62b3418bddd08cbc69cb4490a5a3382a9f0000000000000000000000000000000000000000000000000000000000000064") + require.Nil(t, err) + require.Equal(t, res.Signature, "processDepositQueue(address,uint256)") + require.Equal(t, res.Name, "processDepositQueue") + require.Equal(t, res.Inputs, map[string]string{ + "0": "0x3030303030303030303030303637306463613632", + "1": "44417128579249187980157595307322491418158007948522794164811090501355597543782", + }) + + _, err = client.Run("0x70a08231000") + require.NotNil(t, err) + + _, err = client.Run("0x70a082310") + require.NotNil(t, err) +} diff --git a/services/wallet/thirdparty/types.go b/services/wallet/thirdparty/types.go index 1a4558f87..2c9e26fae 100644 --- a/services/wallet/thirdparty/types.go +++ b/services/wallet/thirdparty/types.go @@ -81,3 +81,13 @@ type NFTContractOwnershipProvider interface { FetchNFTOwnersByContractAddress(chainID uint64, contractAddress common.Address) (*NFTContractOwnership, error) IsChainSupported(chainID uint64) bool } + +type DataParsed struct { + Name string `json:"name"` + Inputs map[string]string `json:"inputs"` + Signature string `json:"signature"` +} + +type DecoderProvider interface { + Run(data string) (*DataParsed, error) +}