diff --git a/accounts/abi/human_readable_test.go b/accounts/abi/human_readable_test.go index 96425be9c6..52e3045637 100644 --- a/accounts/abi/human_readable_test.go +++ b/accounts/abi/human_readable_test.go @@ -68,9 +68,14 @@ func parseHumanReadableABIArray(signatures []string) ([]byte, error) { } isEvent := resultType == "event" isFunction := resultType == "function" + isConstructor := resultType == "constructor" + isFallback := resultType == "fallback" + isReceive := resultType == "receive" - if name, ok := result["name"]; ok { - normalized["name"] = name + if !isConstructor && !isFallback && !isReceive { + if name, ok := result["name"]; ok { + normalized["name"] = name + } } if inputs, ok := result["inputs"].([]ArgumentMarshaling); ok { normInputs := make([]map[string]interface{}, len(inputs)) @@ -169,6 +174,53 @@ func TestParseHumanReadableABI(t *testing.T) { } ]`, }, + { + name: "constructor", + input: []string{"constructor(address owner, uint256 initialSupply)"}, + expected: `[ + { + "type": "constructor", + "inputs": [ + {"name": "owner", "type": "address"}, + {"name": "initialSupply", "type": "uint256"} + ], + "stateMutability": "nonpayable" + } + ]`, + }, + { + name: "constructor payable", + input: []string{"constructor(address owner) payable"}, + expected: `[ + { + "type": "constructor", + "inputs": [ + {"name": "owner", "type": "address"} + ], + "stateMutability": "payable" + } + ]`, + }, + { + name: "fallback function", + input: []string{"fallback()"}, + expected: `[ + { + "type": "fallback", + "stateMutability": "nonpayable" + } + ]`, + }, + { + name: "receive function", + input: []string{"receive() payable"}, + expected: `[ + { + "type": "receive", + "stateMutability": "payable" + } + ]`, + }, { name: "multiple functions", input: []string{ @@ -384,6 +436,48 @@ func TestParseHumanReadableABI(t *testing.T) { } ]`, }, + { + name: "int and uint without explicit sizes normalize to 256 bits", + input: []string{ + "function testIntUint(int value1, uint value2)", + "function testArrays(int[] values1, uint[10] values2)", + "function testMixed(int value1, uint value2, int8 value3, uint256 value4)", + }, + expected: `[ + { + "type": "function", + "name": "testIntUint", + "inputs": [ + {"name": "value1", "type": "int256"}, + {"name": "value2", "type": "uint256"} + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "testArrays", + "inputs": [ + {"name": "values1", "type": "int256[]"}, + {"name": "values2", "type": "uint256[10]"} + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "testMixed", + "inputs": [ + {"name": "value1", "type": "int256"}, + {"name": "value2", "type": "uint256"}, + {"name": "value3", "type": "int8"}, + {"name": "value4", "type": "uint256"} + ], + "outputs": [], + "stateMutability": "nonpayable" + } + ]`, + }, } for _, tt := range tests { diff --git a/accounts/abi/selector_parser.go b/accounts/abi/selector_parser.go index 91cef37a3b..cec41e0d8b 100644 --- a/accounts/abi/selector_parser.go +++ b/accounts/abi/selector_parser.go @@ -51,6 +51,18 @@ type ABIMarshaling map[string]interface{} func ParseHumanReadableABI(signature string) (ABIMarshaling, error) { signature = skipWhitespace(signature) + if strings.HasPrefix(signature, "constructor") { + return ParseConstructor(signature) + } + + if strings.HasPrefix(signature, "fallback") { + return ParseFallback(signature) + } + + if strings.HasPrefix(signature, "receive") { + return ParseReceive(signature) + } + if strings.HasPrefix(signature, "event ") || (strings.Contains(signature, "(") && strings.Contains(signature, "indexed")) { event, err := ParseEvent(signature) if err != nil { @@ -151,6 +163,23 @@ func parseKeyword(s string, keyword string) (string, bool) { return skipWhitespace(rest), true } +// normalizeType normalizes bare int/uint to int256/uint256, including arrays +func normalizeType(typeName string) string { + if typeName == "int" { + return "int256" + } + if typeName == "uint" { + return "uint256" + } + if strings.HasPrefix(typeName, "int[") { + return "int256" + typeName[3:] + } + if strings.HasPrefix(typeName, "uint[") { + return "uint256" + typeName[4:] + } + return typeName +} + func parseElementaryType(unescapedSelector string) (string, string, error) { parsedType, rest, err := parseToken(unescapedSelector, false) if err != nil { @@ -169,6 +198,7 @@ func parseElementaryType(unescapedSelector string) (string, string, error) { parsedType = parsedType + string(rest[0]) rest = rest[1:] } + parsedType = normalizeType(parsedType) return parsedType, rest, nil } @@ -431,6 +461,115 @@ func ParseError(unescapedSelector string) (ErrorMarshaling, error) { }, nil } +// ParseConstructor parses a constructor signature +func ParseConstructor(signature string) (ABIMarshaling, error) { + signature = skipWhitespace(signature) + + rest, _ := parseKeyword(signature, "constructor") + rest = skipWhitespace(rest) + + if len(rest) == 0 || rest[0] != '(' { + return nil, fmt.Errorf("expected '(' after constructor keyword") + } + rest = rest[1:] + + parenCount := 1 + paramEnd := 0 + for i := 0; i < len(rest); i++ { + if rest[i] == '(' { + parenCount++ + } else if rest[i] == ')' { + parenCount-- + if parenCount == 0 { + paramEnd = i + break + } + } + } + + if parenCount != 0 { + return nil, fmt.Errorf("unbalanced parentheses in constructor signature") + } + + paramsStr := rest[:paramEnd] + arguments, err := parseParameterList(paramsStr, false) + if err != nil { + return nil, fmt.Errorf("failed to parse constructor parameters: %v", err) + } + + rest = skipWhitespace(rest[paramEnd+1:]) + + stateMutability := "nonpayable" + if newRest, found := parseKeyword(rest, "payable"); found { + stateMutability = "payable" + rest = newRest + } + + result := make(ABIMarshaling) + result["type"] = "constructor" + result["inputs"] = arguments + result["stateMutability"] = stateMutability + return result, nil +} + +// ParseFallback parses a fallback function signature +// ParseFallback parses a fallback function signature (no parameters allowed) +func ParseFallback(signature string) (ABIMarshaling, error) { + signature = skipWhitespace(signature) + + rest, _ := parseKeyword(signature, "fallback") + rest = skipWhitespace(rest) + + if len(rest) == 0 || rest[0] != '(' { + return nil, fmt.Errorf("expected '(' after fallback keyword") + } + rest = rest[1:] + + if len(rest) == 0 || rest[0] != ')' { + return nil, fmt.Errorf("fallback function cannot have parameters") + } + rest = skipWhitespace(rest[1:]) + + stateMutability := "nonpayable" + if newRest, found := parseKeyword(rest, "payable"); found { + stateMutability = "payable" + rest = newRest + } + + result := make(ABIMarshaling) + result["type"] = "fallback" + result["stateMutability"] = stateMutability + return result, nil +} + +// ParseReceive parses a receive function signature (no parameters, always payable) +func ParseReceive(signature string) (ABIMarshaling, error) { + signature = skipWhitespace(signature) + + rest, _ := parseKeyword(signature, "receive") + rest = skipWhitespace(rest) + + if len(rest) == 0 || rest[0] != '(' { + return nil, fmt.Errorf("expected '(' after receive keyword") + } + rest = rest[1:] + + if len(rest) == 0 || rest[0] != ')' { + return nil, fmt.Errorf("receive function cannot have parameters") + } + rest = skipWhitespace(rest[1:]) + + stateMutability := "payable" + if newRest, found := parseKeyword(rest, "payable"); found { + rest = newRest + } + + result := make(ABIMarshaling) + result["type"] = "receive" + result["stateMutability"] = stateMutability + return result, nil +} + func ParseSelector(unescapedSelector string) (SelectorMarshaling, error) { unescapedSelector = skipWhitespace(unescapedSelector)