Skip to content

Commit 13fa6ae

Browse files
authored
convert decoded big numbers to strings in api response (#193)
### TL;DR Added support for converting big numbers to strings in decoded transaction and log data. ### What changed? - Added a new utility function `ConvertBigNumbersToString` that recursively converts `*big.Int` and `big.Int` values to strings - Updated the `Serialize` method for `DecodedLog` to convert big numbers to strings in both indexed and non-indexed parameters - Updated the `Serialize` method for `DecodedTransaction` to convert big numbers to strings in decoded inputs - Removed unused `StripPayload` function and related helper functions ### How to test? 1. Create a transaction or log that contains big numbers in its decoded data 2. Call the `Serialize` method on the decoded transaction or log 3. Verify that the big numbers are properly converted to strings in the output ### Why make this change? Big numbers in decoded transaction and log data need to be converted to strings to ensure they can be properly serialized to JSON without precision loss. This is especially important for large numbers that exceed the range of standard JSON number types.
2 parents 4e6ed3f + e1c3cc1 commit 13fa6ae

File tree

3 files changed

+42
-141
lines changed

3 files changed

+42
-141
lines changed

internal/common/log.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -252,13 +252,17 @@ func (l *Log) Serialize() LogModel {
252252
}
253253

254254
func (l *DecodedLog) Serialize() DecodedLogModel {
255+
// Convert big numbers to strings in both indexed and non-indexed parameters
256+
indexedParams := ConvertBigNumbersToString(l.Decoded.IndexedParams).(map[string]interface{})
257+
nonIndexedParams := ConvertBigNumbersToString(l.Decoded.NonIndexedParams).(map[string]interface{})
258+
255259
return DecodedLogModel{
256260
LogModel: l.Log.Serialize(),
257261
Decoded: DecodedLogDataModel{
258262
Name: l.Decoded.Name,
259263
Signature: l.Decoded.Signature,
260-
IndexedParams: l.Decoded.IndexedParams,
261-
NonIndexedParams: l.Decoded.NonIndexedParams,
264+
IndexedParams: indexedParams,
265+
NonIndexedParams: nonIndexedParams,
262266
},
263267
}
264268
}

internal/common/transaction.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -229,12 +229,15 @@ func (t *Transaction) Serialize() TransactionModel {
229229
}
230230

231231
func (t *DecodedTransaction) Serialize() DecodedTransactionModel {
232+
// Convert big numbers to strings in the decoded inputs
233+
decodedInputs := ConvertBigNumbersToString(t.Decoded.Inputs).(map[string]interface{})
234+
232235
return DecodedTransactionModel{
233236
TransactionModel: t.Transaction.Serialize(),
234237
Decoded: DecodedTransactionDataModel{
235238
Name: t.Decoded.Name,
236239
Signature: t.Decoded.Signature,
237-
Inputs: t.Decoded.Inputs,
240+
Inputs: decodedInputs,
238241
},
239242
}
240243
}

internal/common/utils.go

+32-138
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"math/big"
66
"regexp"
77
"strings"
8-
"unicode"
98
)
109

1110
func BigIntSliceToChunks(values []*big.Int, chunkSize int) [][]*big.Int {
@@ -23,143 +22,6 @@ func BigIntSliceToChunks(values []*big.Int, chunkSize int) [][]*big.Int {
2322
return chunks
2423
}
2524

26-
// StripPayload removes parameter names, 'indexed' keywords,
27-
// and extra whitespaces from a Solidity function or event signature.
28-
func StripPayload(signature string) string {
29-
// Find the index of the first '(' and last ')'
30-
start := strings.Index(signature, "(")
31-
end := strings.LastIndex(signature, ")")
32-
if start == -1 || end == -1 || end <= start {
33-
// Return the original signature if it doesn't match the expected pattern
34-
return signature
35-
}
36-
37-
functionName := strings.TrimSpace(signature[:start])
38-
paramsStr := signature[start+1 : end]
39-
40-
// Parse parameters
41-
strippedParams := parseParameters(paramsStr)
42-
43-
// Reconstruct the cleaned-up signature
44-
strippedSignature := fmt.Sprintf("%s(%s)", functionName, strings.Join(strippedParams, ","))
45-
return strippedSignature
46-
}
47-
48-
// parseParameters parses the parameter string and returns a slice of cleaned-up parameter types
49-
func parseParameters(paramsStr string) []string {
50-
var params []string
51-
var currentParam strings.Builder
52-
bracketDepth := 0
53-
var inType bool // Indicates if we are currently parsing a type
54-
55-
runes := []rune(paramsStr)
56-
i := 0
57-
for i < len(runes) {
58-
char := runes[i]
59-
switch char {
60-
case '(', '[', '{':
61-
bracketDepth++
62-
inType = true
63-
currentParam.WriteRune(char)
64-
i++
65-
case ')', ']', '}':
66-
bracketDepth--
67-
currentParam.WriteRune(char)
68-
i++
69-
case ',':
70-
if bracketDepth == 0 {
71-
// End of current parameter
72-
paramType := cleanType(currentParam.String())
73-
if paramType != "" {
74-
params = append(params, paramType)
75-
}
76-
currentParam.Reset()
77-
inType = false
78-
i++
79-
} else {
80-
currentParam.WriteRune(char)
81-
i++
82-
}
83-
case ' ':
84-
if inType {
85-
currentParam.WriteRune(char)
86-
}
87-
i++
88-
default:
89-
// Check if the word is a keyword to ignore
90-
if unicode.IsLetter(char) {
91-
wordStart := i
92-
for i < len(runes) && (unicode.IsLetter(runes[i]) || unicode.IsDigit(runes[i])) {
93-
i++
94-
}
95-
word := string(runes[wordStart:i])
96-
97-
// Ignore 'indexed' and parameter names
98-
if isType(word) {
99-
inType = true
100-
currentParam.WriteString(word)
101-
} else if word == "indexed" {
102-
// Skip 'indexed'
103-
inType = false
104-
} else {
105-
// Ignore parameter names
106-
if inType {
107-
// If we are in the middle of parsing a type and encounter a parameter name, skip it
108-
inType = false
109-
}
110-
}
111-
} else {
112-
if inType {
113-
currentParam.WriteRune(char)
114-
}
115-
i++
116-
}
117-
}
118-
}
119-
120-
// Add the last parameter
121-
if currentParam.Len() > 0 {
122-
paramType := cleanType(currentParam.String())
123-
if paramType != "" {
124-
params = append(params, paramType)
125-
}
126-
}
127-
128-
return params
129-
}
130-
131-
// cleanType cleans up a parameter type string by removing extra spaces and 'tuple' keyword
132-
func cleanType(param string) string {
133-
// Remove 'tuple' keyword
134-
param = strings.ReplaceAll(param, "tuple", "")
135-
// Remove 'indexed' keyword
136-
param = strings.ReplaceAll(param, "indexed", "")
137-
// Remove any parameter names (already handled in parsing)
138-
param = strings.TrimSpace(param)
139-
// Remove extra whitespaces
140-
param = strings.Join(strings.Fields(param), "")
141-
return param
142-
}
143-
144-
// isType checks if a word is a Solidity type
145-
func isType(word string) bool {
146-
if strings.HasPrefix(word, "uint") || strings.HasPrefix(word, "int") {
147-
return true
148-
}
149-
types := map[string]bool{
150-
"address": true,
151-
"bool": true,
152-
"string": true,
153-
"bytes": true,
154-
"fixed": true,
155-
"ufixed": true,
156-
"function": true,
157-
// Add other types as needed
158-
}
159-
160-
return types[word]
161-
}
162-
16325
var allowedFunctions = map[string]struct{}{
16426
"sum": {},
16527
"count": {},
@@ -217,3 +79,35 @@ func ValidateQuery(query string) error {
21779

21880
return nil
21981
}
82+
83+
func ConvertBigNumbersToString(data interface{}) interface{} {
84+
switch v := data.(type) {
85+
case map[string]interface{}:
86+
for key, value := range v {
87+
v[key] = ConvertBigNumbersToString(value)
88+
}
89+
return v
90+
case []interface{}:
91+
for i, value := range v {
92+
v[i] = ConvertBigNumbersToString(value)
93+
}
94+
return v
95+
case []*big.Int:
96+
result := make([]string, len(v))
97+
for i, num := range v {
98+
if num == nil {
99+
result[i] = "0"
100+
} else {
101+
result[i] = num.String()
102+
}
103+
}
104+
return result
105+
case *big.Int:
106+
if v == nil {
107+
return "0"
108+
}
109+
return v.String()
110+
default:
111+
return v
112+
}
113+
}

0 commit comments

Comments
 (0)