Skip to content

Commit b669de6

Browse files
committed
decode transaction inputs
1 parent 69139c0 commit b669de6

File tree

3 files changed

+229
-5
lines changed

3 files changed

+229
-5
lines changed

internal/common/abi.go

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
package common
2+
3+
import (
4+
"fmt"
5+
"regexp"
6+
"strings"
7+
8+
"github.com/ethereum/go-ethereum/accounts/abi"
9+
)
10+
11+
12+
func ConstructFunctionABI(signature string) (*abi.Method, error) {
13+
regex := regexp.MustCompile(`^(\w+)\((.*)\)$`)
14+
matches := regex.FindStringSubmatch(strings.TrimSpace(signature))
15+
if len(matches) != 3 {
16+
return nil, fmt.Errorf("invalid event signature format")
17+
}
18+
19+
functionName := matches[1]
20+
params := matches[2]
21+
22+
inputs, err := parseParamsToAbiArguments(params)
23+
if err != nil {
24+
return nil, fmt.Errorf("failed to parse params to abi arguments '%s': %v", params, err)
25+
}
26+
27+
function := abi.NewMethod(functionName, functionName, abi.Function, "", false, false, inputs, nil)
28+
29+
return &function, nil
30+
}
31+
32+
func parseParamsToAbiArguments(params string) (abi.Arguments, error) {
33+
paramList := splitParams(strings.TrimSpace(params))
34+
var inputs abi.Arguments
35+
for idx, param := range paramList {
36+
arg, err := parseParamToAbiArgument(param, fmt.Sprintf("%d", idx))
37+
if err != nil {
38+
return nil, fmt.Errorf("failed to parse param to arg '%s': %v", param, err)
39+
}
40+
inputs = append(inputs, *arg)
41+
}
42+
return inputs, nil
43+
}
44+
45+
/**
46+
* Splits a string of parameters into a list of parameters
47+
*/
48+
func splitParams(params string) []string {
49+
var result []string
50+
depth := 0
51+
current := ""
52+
for _, r := range params {
53+
switch r {
54+
case ',':
55+
if depth == 0 {
56+
result = append(result, strings.TrimSpace(current))
57+
current = ""
58+
continue
59+
}
60+
case '(':
61+
depth++
62+
case ')':
63+
depth--
64+
}
65+
current += string(r)
66+
}
67+
if strings.TrimSpace(current) != "" {
68+
result = append(result, strings.TrimSpace(current))
69+
}
70+
return result
71+
}
72+
73+
func parseParamToAbiArgument(param string, fallbackName string) (*abi.Argument, error) {
74+
argName, paramType, err := getArgNameAndType(param, fallbackName)
75+
if err != nil {
76+
return nil, fmt.Errorf("failed to get arg name and type '%s': %v", param, err)
77+
}
78+
if isTuple(paramType) {
79+
argType, err := marshalTupleParamToArgumentType(paramType)
80+
if err != nil {
81+
return nil, fmt.Errorf("failed to marshal tuple: %v", err)
82+
}
83+
return &abi.Argument{
84+
Name: argName,
85+
Type: argType,
86+
}, nil
87+
} else {
88+
argType, err := abi.NewType(paramType, paramType, nil)
89+
if err != nil {
90+
return nil, fmt.Errorf("failed to parse type '%s': %v", paramType, err)
91+
}
92+
return &abi.Argument{
93+
Name: argName,
94+
Type: argType,
95+
}, nil
96+
}
97+
}
98+
99+
func getArgNameAndType(param string, fallbackName string) (name string, paramType string, err error) {
100+
if isTuple(param) {
101+
lastParenIndex := strings.LastIndex(param, ")")
102+
if lastParenIndex == -1 {
103+
return "", "", fmt.Errorf("invalid tuple format")
104+
}
105+
if len(param)-1 == lastParenIndex {
106+
return fallbackName, param, nil
107+
}
108+
paramsEndIdx := lastParenIndex + 1
109+
if strings.HasPrefix(param[paramsEndIdx:], "[]") {
110+
paramsEndIdx = lastParenIndex + 3
111+
}
112+
return strings.TrimSpace(param[paramsEndIdx:]), param[:paramsEndIdx], nil
113+
} else {
114+
tokens := strings.Fields(param)
115+
if len(tokens) == 1 {
116+
return fallbackName, strings.TrimSpace(tokens[0]), nil
117+
}
118+
return strings.TrimSpace(tokens[len(tokens)-1]), strings.Join(tokens[:len(tokens)-1], " "), nil
119+
}
120+
}
121+
122+
func isTuple(param string) bool {
123+
return strings.HasPrefix(param, "(")
124+
}
125+
126+
func marshalTupleParamToArgumentType(paramType string) (abi.Type, error) {
127+
typ := "tuple"
128+
isSlice := strings.HasSuffix(paramType, "[]")
129+
strippedParamType := strings.TrimPrefix(paramType, "(")
130+
if isSlice {
131+
strippedParamType = strings.TrimSuffix(strippedParamType, "[]")
132+
typ = "tuple[]"
133+
}
134+
strippedParamType = strings.TrimSuffix(strippedParamType, ")")
135+
components, err := marshalParamArguments(strippedParamType)
136+
if err != nil {
137+
return abi.Type{}, fmt.Errorf("failed to marshal tuple: %v", err)
138+
}
139+
return abi.NewType(typ, typ, components)
140+
}
141+
142+
func marshalParamArguments(param string) ([]abi.ArgumentMarshaling, error) {
143+
paramList := splitParams(param)
144+
components := []abi.ArgumentMarshaling{}
145+
for idx, param := range paramList {
146+
argName, paramType, err := getArgNameAndType(param, fmt.Sprintf("field%d", idx))
147+
if err != nil {
148+
return nil, fmt.Errorf("failed to get arg name and type '%s': %v", param, err)
149+
}
150+
if isTuple(paramType) {
151+
subComponents, err := marshalParamArguments(paramType[1 : len(paramType)-1])
152+
if err != nil {
153+
return nil, fmt.Errorf("failed to marshal tuple: %v", err)
154+
}
155+
components = append(components, abi.ArgumentMarshaling{
156+
Type: "tuple",
157+
Name: argName,
158+
Components: subComponents,
159+
})
160+
} else {
161+
components = append(components, abi.ArgumentMarshaling{
162+
Type: paramType,
163+
Name: argName,
164+
})
165+
}
166+
}
167+
return components, nil
168+
}

internal/common/transaction.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
package common
22

33
import (
4+
"encoding/hex"
45
"math/big"
6+
"strings"
7+
8+
"github.com/ethereum/go-ethereum/accounts/abi"
9+
"github.com/rs/zerolog/log"
510
)
611

712
type Transaction struct {
@@ -35,3 +40,40 @@ type Transaction struct {
3540
LogsBloom *string `json:"logs_bloom"`
3641
Status *uint64 `json:"status"`
3742
}
43+
44+
type DecodedTransactionData struct {
45+
Name string `json:"name"`
46+
Signature string `json:"signature"`
47+
Inputs map[string]interface{} `json:"inputs"`
48+
}
49+
50+
type DecodedTransaction struct {
51+
Transaction
52+
Decoded DecodedTransactionData `json:"decodedData"`
53+
}
54+
55+
func (t *Transaction) Decode(functionABI *abi.Method) *DecodedTransaction {
56+
decodedData, err := hex.DecodeString(strings.TrimPrefix(t.Data, "0x"))
57+
if err != nil {
58+
log.Debug().Msgf("failed to decode transaction data: %v", err)
59+
return &DecodedTransaction{Transaction: *t}
60+
}
61+
62+
if len(decodedData) < 4 {
63+
log.Debug().Msg("Data too short to contain function selector")
64+
return &DecodedTransaction{Transaction: *t}
65+
}
66+
inputData := decodedData[4:]
67+
decodedInputs := make(map[string]interface{})
68+
err = functionABI.Inputs.UnpackIntoMap(decodedInputs, inputData)
69+
if err != nil {
70+
log.Warn().Msgf("failed to decode function parameters: %v, signature: %s", err, functionABI.Sig)
71+
}
72+
return &DecodedTransaction{
73+
Transaction: *t,
74+
Decoded: DecodedTransactionData{
75+
Name: functionABI.RawName,
76+
Signature: functionABI.Sig,
77+
Inputs: decodedInputs,
78+
}}
79+
}

internal/handlers/transactions_handlers.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package handlers
33
import (
44
"net/http"
55

6+
"github.com/ethereum/go-ethereum/accounts/abi"
67
"github.com/ethereum/go-ethereum/crypto"
78
"github.com/gin-gonic/gin"
89
"github.com/rs/zerolog/log"
@@ -56,7 +57,7 @@ type TransactionModel struct {
5657
// @Failure 500 {object} api.Error
5758
// @Router /{chainId}/transactions [get]
5859
func GetTransactions(c *gin.Context) {
59-
handleTransactionsRequest(c, "", "")
60+
handleTransactionsRequest(c, "", "", nil)
6061
}
6162

6263
// @Summary Get transactions by contract
@@ -81,7 +82,7 @@ func GetTransactions(c *gin.Context) {
8182
// @Router /{chainId}/transactions/{to} [get]
8283
func GetTransactionsByContract(c *gin.Context) {
8384
to := c.Param("to")
84-
handleTransactionsRequest(c, to, "")
85+
handleTransactionsRequest(c, to, "", nil)
8586
}
8687

8788
// @Summary Get transactions by contract and signature
@@ -109,10 +110,14 @@ func GetTransactionsByContractAndSignature(c *gin.Context) {
109110
to := c.Param("to")
110111
signature := c.Param("signature")
111112
strippedSignature := common.StripPayload(signature)
112-
handleTransactionsRequest(c, to, strippedSignature)
113+
functionABI, err := common.ConstructFunctionABI(signature)
114+
if err != nil {
115+
log.Debug().Err(err).Msgf("Unable to construct function ABI for %s", signature)
116+
}
117+
handleTransactionsRequest(c, to, strippedSignature, functionABI)
113118
}
114119

115-
func handleTransactionsRequest(c *gin.Context, contractAddress, signature string) {
120+
func handleTransactionsRequest(c *gin.Context, contractAddress, signature string, functionABI *abi.Method) {
116121
chainId, err := api.GetChainId(c)
117122
if err != nil {
118123
api.BadRequestErrorHandler(c, err)
@@ -187,7 +192,16 @@ func handleTransactionsRequest(c *gin.Context, contractAddress, signature string
187192
api.InternalErrorHandler(c)
188193
return
189194
}
190-
queryResult.Data = transactionsResult.Data
195+
if functionABI != nil {
196+
decodedTransactions := []*common.DecodedTransaction{}
197+
for _, transaction := range transactionsResult.Data {
198+
decodedTransaction := transaction.Decode(functionABI)
199+
decodedTransactions = append(decodedTransactions, decodedTransaction)
200+
}
201+
queryResult.Data = decodedTransactions
202+
} else {
203+
queryResult.Data = transactionsResult.Data
204+
}
191205
queryResult.Meta.TotalItems = len(transactionsResult.Data)
192206
}
193207

0 commit comments

Comments
 (0)