accounts/abi: support constructor/fallback/receive

This commit is contained in:
mmsqe 2026-01-15 14:24:06 +08:00
parent bf541d1dfd
commit fd81bb1b9f
No known key found for this signature in database
GPG key ID: 1D6409A9D4025709
2 changed files with 235 additions and 2 deletions

View file

@ -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 {

View file

@ -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)