mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-05-08 17:21:47 +00:00
accounts/abi: support inline tuple
This commit is contained in:
parent
fd81bb1b9f
commit
7fddaecfd9
2 changed files with 314 additions and 102 deletions
|
|
@ -2,6 +2,7 @@ package abi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
|
@ -45,9 +46,113 @@ func normalizeArgument(arg ArgumentMarshaling, isEvent bool) map[string]interfac
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseStructDefinitions extracts and parses struct definitions from signatures
|
||||||
|
func parseStructDefinitions(signatures []string) (map[string][]ArgumentMarshaling, error) {
|
||||||
|
structs := make(map[string][]ArgumentMarshaling)
|
||||||
|
|
||||||
|
for _, sig := range signatures {
|
||||||
|
sig = skipWhitespace(sig)
|
||||||
|
if !strings.HasPrefix(sig, "struct ") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rest := sig[7:]
|
||||||
|
rest = skipWhitespace(rest)
|
||||||
|
|
||||||
|
nameEnd := 0
|
||||||
|
for nameEnd < len(rest) && (isAlpha(rest[nameEnd]) || isDigit(rest[nameEnd]) || rest[nameEnd] == '_') {
|
||||||
|
nameEnd++
|
||||||
|
}
|
||||||
|
if nameEnd == 0 {
|
||||||
|
return nil, fmt.Errorf("invalid struct definition: missing name")
|
||||||
|
}
|
||||||
|
|
||||||
|
structName := rest[:nameEnd]
|
||||||
|
rest = skipWhitespace(rest[nameEnd:])
|
||||||
|
|
||||||
|
if len(rest) == 0 || rest[0] != '{' {
|
||||||
|
return nil, fmt.Errorf("invalid struct definition: expected '{'")
|
||||||
|
}
|
||||||
|
rest = rest[1:]
|
||||||
|
|
||||||
|
closeBrace := strings.Index(rest, "}")
|
||||||
|
if closeBrace == -1 {
|
||||||
|
return nil, fmt.Errorf("invalid struct definition: missing '}'")
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldsStr := rest[:closeBrace]
|
||||||
|
fields := strings.Split(fieldsStr, ";")
|
||||||
|
|
||||||
|
var components []ArgumentMarshaling
|
||||||
|
for _, field := range fields {
|
||||||
|
field = skipWhitespace(field)
|
||||||
|
if field == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Fields(field)
|
||||||
|
if len(parts) < 2 {
|
||||||
|
return nil, fmt.Errorf("invalid struct field: %s", field)
|
||||||
|
}
|
||||||
|
|
||||||
|
typeName := parts[0]
|
||||||
|
fieldName := parts[1]
|
||||||
|
|
||||||
|
components = append(components, ArgumentMarshaling{
|
||||||
|
Name: fieldName,
|
||||||
|
Type: typeName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
structs[structName] = components
|
||||||
|
}
|
||||||
|
|
||||||
|
return structs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// expandStructReferences replaces struct references with tuple types
|
||||||
|
func expandStructReferences(args []ArgumentMarshaling, structs map[string][]ArgumentMarshaling) error {
|
||||||
|
for i := range args {
|
||||||
|
baseType := args[i].Type
|
||||||
|
arraySuffix := ""
|
||||||
|
|
||||||
|
for strings.HasSuffix(baseType, "]") {
|
||||||
|
bracketIdx := strings.LastIndex(baseType, "[")
|
||||||
|
if bracketIdx == -1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
arraySuffix = baseType[bracketIdx:] + arraySuffix
|
||||||
|
baseType = baseType[:bracketIdx]
|
||||||
|
}
|
||||||
|
|
||||||
|
if structComponents, ok := structs[baseType]; ok {
|
||||||
|
expandedComponents := make([]ArgumentMarshaling, len(structComponents))
|
||||||
|
copy(expandedComponents, structComponents)
|
||||||
|
|
||||||
|
if err := expandStructReferences(expandedComponents, structs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
args[i].Type = "tuple" + arraySuffix
|
||||||
|
args[i].InternalType = "struct " + baseType + arraySuffix
|
||||||
|
args[i].Components = expandedComponents
|
||||||
|
} else if len(args[i].Components) > 0 {
|
||||||
|
if err := expandStructReferences(args[i].Components, structs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// parseHumanReadableABIArray processes multiple human-readable ABI signatures
|
// parseHumanReadableABIArray processes multiple human-readable ABI signatures
|
||||||
// and returns a JSON array. Comments and empty lines are skipped.
|
// and returns a JSON array. Comments and empty lines are skipped.
|
||||||
func parseHumanReadableABIArray(signatures []string) ([]byte, error) {
|
func parseHumanReadableABIArray(signatures []string) ([]byte, error) {
|
||||||
|
structs, err := parseStructDefinitions(signatures)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
var results []map[string]interface{}
|
var results []map[string]interface{}
|
||||||
for _, sig := range signatures {
|
for _, sig := range signatures {
|
||||||
sig = skipWhitespace(sig)
|
sig = skipWhitespace(sig)
|
||||||
|
|
@ -78,6 +183,9 @@ func parseHumanReadableABIArray(signatures []string) ([]byte, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if inputs, ok := result["inputs"].([]ArgumentMarshaling); ok {
|
if inputs, ok := result["inputs"].([]ArgumentMarshaling); ok {
|
||||||
|
if err := expandStructReferences(inputs, structs); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
normInputs := make([]map[string]interface{}, len(inputs))
|
normInputs := make([]map[string]interface{}, len(inputs))
|
||||||
for i, inp := range inputs {
|
for i, inp := range inputs {
|
||||||
normInputs[i] = normalizeArgument(inp, isEvent)
|
normInputs[i] = normalizeArgument(inp, isEvent)
|
||||||
|
|
@ -85,6 +193,9 @@ func parseHumanReadableABIArray(signatures []string) ([]byte, error) {
|
||||||
normalized["inputs"] = normInputs
|
normalized["inputs"] = normInputs
|
||||||
}
|
}
|
||||||
if outputs, ok := result["outputs"].([]ArgumentMarshaling); ok {
|
if outputs, ok := result["outputs"].([]ArgumentMarshaling); ok {
|
||||||
|
if err := expandStructReferences(outputs, structs); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
normOutputs := make([]map[string]interface{}, len(outputs))
|
normOutputs := make([]map[string]interface{}, len(outputs))
|
||||||
for i, out := range outputs {
|
for i, out := range outputs {
|
||||||
normOutputs[i] = normalizeArgument(out, false)
|
normOutputs[i] = normalizeArgument(out, false)
|
||||||
|
|
@ -478,6 +589,65 @@ func TestParseHumanReadableABI(t *testing.T) {
|
||||||
}
|
}
|
||||||
]`,
|
]`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "nested tuple in return",
|
||||||
|
input: []string{
|
||||||
|
"function communityPool() view returns ((string denom, uint256 amount)[] coins)",
|
||||||
|
},
|
||||||
|
expected: `[
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "communityPool",
|
||||||
|
"inputs": [],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "coins",
|
||||||
|
"type": "tuple[]",
|
||||||
|
"components": [
|
||||||
|
{"name": "denom", "type": "string"},
|
||||||
|
{"name": "amount", "type": "uint256"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view"
|
||||||
|
}
|
||||||
|
]`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "function with struct arrays and nested arrays",
|
||||||
|
input: []string{
|
||||||
|
"struct DataPoint { uint256 value; string label; }",
|
||||||
|
"function processData(DataPoint[][] dataMatrix, DataPoint[5][] fixedDataArray)",
|
||||||
|
},
|
||||||
|
expected: `[
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "processData",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "dataMatrix",
|
||||||
|
"type": "tuple[][]",
|
||||||
|
"internalType": "struct DataPoint[][]",
|
||||||
|
"components": [
|
||||||
|
{"name": "value", "type": "uint256"},
|
||||||
|
{"name": "label", "type": "string"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fixedDataArray",
|
||||||
|
"type": "tuple[5][]",
|
||||||
|
"internalType": "struct DataPoint[5][]",
|
||||||
|
"components": [
|
||||||
|
{"name": "value", "type": "uint256"},
|
||||||
|
{"name": "label", "type": "string"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"stateMutability": "nonpayable"
|
||||||
|
}
|
||||||
|
]`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|
|
||||||
|
|
@ -202,76 +202,110 @@ func parseElementaryType(unescapedSelector string) (string, string, error) {
|
||||||
return parsedType, rest, nil
|
return parsedType, rest, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseCompositeType(unescapedSelector string) ([]interface{}, string, error) {
|
// parseTupleType parses inline tuple syntax like (string denom, uint256 amount)[]
|
||||||
if len(unescapedSelector) == 0 || unescapedSelector[0] != '(' {
|
func parseTupleType(params string) ([]ArgumentMarshaling, string, string, error) {
|
||||||
return nil, "", fmt.Errorf("expected '(', got %c", unescapedSelector[0])
|
if params == "" || params[0] != '(' {
|
||||||
|
return nil, "", params, fmt.Errorf("expected '(' at start of tuple")
|
||||||
}
|
}
|
||||||
parsedType, rest, err := parseType(unescapedSelector[1:])
|
|
||||||
if err != nil {
|
rest := params[1:]
|
||||||
return nil, "", fmt.Errorf("failed to parse type: %v", err)
|
rest = skipWhitespace(rest)
|
||||||
}
|
|
||||||
result := []interface{}{parsedType}
|
if rest[0] == ')' {
|
||||||
for len(rest) > 0 && rest[0] != ')' {
|
rest = rest[1:]
|
||||||
parsedType, rest, err = parseType(rest[1:])
|
arraySuffix := ""
|
||||||
if err != nil {
|
for len(rest) > 0 && rest[0] == '[' {
|
||||||
return nil, "", fmt.Errorf("failed to parse type: %v", err)
|
endBracket := 1
|
||||||
|
for endBracket < len(rest) && rest[endBracket] != ']' {
|
||||||
|
endBracket++
|
||||||
|
}
|
||||||
|
if endBracket >= len(rest) {
|
||||||
|
return nil, "", rest, fmt.Errorf("unclosed array bracket")
|
||||||
|
}
|
||||||
|
arraySuffix += rest[:endBracket+1]
|
||||||
|
rest = rest[endBracket+1:]
|
||||||
}
|
}
|
||||||
result = append(result, parsedType)
|
return []ArgumentMarshaling{}, arraySuffix, rest, nil
|
||||||
}
|
}
|
||||||
if len(rest) == 0 || rest[0] != ')' {
|
|
||||||
return nil, "", fmt.Errorf("expected ')', got '%s'", rest)
|
|
||||||
}
|
|
||||||
if len(rest) >= 3 && rest[1] == '[' && rest[2] == ']' {
|
|
||||||
return append(result, "[]"), rest[3:], nil
|
|
||||||
}
|
|
||||||
return result, rest[1:], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseType(unescapedSelector string) (interface{}, string, error) {
|
var components []ArgumentMarshaling
|
||||||
if len(unescapedSelector) == 0 {
|
paramIndex := 0
|
||||||
return nil, "", errors.New("empty type")
|
|
||||||
}
|
|
||||||
if unescapedSelector[0] == '(' {
|
|
||||||
return parseCompositeType(unescapedSelector)
|
|
||||||
} else {
|
|
||||||
return parseElementaryType(unescapedSelector)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func assembleArgs(args []interface{}) ([]ArgumentMarshaling, error) {
|
for {
|
||||||
arguments := make([]ArgumentMarshaling, 0)
|
rest = skipWhitespace(rest)
|
||||||
for i, arg := range args {
|
|
||||||
name := fmt.Sprintf("name%d", i)
|
var component ArgumentMarshaling
|
||||||
if s, ok := arg.(string); ok {
|
var err error
|
||||||
arguments = append(arguments, ArgumentMarshaling{
|
|
||||||
Name: name,
|
if rest[0] == '(' {
|
||||||
Type: s,
|
subComponents, subArraySuffix, newRest, subErr := parseTupleType(rest)
|
||||||
InternalType: s,
|
if subErr != nil {
|
||||||
Components: nil,
|
return nil, "", rest, fmt.Errorf("failed to parse nested tuple: %v", subErr)
|
||||||
Indexed: false,
|
|
||||||
})
|
|
||||||
} else if components, ok := arg.([]interface{}); ok {
|
|
||||||
subArgs, err := assembleArgs(components)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to assemble components: %v", err)
|
|
||||||
}
|
}
|
||||||
tupleType := "tuple"
|
rest = newRest
|
||||||
if len(subArgs) != 0 && subArgs[len(subArgs)-1].Type == "[]" {
|
rest = skipWhitespace(rest)
|
||||||
subArgs = subArgs[:len(subArgs)-1]
|
|
||||||
tupleType = "tuple[]"
|
paramName := fmt.Sprintf("param%d", paramIndex)
|
||||||
|
if len(rest) > 0 && (isAlpha(rest[0]) || isIdentifierSymbol(rest[0])) {
|
||||||
|
paramName, rest, err = parseIdentifier(rest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", rest, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component = ArgumentMarshaling{
|
||||||
|
Name: paramName,
|
||||||
|
Type: "tuple" + subArraySuffix,
|
||||||
|
Components: subComponents,
|
||||||
}
|
}
|
||||||
arguments = append(arguments, ArgumentMarshaling{
|
|
||||||
Name: name,
|
|
||||||
Type: tupleType,
|
|
||||||
InternalType: tupleType,
|
|
||||||
Components: subArgs,
|
|
||||||
Indexed: false,
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("failed to assemble args: unexpected type %T", arg)
|
var typeName string
|
||||||
|
typeName, rest, err = parseElementaryType(rest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", rest, fmt.Errorf("failed to parse type: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rest = skipWhitespace(rest)
|
||||||
|
|
||||||
|
paramName := fmt.Sprintf("param%d", paramIndex)
|
||||||
|
if len(rest) > 0 && (isAlpha(rest[0]) || isIdentifierSymbol(rest[0])) {
|
||||||
|
paramName, rest, err = parseIdentifier(rest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", rest, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component = ArgumentMarshaling{
|
||||||
|
Name: paramName,
|
||||||
|
Type: typeName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
components = append(components, component)
|
||||||
|
rest = skipWhitespace(rest)
|
||||||
|
|
||||||
|
if rest[0] == ')' {
|
||||||
|
rest = rest[1:]
|
||||||
|
arraySuffix := ""
|
||||||
|
for len(rest) > 0 && rest[0] == '[' {
|
||||||
|
endBracket := 1
|
||||||
|
for endBracket < len(rest) && rest[endBracket] != ']' {
|
||||||
|
endBracket++
|
||||||
|
}
|
||||||
|
if endBracket >= len(rest) {
|
||||||
|
return nil, "", rest, fmt.Errorf("unclosed array bracket")
|
||||||
|
}
|
||||||
|
arraySuffix += rest[:endBracket+1]
|
||||||
|
rest = rest[endBracket+1:]
|
||||||
|
}
|
||||||
|
return components, arraySuffix, rest, nil
|
||||||
|
} else if rest[0] == ',' {
|
||||||
|
rest = rest[1:]
|
||||||
|
paramIndex++
|
||||||
|
} else {
|
||||||
|
return nil, "", rest, fmt.Errorf("expected ',' or ')' in tuple, got '%c'", rest[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return arguments, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseParameterList parses a parameter list with optional indexed flags
|
// parseParameterList parses a parameter list with optional indexed flags
|
||||||
|
|
@ -287,61 +321,69 @@ func parseParameterList(params string, allowIndexed bool) ([]ArgumentMarshaling,
|
||||||
for params != "" {
|
for params != "" {
|
||||||
params = skipWhitespace(params)
|
params = skipWhitespace(params)
|
||||||
|
|
||||||
var typeStr interface{}
|
var arg ArgumentMarshaling
|
||||||
var rest string
|
var rest string
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if params[0] == '(' {
|
if params[0] == '(' {
|
||||||
typeStr, rest, err = parseCompositeType(params)
|
components, arraySuffix, newRest, tupleErr := parseTupleType(params)
|
||||||
} else {
|
if tupleErr != nil {
|
||||||
typeStr, rest, err = parseElementaryType(params)
|
return nil, fmt.Errorf("failed to parse tuple: %v", tupleErr)
|
||||||
}
|
}
|
||||||
|
rest = newRest
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse parameter type: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rest = skipWhitespace(rest)
|
|
||||||
|
|
||||||
indexed := false
|
|
||||||
if allowIndexed {
|
|
||||||
rest, indexed = parseKeyword(rest, "indexed")
|
|
||||||
rest = skipWhitespace(rest)
|
rest = skipWhitespace(rest)
|
||||||
}
|
|
||||||
|
|
||||||
paramName := fmt.Sprintf("param%d", paramIndex)
|
indexed := false
|
||||||
if len(rest) > 0 && (isAlpha(rest[0]) || isIdentifierSymbol(rest[0])) {
|
if allowIndexed {
|
||||||
var name string
|
rest, indexed = parseKeyword(rest, "indexed")
|
||||||
name, rest, err = parseIdentifier(rest)
|
rest = skipWhitespace(rest)
|
||||||
if err == nil {
|
|
||||||
paramName = name
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if s, ok := typeStr.(string); ok {
|
paramName := fmt.Sprintf("param%d", paramIndex)
|
||||||
arguments = append(arguments, ArgumentMarshaling{
|
if len(rest) > 0 && (isAlpha(rest[0]) || isIdentifierSymbol(rest[0])) {
|
||||||
Name: paramName,
|
paramName, rest, err = parseIdentifier(rest)
|
||||||
Type: s,
|
if err != nil {
|
||||||
Indexed: indexed,
|
return nil, err
|
||||||
})
|
}
|
||||||
} else if components, ok := typeStr.([]interface{}); ok {
|
|
||||||
subArgs, err := assembleArgs(components)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to assemble tuple components: %v", err)
|
|
||||||
}
|
}
|
||||||
tupleType := "tuple"
|
|
||||||
if len(subArgs) != 0 && subArgs[len(subArgs)-1].Type == "[]" {
|
arg = ArgumentMarshaling{
|
||||||
subArgs = subArgs[:len(subArgs)-1]
|
|
||||||
tupleType = "tuple[]"
|
|
||||||
}
|
|
||||||
arguments = append(arguments, ArgumentMarshaling{
|
|
||||||
Name: paramName,
|
Name: paramName,
|
||||||
Type: tupleType,
|
Type: "tuple" + arraySuffix,
|
||||||
Components: subArgs,
|
Components: components,
|
||||||
Indexed: indexed,
|
Indexed: indexed,
|
||||||
})
|
}
|
||||||
|
} else {
|
||||||
|
var typeName string
|
||||||
|
typeName, rest, err = parseElementaryType(params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse parameter type: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rest = skipWhitespace(rest)
|
||||||
|
|
||||||
|
indexed := false
|
||||||
|
if allowIndexed {
|
||||||
|
rest, indexed = parseKeyword(rest, "indexed")
|
||||||
|
rest = skipWhitespace(rest)
|
||||||
|
}
|
||||||
|
|
||||||
|
paramName := fmt.Sprintf("param%d", paramIndex)
|
||||||
|
if len(rest) > 0 && (isAlpha(rest[0]) || isIdentifierSymbol(rest[0])) {
|
||||||
|
paramName, rest, err = parseIdentifier(rest)
|
||||||
|
if err == nil && paramName != "" {
|
||||||
|
rest = skipWhitespace(rest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
arg = ArgumentMarshaling{
|
||||||
|
Name: paramName,
|
||||||
|
Type: typeName,
|
||||||
|
Indexed: indexed,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
arguments = append(arguments, arg)
|
||||||
rest = skipWhitespace(rest)
|
rest = skipWhitespace(rest)
|
||||||
|
|
||||||
if rest == "" {
|
if rest == "" {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue