mirror of https://github.com/ethereum/go-ethereum
signer/fourbyte: add support for nested types in selectors (#24407)
This replaces the simple selector parser in signer/fourbyte with one that can actually handle most types. The new parser is added in accounts/abi to also make it useable elsewhere.pull/24507/head
parent
8fddf27a98
commit
37f9d25ba0
@ -0,0 +1,152 @@ |
||||
package abi |
||||
|
||||
import ( |
||||
"fmt" |
||||
) |
||||
|
||||
type SelectorMarshaling struct { |
||||
Name string `json:"name"` |
||||
Type string `json:"type"` |
||||
Inputs []ArgumentMarshaling `json:"inputs"` |
||||
} |
||||
|
||||
func isDigit(c byte) bool { |
||||
return c >= '0' && c <= '9' |
||||
} |
||||
|
||||
func isAlpha(c byte) bool { |
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') |
||||
} |
||||
|
||||
func isIdentifierSymbol(c byte) bool { |
||||
return c == '$' || c == '_' |
||||
} |
||||
|
||||
func parseToken(unescapedSelector string, isIdent bool) (string, string, error) { |
||||
if len(unescapedSelector) == 0 { |
||||
return "", "", fmt.Errorf("empty token") |
||||
} |
||||
firstChar := unescapedSelector[0] |
||||
position := 1 |
||||
if !(isAlpha(firstChar) || (isIdent && isIdentifierSymbol(firstChar))) { |
||||
return "", "", fmt.Errorf("invalid token start: %c", firstChar) |
||||
} |
||||
for position < len(unescapedSelector) { |
||||
char := unescapedSelector[position] |
||||
if !(isAlpha(char) || isDigit(char) || (isIdent && isIdentifierSymbol(char))) { |
||||
break |
||||
} |
||||
position++ |
||||
} |
||||
return unescapedSelector[:position], unescapedSelector[position:], nil |
||||
} |
||||
|
||||
func parseIdentifier(unescapedSelector string) (string, string, error) { |
||||
return parseToken(unescapedSelector, true) |
||||
} |
||||
|
||||
func parseElementaryType(unescapedSelector string) (string, string, error) { |
||||
parsedType, rest, err := parseToken(unescapedSelector, false) |
||||
if err != nil { |
||||
return "", "", fmt.Errorf("failed to parse elementary type: %v", err) |
||||
} |
||||
// handle arrays
|
||||
for len(rest) > 0 && rest[0] == '[' { |
||||
parsedType = parsedType + string(rest[0]) |
||||
rest = rest[1:] |
||||
for len(rest) > 0 && isDigit(rest[0]) { |
||||
parsedType = parsedType + string(rest[0]) |
||||
rest = rest[1:] |
||||
} |
||||
if len(rest) == 0 || rest[0] != ']' { |
||||
return "", "", fmt.Errorf("failed to parse array: expected ']', got %c", unescapedSelector[0]) |
||||
} |
||||
parsedType = parsedType + string(rest[0]) |
||||
rest = rest[1:] |
||||
} |
||||
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]) |
||||
} |
||||
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) |
||||
} |
||||
result = append(result, parsedType) |
||||
} |
||||
if len(rest) == 0 || rest[0] != ')' { |
||||
return nil, "", fmt.Errorf("expected ')', got '%s'", rest) |
||||
} |
||||
return result, rest[1:], nil |
||||
} |
||||
|
||||
func parseType(unescapedSelector string) (interface{}, string, error) { |
||||
if len(unescapedSelector) == 0 { |
||||
return nil, "", fmt.Errorf("empty type") |
||||
} |
||||
if unescapedSelector[0] == '(' { |
||||
return parseCompositeType(unescapedSelector) |
||||
} else { |
||||
return parseElementaryType(unescapedSelector) |
||||
} |
||||
} |
||||
|
||||
func assembleArgs(args []interface{}) ([]ArgumentMarshaling, error) { |
||||
arguments := make([]ArgumentMarshaling, 0) |
||||
for i, arg := range args { |
||||
// generate dummy name to avoid unmarshal issues
|
||||
name := fmt.Sprintf("name%d", i) |
||||
if s, ok := arg.(string); ok { |
||||
arguments = append(arguments, ArgumentMarshaling{name, s, s, nil, 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) |
||||
} |
||||
arguments = append(arguments, ArgumentMarshaling{name, "tuple", "tuple", subArgs, false}) |
||||
} else { |
||||
return nil, fmt.Errorf("failed to assemble args: unexpected type %T", arg) |
||||
} |
||||
} |
||||
return arguments, nil |
||||
} |
||||
|
||||
// ParseSelector converts a method selector into a struct that can be JSON encoded
|
||||
// and consumed by other functions in this package.
|
||||
// Note, although uppercase letters are not part of the ABI spec, this function
|
||||
// still accepts it as the general format is valid.
|
||||
func ParseSelector(unescapedSelector string) (SelectorMarshaling, error) { |
||||
name, rest, err := parseIdentifier(unescapedSelector) |
||||
if err != nil { |
||||
return SelectorMarshaling{}, fmt.Errorf("failed to parse selector '%s': %v", unescapedSelector, err) |
||||
} |
||||
args := []interface{}{} |
||||
if len(rest) >= 2 && rest[0] == '(' && rest[1] == ')' { |
||||
rest = rest[2:] |
||||
} else { |
||||
args, rest, err = parseCompositeType(rest) |
||||
if err != nil { |
||||
return SelectorMarshaling{}, fmt.Errorf("failed to parse selector '%s': %v", unescapedSelector, err) |
||||
} |
||||
} |
||||
if len(rest) > 0 { |
||||
return SelectorMarshaling{}, fmt.Errorf("failed to parse selector '%s': unexpected string '%s'", unescapedSelector, rest) |
||||
} |
||||
|
||||
// Reassemble the fake ABI and constuct the JSON
|
||||
fakeArgs, err := assembleArgs(args) |
||||
if err != nil { |
||||
return SelectorMarshaling{}, fmt.Errorf("failed to parse selector: %v", err) |
||||
} |
||||
|
||||
return SelectorMarshaling{name, "function", fakeArgs}, nil |
||||
} |
@ -0,0 +1,54 @@ |
||||
package abi |
||||
|
||||
import ( |
||||
"fmt" |
||||
"log" |
||||
"reflect" |
||||
"testing" |
||||
) |
||||
|
||||
func TestParseSelector(t *testing.T) { |
||||
mkType := func(types ...interface{}) []ArgumentMarshaling { |
||||
var result []ArgumentMarshaling |
||||
for i, typeOrComponents := range types { |
||||
name := fmt.Sprintf("name%d", i) |
||||
if typeName, ok := typeOrComponents.(string); ok { |
||||
result = append(result, ArgumentMarshaling{name, typeName, typeName, nil, false}) |
||||
} else if components, ok := typeOrComponents.([]ArgumentMarshaling); ok { |
||||
result = append(result, ArgumentMarshaling{name, "tuple", "tuple", components, false}) |
||||
} else { |
||||
log.Fatalf("unexpected type %T", typeOrComponents) |
||||
} |
||||
} |
||||
return result |
||||
} |
||||
tests := []struct { |
||||
input string |
||||
name string |
||||
args []ArgumentMarshaling |
||||
}{ |
||||
{"noargs()", "noargs", []ArgumentMarshaling{}}, |
||||
{"simple(uint256,uint256,uint256)", "simple", mkType("uint256", "uint256", "uint256")}, |
||||
{"other(uint256,address)", "other", mkType("uint256", "address")}, |
||||
{"withArray(uint256[],address[2],uint8[4][][5])", "withArray", mkType("uint256[]", "address[2]", "uint8[4][][5]")}, |
||||
{"singleNest(bytes32,uint8,(uint256,uint256),address)", "singleNest", mkType("bytes32", "uint8", mkType("uint256", "uint256"), "address")}, |
||||
{"multiNest(address,(uint256[],uint256),((address,bytes32),uint256))", "multiNest", |
||||
mkType("address", mkType("uint256[]", "uint256"), mkType(mkType("address", "bytes32"), "uint256"))}, |
||||
} |
||||
for i, tt := range tests { |
||||
selector, err := ParseSelector(tt.input) |
||||
if err != nil { |
||||
t.Errorf("test %d: failed to parse selector '%v': %v", i, tt.input, err) |
||||
} |
||||
if selector.Name != tt.name { |
||||
t.Errorf("test %d: unexpected function name: '%s' != '%s'", i, selector.Name, tt.name) |
||||
} |
||||
|
||||
if selector.Type != "function" { |
||||
t.Errorf("test %d: unexpected type: '%s' != '%s'", i, selector.Type, "function") |
||||
} |
||||
if !reflect.DeepEqual(selector.Inputs, tt.args) { |
||||
t.Errorf("test %d: unexpected args: '%v' != '%v'", i, selector.Inputs, tt.args) |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue