mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-03-01 00:53: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 (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
|
@ -45,9 +46,113 @@ func normalizeArgument(arg ArgumentMarshaling, isEvent bool) map[string]interfac
|
|||
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
|
||||
// and returns a JSON array. Comments and empty lines are skipped.
|
||||
func parseHumanReadableABIArray(signatures []string) ([]byte, error) {
|
||||
structs, err := parseStructDefinitions(signatures)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var results []map[string]interface{}
|
||||
for _, sig := range signatures {
|
||||
sig = skipWhitespace(sig)
|
||||
|
|
@ -78,6 +183,9 @@ func parseHumanReadableABIArray(signatures []string) ([]byte, error) {
|
|||
}
|
||||
}
|
||||
if inputs, ok := result["inputs"].([]ArgumentMarshaling); ok {
|
||||
if err := expandStructReferences(inputs, structs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
normInputs := make([]map[string]interface{}, len(inputs))
|
||||
for i, inp := range inputs {
|
||||
normInputs[i] = normalizeArgument(inp, isEvent)
|
||||
|
|
@ -85,6 +193,9 @@ func parseHumanReadableABIArray(signatures []string) ([]byte, error) {
|
|||
normalized["inputs"] = normInputs
|
||||
}
|
||||
if outputs, ok := result["outputs"].([]ArgumentMarshaling); ok {
|
||||
if err := expandStructReferences(outputs, structs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
normOutputs := make([]map[string]interface{}, len(outputs))
|
||||
for i, out := range outputs {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -202,76 +202,110 @@ func parseElementaryType(unescapedSelector string) (string, string, error) {
|
|||
return parsedType, rest, nil
|
||||
}
|
||||
|
||||
func parseCompositeType(unescapedSelector string) ([]interface{}, string, error) {
|
||||
if len(unescapedSelector) == 0 || unescapedSelector[0] != '(' {
|
||||
return nil, "", fmt.Errorf("expected '(', got %c", unescapedSelector[0])
|
||||
// parseTupleType parses inline tuple syntax like (string denom, uint256 amount)[]
|
||||
func parseTupleType(params string) ([]ArgumentMarshaling, string, string, error) {
|
||||
if params == "" || params[0] != '(' {
|
||||
return nil, "", params, fmt.Errorf("expected '(' at start of tuple")
|
||||
}
|
||||
parsedType, rest, err := parseType(unescapedSelector[1:])
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to parse type: %v", err)
|
||||
}
|
||||
result := []interface{}{parsedType}
|
||||
for len(rest) > 0 && rest[0] != ')' {
|
||||
parsedType, rest, err = parseType(rest[1:])
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to parse type: %v", err)
|
||||
|
||||
rest := params[1:]
|
||||
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:]
|
||||
}
|
||||
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) {
|
||||
if len(unescapedSelector) == 0 {
|
||||
return nil, "", errors.New("empty type")
|
||||
}
|
||||
if unescapedSelector[0] == '(' {
|
||||
return parseCompositeType(unescapedSelector)
|
||||
} else {
|
||||
return parseElementaryType(unescapedSelector)
|
||||
}
|
||||
}
|
||||
var components []ArgumentMarshaling
|
||||
paramIndex := 0
|
||||
|
||||
func assembleArgs(args []interface{}) ([]ArgumentMarshaling, error) {
|
||||
arguments := make([]ArgumentMarshaling, 0)
|
||||
for i, arg := range args {
|
||||
name := fmt.Sprintf("name%d", i)
|
||||
if s, ok := arg.(string); ok {
|
||||
arguments = append(arguments, ArgumentMarshaling{
|
||||
Name: name,
|
||||
Type: s,
|
||||
InternalType: s,
|
||||
Components: nil,
|
||||
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)
|
||||
for {
|
||||
rest = skipWhitespace(rest)
|
||||
|
||||
var component ArgumentMarshaling
|
||||
var err error
|
||||
|
||||
if rest[0] == '(' {
|
||||
subComponents, subArraySuffix, newRest, subErr := parseTupleType(rest)
|
||||
if subErr != nil {
|
||||
return nil, "", rest, fmt.Errorf("failed to parse nested tuple: %v", subErr)
|
||||
}
|
||||
tupleType := "tuple"
|
||||
if len(subArgs) != 0 && subArgs[len(subArgs)-1].Type == "[]" {
|
||||
subArgs = subArgs[:len(subArgs)-1]
|
||||
tupleType = "tuple[]"
|
||||
rest = newRest
|
||||
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: "tuple" + subArraySuffix,
|
||||
Components: subComponents,
|
||||
}
|
||||
arguments = append(arguments, ArgumentMarshaling{
|
||||
Name: name,
|
||||
Type: tupleType,
|
||||
InternalType: tupleType,
|
||||
Components: subArgs,
|
||||
Indexed: false,
|
||||
})
|
||||
} 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
|
||||
|
|
@ -287,61 +321,69 @@ func parseParameterList(params string, allowIndexed bool) ([]ArgumentMarshaling,
|
|||
for params != "" {
|
||||
params = skipWhitespace(params)
|
||||
|
||||
var typeStr interface{}
|
||||
var arg ArgumentMarshaling
|
||||
var rest string
|
||||
var err error
|
||||
|
||||
if params[0] == '(' {
|
||||
typeStr, rest, err = parseCompositeType(params)
|
||||
} else {
|
||||
typeStr, 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")
|
||||
components, arraySuffix, newRest, tupleErr := parseTupleType(params)
|
||||
if tupleErr != nil {
|
||||
return nil, fmt.Errorf("failed to parse tuple: %v", tupleErr)
|
||||
}
|
||||
rest = newRest
|
||||
rest = skipWhitespace(rest)
|
||||
}
|
||||
|
||||
paramName := fmt.Sprintf("param%d", paramIndex)
|
||||
if len(rest) > 0 && (isAlpha(rest[0]) || isIdentifierSymbol(rest[0])) {
|
||||
var name string
|
||||
name, rest, err = parseIdentifier(rest)
|
||||
if err == nil {
|
||||
paramName = name
|
||||
indexed := false
|
||||
if allowIndexed {
|
||||
rest, indexed = parseKeyword(rest, "indexed")
|
||||
rest = skipWhitespace(rest)
|
||||
}
|
||||
}
|
||||
|
||||
if s, ok := typeStr.(string); ok {
|
||||
arguments = append(arguments, ArgumentMarshaling{
|
||||
Name: paramName,
|
||||
Type: s,
|
||||
Indexed: indexed,
|
||||
})
|
||||
} 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)
|
||||
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, err
|
||||
}
|
||||
}
|
||||
tupleType := "tuple"
|
||||
if len(subArgs) != 0 && subArgs[len(subArgs)-1].Type == "[]" {
|
||||
subArgs = subArgs[:len(subArgs)-1]
|
||||
tupleType = "tuple[]"
|
||||
}
|
||||
arguments = append(arguments, ArgumentMarshaling{
|
||||
|
||||
arg = ArgumentMarshaling{
|
||||
Name: paramName,
|
||||
Type: tupleType,
|
||||
Components: subArgs,
|
||||
Type: "tuple" + arraySuffix,
|
||||
Components: components,
|
||||
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)
|
||||
|
||||
if rest == "" {
|
||||
|
|
|
|||
Loading…
Reference in a new issue