feat: add api to parse tx input

This commit is contained in:
Anthony Laibe 2023-05-25 15:12:54 +02:00 committed by Anthony Laibe
parent 2375b10b03
commit 9d5b123c95
5 changed files with 155 additions and 0 deletions

View File

@ -112,6 +112,12 @@ func (api *API) GetTransfersForIdentities(ctx context.Context, identities []tran
return api.s.transferController.GetTransfersForIdentities(ctx, identities) 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 // Deprecated: GetCachedBalances is deprecated. Use GetTokensBalances instead
func (api *API) GetCachedBalances(ctx context.Context, addresses []common.Address) ([]transfer.BlockView, error) { func (api *API) GetCachedBalances(ctx context.Context, addresses []common.Address) ([]transfer.BlockView, error) {
return api.s.transferController.GetCachedBalances(ctx, api.s.rpcClient.UpstreamChainID, addresses) return api.s.transferController.GetCachedBalances(ctx, api.s.rpcClient.UpstreamChainID, addresses)

View File

@ -26,6 +26,7 @@ import (
"github.com/status-im/status-go/services/wallet/thirdparty/alchemy" "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/coingecko"
"github.com/status-im/status-go/services/wallet/thirdparty/cryptocompare" "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/thirdparty/infura"
"github.com/status-im/status-go/services/wallet/token" "github.com/status-im/status-go/services/wallet/token"
"github.com/status-im/status-go/services/wallet/transfer" "github.com/status-im/status-go/services/wallet/transfer"
@ -121,6 +122,7 @@ func NewService(
history: history, history: history,
currency: currency, currency: currency,
activity: activity, activity: activity,
decoder: fourbyte.NewClient(),
} }
} }
@ -148,6 +150,7 @@ type Service struct {
history *history.Service history *history.Service
currency *currency.Service currency *currency.Service
activity *activity.Service activity *activity.Service
decoder thirdparty.DecoderProvider
} }
// Start signals transmitter. // Start signals transmitter.

View File

@ -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")
}

View File

@ -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)
}

View File

@ -81,3 +81,13 @@ type NFTContractOwnershipProvider interface {
FetchNFTOwnersByContractAddress(chainID uint64, contractAddress common.Address) (*NFTContractOwnership, error) FetchNFTOwnersByContractAddress(chainID uint64, contractAddress common.Address) (*NFTContractOwnership, error)
IsChainSupported(chainID uint64) bool 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)
}