Skip to content

Commit 3d3ba98

Browse files
committed
decode events
1 parent 226bdae commit 3d3ba98

File tree

3 files changed

+197
-20
lines changed

3 files changed

+197
-20
lines changed

internal/common/abi.go

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,34 @@ import (
88
"github.com/ethereum/go-ethereum/accounts/abi"
99
)
1010

11-
func ConstructFunctionABI(signature string) (*abi.Method, error) {
11+
func ConstructEventABI(signature string) (*abi.Event, error) {
12+
// Regex to extract the event name and parameters
1213
regex := regexp.MustCompile(`^(\w+)\((.*)\)$`)
1314
matches := regex.FindStringSubmatch(strings.TrimSpace(signature))
1415
if len(matches) != 3 {
1516
return nil, fmt.Errorf("invalid event signature format")
1617
}
1718

19+
eventName := matches[1]
20+
parameters := matches[2]
21+
22+
inputs, err := parseParamsToAbiArguments(parameters)
23+
if err != nil {
24+
return nil, fmt.Errorf("failed to parse params to abi arguments '%s': %v", parameters, err)
25+
}
26+
27+
event := abi.NewEvent(eventName, eventName, false, inputs)
28+
29+
return &event, nil
30+
}
31+
32+
func ConstructFunctionABI(signature string) (*abi.Method, error) {
33+
regex := regexp.MustCompile(`^(\w+)\((.*)\)$`)
34+
matches := regex.FindStringSubmatch(strings.TrimSpace(signature))
35+
if len(matches) != 3 {
36+
return nil, fmt.Errorf("invalid function signature format")
37+
}
38+
1839
functionName := matches[1]
1940
params := matches[2]
2041

@@ -70,7 +91,7 @@ func splitParams(params string) []string {
7091
}
7192

7293
func parseParamToAbiArgument(param string, fallbackName string) (*abi.Argument, error) {
73-
argName, paramType, err := getArgNameAndType(param, fallbackName)
94+
argName, paramType, indexed, err := getArgNameAndType(param, fallbackName)
7495
if err != nil {
7596
return nil, fmt.Errorf("failed to get arg name and type '%s': %v", param, err)
7697
}
@@ -80,42 +101,59 @@ func parseParamToAbiArgument(param string, fallbackName string) (*abi.Argument,
80101
return nil, fmt.Errorf("failed to marshal tuple: %v", err)
81102
}
82103
return &abi.Argument{
83-
Name: argName,
84-
Type: argType,
104+
Name: argName,
105+
Type: argType,
106+
Indexed: indexed,
85107
}, nil
86108
} else {
87109
argType, err := abi.NewType(paramType, paramType, nil)
88110
if err != nil {
89111
return nil, fmt.Errorf("failed to parse type '%s': %v", paramType, err)
90112
}
91113
return &abi.Argument{
92-
Name: argName,
93-
Type: argType,
114+
Name: argName,
115+
Type: argType,
116+
Indexed: indexed,
94117
}, nil
95118
}
96119
}
97120

98-
func getArgNameAndType(param string, fallbackName string) (name string, paramType string, err error) {
121+
func getArgNameAndType(param string, fallbackName string) (name string, paramType string, indexed bool, err error) {
122+
param, indexed = checkIfParamIsIndexed(param)
99123
if isTuple(param) {
100124
lastParenIndex := strings.LastIndex(param, ")")
101125
if lastParenIndex == -1 {
102-
return "", "", fmt.Errorf("invalid tuple format")
126+
return "", "", false, fmt.Errorf("invalid tuple format")
103127
}
104128
if len(param)-1 == lastParenIndex {
105-
return fallbackName, param, nil
129+
return fallbackName, param, indexed, nil
106130
}
107131
paramsEndIdx := lastParenIndex + 1
108132
if strings.HasPrefix(param[paramsEndIdx:], "[]") {
109133
paramsEndIdx = lastParenIndex + 3
110134
}
111-
return strings.TrimSpace(param[paramsEndIdx:]), param[:paramsEndIdx], nil
135+
return strings.TrimSpace(param[paramsEndIdx:]), param[:paramsEndIdx], indexed, nil
112136
} else {
113137
tokens := strings.Fields(param)
114138
if len(tokens) == 1 {
115-
return fallbackName, strings.TrimSpace(tokens[0]), nil
139+
return fallbackName, strings.TrimSpace(tokens[0]), indexed, nil
140+
}
141+
return strings.TrimSpace(tokens[len(tokens)-1]), strings.Join(tokens[:len(tokens)-1], " "), indexed, nil
142+
}
143+
}
144+
145+
func checkIfParamIsIndexed(param string) (string, bool) {
146+
tokens := strings.Fields(param)
147+
indexed := false
148+
for i, token := range tokens {
149+
if token == "indexed" || strings.HasPrefix(token, "index_topic_") {
150+
tokens = append(tokens[:i], tokens[i+1:]...)
151+
indexed = true
152+
break
116153
}
117-
return strings.TrimSpace(tokens[len(tokens)-1]), strings.Join(tokens[:len(tokens)-1], " "), nil
118154
}
155+
param = strings.Join(tokens, " ")
156+
return param, indexed
119157
}
120158

121159
func isTuple(param string) bool {
@@ -142,7 +180,7 @@ func marshalParamArguments(param string) ([]abi.ArgumentMarshaling, error) {
142180
paramList := splitParams(param)
143181
components := []abi.ArgumentMarshaling{}
144182
for idx, param := range paramList {
145-
argName, paramType, err := getArgNameAndType(param, fmt.Sprintf("field%d", idx))
183+
argName, paramType, indexed, err := getArgNameAndType(param, fmt.Sprintf("field%d", idx))
146184
if err != nil {
147185
return nil, fmt.Errorf("failed to get arg name and type '%s': %v", param, err)
148186
}
@@ -155,11 +193,13 @@ func marshalParamArguments(param string) ([]abi.ArgumentMarshaling, error) {
155193
Type: "tuple",
156194
Name: argName,
157195
Components: subComponents,
196+
Indexed: indexed,
158197
})
159198
} else {
160199
components = append(components, abi.ArgumentMarshaling{
161-
Type: paramType,
162-
Name: argName,
200+
Type: paramType,
201+
Name: argName,
202+
Indexed: indexed,
163203
})
164204
}
165205
}

internal/common/log.go

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

33
import (
4+
"encoding/hex"
5+
"fmt"
46
"math/big"
7+
8+
"github.com/ethereum/go-ethereum/accounts/abi"
9+
gethCommon "github.com/ethereum/go-ethereum/common"
10+
"github.com/rs/zerolog/log"
511
)
612

713
type Log struct {
@@ -20,3 +26,120 @@ type Log struct {
2026
type RawLogs = []map[string]interface{}
2127
type RawReceipts = []RawReceipt
2228
type RawReceipt = map[string]interface{}
29+
30+
type DecodedLogData struct {
31+
Name string `json:"name"`
32+
Signature string `json:"signature"`
33+
IndexedParams map[string]interface{} `json:"indexedParams"`
34+
NonIndexedParams map[string]interface{} `json:"nonIndexedParams"`
35+
}
36+
37+
type DecodedLog struct {
38+
Log
39+
Decoded DecodedLogData `json:"decodedData"`
40+
}
41+
42+
func (l *Log) Decode(eventABI *abi.Event) *DecodedLog {
43+
44+
decodedIndexed := make(map[string]interface{})
45+
indexedArgs := abi.Arguments{}
46+
for _, arg := range eventABI.Inputs {
47+
if arg.Indexed {
48+
indexedArgs = append(indexedArgs, arg)
49+
}
50+
}
51+
// Decode indexed parameters
52+
for i, arg := range indexedArgs {
53+
if len(l.Topics) <= i+1 {
54+
log.Warn().Msgf("missing topic for indexed parameter: %s, signature: %s", arg.Name, eventABI.Sig)
55+
return &DecodedLog{Log: *l}
56+
}
57+
decodedValue, err := decodeIndexedArgument(arg.Type, l.Topics[i+1])
58+
if err != nil {
59+
log.Warn().Msgf("failed to decode indexed parameter %s: %v, signature: %s", arg.Name, err, eventABI.Sig)
60+
return &DecodedLog{Log: *l}
61+
}
62+
decodedIndexed[arg.Name] = decodedValue
63+
}
64+
65+
// Decode non-indexed parameters
66+
decodedNonIndexed := make(map[string]interface{})
67+
dataBytes := gethCommon.Hex2Bytes(l.Data[2:])
68+
err := eventABI.Inputs.UnpackIntoMap(decodedNonIndexed, dataBytes)
69+
if err != nil {
70+
log.Warn().Msgf("failed to decode non-indexed parameters: %v, signature: %s", err, eventABI.Sig)
71+
return &DecodedLog{Log: *l}
72+
}
73+
74+
return &DecodedLog{
75+
Log: *l,
76+
Decoded: DecodedLogData{
77+
Name: eventABI.Name,
78+
Signature: eventABI.Sig,
79+
IndexedParams: decodedIndexed,
80+
NonIndexedParams: convertBytesAndNumericToHex(decodedNonIndexed).(map[string]interface{}),
81+
},
82+
}
83+
}
84+
85+
func decodeIndexedArgument(argType abi.Type, topic string) (interface{}, error) {
86+
topicBytes := gethCommon.Hex2Bytes(topic[2:]) // Remove "0x" prefix
87+
switch argType.T {
88+
case abi.AddressTy:
89+
return gethCommon.BytesToAddress(topicBytes), nil
90+
case abi.UintTy, abi.IntTy:
91+
return new(big.Int).SetBytes(topicBytes), nil
92+
case abi.BoolTy:
93+
return topicBytes[0] != 0, nil
94+
case abi.StringTy:
95+
return string(topicBytes), nil
96+
case abi.BytesTy, abi.FixedBytesTy:
97+
return "0x" + gethCommon.Bytes2Hex(topicBytes), nil
98+
case abi.HashTy:
99+
if len(topicBytes) != 32 {
100+
return nil, fmt.Errorf("invalid hash length: expected 32, got %d", len(topicBytes))
101+
}
102+
return gethCommon.BytesToHash(topicBytes), nil
103+
case abi.FixedPointTy:
104+
bi := new(big.Int).SetBytes(topicBytes)
105+
bf := new(big.Float).SetInt(bi)
106+
return bf, nil
107+
case abi.SliceTy, abi.ArrayTy, abi.TupleTy:
108+
return nil, fmt.Errorf("type %s is not supported for indexed parameters", argType.String())
109+
default:
110+
return nil, fmt.Errorf("unsupported indexed type: %s", argType.String())
111+
}
112+
}
113+
114+
func convertBytesAndNumericToHex(data interface{}) interface{} {
115+
switch v := data.(type) {
116+
case map[string]interface{}:
117+
for key, value := range v {
118+
v[key] = convertBytesAndNumericToHex(value)
119+
}
120+
return v
121+
case []interface{}:
122+
for i, value := range v {
123+
v[i] = convertBytesAndNumericToHex(value)
124+
}
125+
return v
126+
case []byte:
127+
return fmt.Sprintf("0x%s", hex.EncodeToString(v))
128+
case []uint:
129+
hexStrings := make([]string, len(v))
130+
for i, num := range v {
131+
hexStrings[i] = fmt.Sprintf("0x%x", num)
132+
}
133+
return hexStrings
134+
case [32]uint8:
135+
return fmt.Sprintf("0x%s", hex.EncodeToString(v[:]))
136+
case [64]uint8:
137+
return fmt.Sprintf("0x%s", hex.EncodeToString(v[:]))
138+
case [128]uint8:
139+
return fmt.Sprintf("0x%s", hex.EncodeToString(v[:]))
140+
case [256]uint8:
141+
return fmt.Sprintf("0x%s", hex.EncodeToString(v[:]))
142+
default:
143+
return v
144+
}
145+
}

internal/handlers/logs_handlers.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"net/http"
55
"sync"
66

7+
"github.com/ethereum/go-ethereum/accounts/abi"
78
"github.com/ethereum/go-ethereum/crypto"
89
"github.com/gin-gonic/gin"
910
"github.com/rs/zerolog/log"
@@ -54,7 +55,7 @@ type LogModel struct {
5455
// @Failure 500 {object} api.Error
5556
// @Router /{chainId}/events [get]
5657
func GetLogs(c *gin.Context) {
57-
handleLogsRequest(c, "", "")
58+
handleLogsRequest(c, "", "", nil)
5859
}
5960

6061
// @Summary Get logs by contract
@@ -79,7 +80,7 @@ func GetLogs(c *gin.Context) {
7980
// @Router /{chainId}/events/{contract} [get]
8081
func GetLogsByContract(c *gin.Context) {
8182
contractAddress := c.Param("contract")
82-
handleLogsRequest(c, contractAddress, "")
83+
handleLogsRequest(c, contractAddress, "", nil)
8384
}
8485

8586
// @Summary Get logs by contract and event signature
@@ -107,10 +108,14 @@ func GetLogsByContractAndSignature(c *gin.Context) {
107108
contractAddress := c.Param("contract")
108109
eventSignature := c.Param("signature")
109110
strippedSignature := common.StripPayload(eventSignature)
110-
handleLogsRequest(c, contractAddress, strippedSignature)
111+
eventABI, err := common.ConstructEventABI(eventSignature)
112+
if err != nil {
113+
log.Debug().Err(err).Msgf("Unable to construct event ABI for %s", eventSignature)
114+
}
115+
handleLogsRequest(c, contractAddress, strippedSignature, eventABI)
111116
}
112117

113-
func handleLogsRequest(c *gin.Context, contractAddress, signature string) {
118+
func handleLogsRequest(c *gin.Context, contractAddress, signature string, eventABI *abi.Event) {
114119
chainId, err := api.GetChainId(c)
115120
if err != nil {
116121
api.BadRequestErrorHandler(c, err)
@@ -185,7 +190,16 @@ func handleLogsRequest(c *gin.Context, contractAddress, signature string) {
185190
api.InternalErrorHandler(c)
186191
return
187192
}
188-
queryResult.Data = logsResult.Data
193+
if eventABI != nil {
194+
decodedLogs := []*common.DecodedLog{}
195+
for _, log := range logsResult.Data {
196+
decodedLog := log.Decode(eventABI)
197+
decodedLogs = append(decodedLogs, decodedLog)
198+
}
199+
queryResult.Data = decodedLogs
200+
} else {
201+
queryResult.Data = logsResult.Data
202+
}
189203
queryResult.Meta.TotalItems = len(logsResult.Data)
190204
}
191205

0 commit comments

Comments
 (0)