Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions accounts/abi/argument.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,28 @@ func (arguments Arguments) UnpackIntoMap(v map[string]any, data []byte) error {
return nil
}

// UnpackIntoMap performs the operation hexdata -> mapping of argument name to argument value converted to string.
func (arguments Arguments) UnpackIntoMapAsStrings(v map[string]interface{}, data []byte) error {
// Make sure map is not nil
if v == nil {
return errors.New("abi: cannot unpack into a nil map")
}
if len(data) == 0 {
if len(arguments.NonIndexed()) != 0 {
return errors.New("abi: attempting to unmarshall an empty string while arguments are expected")
}
return nil // Nothing to unmarshal, return
}
marshalledValues, err := arguments.UnpackValuesAsStrings(data)
if err != nil {
return err
}
for i, arg := range arguments.NonIndexed() {
v[arg.Name] = marshalledValues[i]
}
return nil
}

// Copy performs the operation go format -> provided struct.
func (arguments Arguments) Copy(v any, values []any) error {
// make sure the passed value is arguments pointer
Expand Down Expand Up @@ -219,6 +241,40 @@ func (arguments Arguments) UnpackValues(data []byte) ([]any, error) {
return retval, nil
}

// UnpackValuesAsStrings can be used to unpack ABI-encoded hexdata according to the ABI-specification,
// without supplying a struct to unpack into. Instead, this method returns a list containing the
// values converted to strings. An atomic argument will be a list with one element.
func (arguments Arguments) UnpackValuesAsStrings(data []byte) ([]interface{}, error) {
nonIndexedArgs := arguments.NonIndexed()
retval := make([]interface{}, 0, len(nonIndexedArgs))
virtualArgs := 0
for index, arg := range nonIndexedArgs {
marshalledValue, err := toString((index+virtualArgs)*32, arg.Type, data)
if err != nil {
return nil, err
}
if arg.Type.T == ArrayTy && !isDynamicType(arg.Type) {
// If we have a static array, like [3]uint256, these are coded as
// just like uint256,uint256,uint256.
// This means that we need to add two 'virtual' arguments when
// we count the index from now on.
//
// Array values nested multiple levels deep are also encoded inline:
// [2][3]uint256: uint256,uint256,uint256,uint256,uint256,uint256
//
// Calculate the full array size to get the correct offset for the next argument.
// Decrement it by 1, as the normal index increment is still applied.
virtualArgs += getTypeSize(arg.Type)/32 - 1
} else if arg.Type.T == TupleTy && !isDynamicType(arg.Type) {
// If we have a static tuple, like (uint256, bool, uint256), these are
// coded as just like uint256,bool,uint256
virtualArgs += getTypeSize(arg.Type)/32 - 1
}
retval = append(retval, marshalledValue)
}
return retval, nil
}

// PackValues performs the operation Go format -> Hexdata.
// It is the semantic opposite of UnpackValues.
func (arguments Arguments) PackValues(args []any) ([]byte, error) {
Expand Down
194 changes: 158 additions & 36 deletions accounts/abi/selector_parser.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,9 @@
// Copyright 2022 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package abi

import (
"errors"
"fmt"
"strings"
)

type SelectorMarshaling struct {
Expand All @@ -35,6 +20,10 @@ func isAlpha(c byte) bool {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
}

func isSpace(c byte) bool {
return c == ' '
}

func isIdentifierSymbol(c byte) bool {
return c == '$' || c == '_'
}
Expand All @@ -50,7 +39,7 @@ func parseToken(unescapedSelector string, isIdent bool) (string, string, error)
}
for position < len(unescapedSelector) {
char := unescapedSelector[position]
if !(isAlpha(char) || isDigit(char) || (isIdent && isIdentifierSymbol(char))) {
if !(isAlpha(char) || isDigit(char) || (isIdent && isIdentifierSymbol(char)) || (!isIdent && isSpace(char))) {
break
}
position++
Expand All @@ -62,11 +51,15 @@ func parseIdentifier(unescapedSelector string) (string, string, error) {
return parseToken(unescapedSelector, true)
}

func parseElementaryType(unescapedSelector string) (string, string, error) {
parsedType, rest, err := parseToken(unescapedSelector, false)
func parseElementaryType(unescapedSelector string) (parsedType string, rest string, err error) {
parsedType, rest, err = parseToken(unescapedSelector, false)
if err != nil {
return "", "", fmt.Errorf("failed to parse elementary type: %v", err)
}
parts := strings.Split(parsedType, " ")
if len(parts) > 1 {
parsedType = parsedType[len(parts[0])+1:]
}
// handle arrays
for len(rest) > 0 && rest[0] == '[' {
parsedType = parsedType + string(rest[0])
Expand All @@ -84,15 +77,38 @@ func parseElementaryType(unescapedSelector string) (string, string, error) {
return parsedType, rest, nil
}

func parseCompositeType(unescapedSelector string) ([]interface{}, string, error) {
func parseElementaryTypeWithName(unescapedSelector string) (parsedType string, rest string, err 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) (result []interface{}, rest string, err error) {
if len(unescapedSelector) == 0 || unescapedSelector[0] != '(' {
return nil, "", fmt.Errorf("expected '(', got %c", unescapedSelector[0])
return nil, "", fmt.Errorf("expected '(...', got %s", unescapedSelector)
}
parsedType, rest, err := parseType(unescapedSelector[1:])
var parsedType interface{}
parsedType, rest, err = parseType(unescapedSelector[1:])
if err != nil {
return nil, "", fmt.Errorf("failed to parse type: %v", err)
}
result := []interface{}{parsedType}
result = []interface{}{parsedType}
for len(rest) > 0 && rest[0] != ')' {
parsedType, rest, err = parseType(rest[1:])
if err != nil {
Expand All @@ -109,7 +125,77 @@ func parseCompositeType(unescapedSelector string) ([]interface{}, string, error)
return result, rest[1:], nil
}

func parseCompositeTypeWithName(unescapedSelector string) (result []interface{}, rest string, err error) {
var name string
parts := strings.Split(unescapedSelector, " ")
if len(parts) < 2 {
return nil, "", fmt.Errorf("expected name in the beginning, got %s", unescapedSelector)
} else {
name = parts[0]
unescapedSelector = unescapedSelector[len(parts[0])+1:]
}
if len(unescapedSelector) == 0 || unescapedSelector[0] != '(' {
return nil, "", fmt.Errorf("expected '(...', got %s", unescapedSelector)
}
result = []interface{}{name}
var parsedType interface{}
var counter int64
parsedType, rest, err = parseTypeWithName(unescapedSelector[1:], counter)
if err != nil {
return nil, "", fmt.Errorf("failed to parse type: %v", err)
}
result = append(result, parsedType)
for len(rest) > 0 && rest[0] != ')' {
counter += 1
parsedType, rest, err = parseTypeWithName(rest[1:], counter)
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)
}
if len(rest) >= 3 && rest[1] == '[' && rest[2] == ']' {
return append(result, "[]"), rest[3:], nil
}
return result, rest[1:], nil
}

func parseFunctionsArgs(unescapedSelector string) (result []interface{}, rest string, err error) {
if len(unescapedSelector) == 0 || unescapedSelector[0] != '(' {
return nil, "", fmt.Errorf("expected '(...', got %s", unescapedSelector)
}
var parsedType interface{}
var counter int64
parsedType, rest, err = parseTypeWithName(unescapedSelector[1:], counter)
if err != nil {
return nil, "", fmt.Errorf("failed to parse type: %v", err)
}
result = []interface{}{parsedType}

for len(rest) > 0 && rest[0] != ')' {
counter += 1
parsedType, rest, err = parseTypeWithName(rest[1:], counter)
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)
}
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) {
parts := strings.Split(unescapedSelector, " ")
if len(parts) > 1 {
unescapedSelector = unescapedSelector[len(parts[0])+1:]
}
if len(unescapedSelector) == 0 {
return nil, "", errors.New("empty type")
}
Expand All @@ -120,15 +206,49 @@ func parseType(unescapedSelector string) (interface{}, string, error) {
}
}

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)
func parseTypeWithName(unescapedSelector string, counter int64) (interface{}, string, error) {
name, rest, _ := parseIdentifier(unescapedSelector)
if len(rest) > 0 && rest[0] == ' ' {
unescapedSelector = unescapedSelector[len(name)+1:]
} else {
name = fmt.Sprintf("name%d", counter)
}
if len(unescapedSelector) == 0 {
return nil, "", errors.New("empty type")
}
if unescapedSelector[0] == '(' {
return parseCompositeTypeWithName(fmt.Sprintf("%v %v", name, unescapedSelector))
} else {
return parseElementaryTypeWithName(fmt.Sprintf("%v %v", name, unescapedSelector))
}
}

func assembleArgs(args []interface{}) (arguments []ArgumentMarshaling, err error) {
arguments = make([]ArgumentMarshaling, 0)
for _, arg := range args {
var name string
if s, ok := arg.(string); ok {
arguments = append(arguments, ArgumentMarshaling{name, s, s, nil, false})
if s == "[]" {
arguments = append(arguments, ArgumentMarshaling{Name: name, Type: s, InternalType: s})
continue
}
parts := strings.Split(s, " ")
if len(parts) < 2 {
return nil, fmt.Errorf("no name in arg %s", s)
} else {
name = parts[0]
s = s[len(name)+1:]
}
arguments = append(arguments, ArgumentMarshaling{Name: name, Type: s, InternalType: s})
} else if components, ok := arg.([]interface{}); ok {
subArgs, err := assembleArgs(components)
var subArgs []ArgumentMarshaling
if len(components) < 2 {
return nil, fmt.Errorf("no name in components %s", components)
} else {
name = components[0].(string)
components = components[1:]
}
subArgs, err = assembleArgs(components)
if err != nil {
return nil, fmt.Errorf("failed to assemble components: %v", err)
}
Expand All @@ -137,7 +257,7 @@ func assembleArgs(args []interface{}) ([]ArgumentMarshaling, error) {
subArgs = subArgs[:len(subArgs)-1]
tupleType = "tuple[]"
}
arguments = append(arguments, ArgumentMarshaling{name, tupleType, tupleType, subArgs, false})
arguments = append(arguments, ArgumentMarshaling{Name: name, Type: tupleType, InternalType: tupleType, Components: subArgs})
} else {
return nil, fmt.Errorf("failed to assemble args: unexpected type %T", arg)
}
Expand All @@ -149,16 +269,17 @@ func assembleArgs(args []interface{}) ([]ArgumentMarshaling, error) {
// 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)
func ParseSelector(unescapedSelector string) (m SelectorMarshaling, err error) {
var name, rest string
name, rest, err = parseIdentifier(unescapedSelector)
if err != nil {
return SelectorMarshaling{}, fmt.Errorf("failed to parse selector '%s': %v", unescapedSelector, err)
}
args := []interface{}{}
args := make([]interface{}, 0)
if len(rest) >= 2 && rest[0] == '(' && rest[1] == ')' {
rest = rest[2:]
} else {
args, rest, err = parseCompositeType(rest)
args, rest, err = parseFunctionsArgs(rest)
if err != nil {
return SelectorMarshaling{}, fmt.Errorf("failed to parse selector '%s': %v", unescapedSelector, err)
}
Expand All @@ -168,7 +289,8 @@ func ParseSelector(unescapedSelector string) (SelectorMarshaling, error) {
}

// Reassemble the fake ABI and construct the JSON
fakeArgs, err := assembleArgs(args)
var fakeArgs []ArgumentMarshaling
fakeArgs, err = assembleArgs(args)
if err != nil {
return SelectorMarshaling{}, fmt.Errorf("failed to parse selector: %v", err)
}
Expand Down
10 changes: 10 additions & 0 deletions accounts/abi/selector_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"fmt"
"log"
"reflect"
"strings"
"testing"
)

Expand All @@ -30,6 +31,11 @@ func TestParseSelector(t *testing.T) {
for i, typeOrComponents := range types {
name := fmt.Sprintf("name%d", i)
if typeName, ok := typeOrComponents.(string); ok {
names := strings.Split(typeName, " ")
if len(names) > 1 {
name = names[0]
typeName = typeName[len(name)+1:]
}
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})
Expand Down Expand Up @@ -60,6 +66,10 @@ func TestParseSelector(t *testing.T) {
mkType([][]ArgumentMarshaling{mkType("uint256", "uint256")}, "bytes32[]")},
{"singleArrayNestWithArrayAndArray((uint256[],address[2],uint8[4][][5])[],bytes32[])", "singleArrayNestWithArrayAndArray",
mkType([][]ArgumentMarshaling{mkType("uint256[]", "address[2]", "uint8[4][][5]")}, "bytes32[]")},
{"transfer(to address,amount uint256)", "transfer",
mkType("to address", "amount uint256")},
{"execute(((a address,c uint256,b uint8),(d uint8,e bytes)),(address,uint256),(uint8,(uint256,address),(uint256,address),address,address,bytes),(uint256,bytes),(uint256,bytes))", "execute",
mkType(mkType(mkType("a address", "c uint256", "b uint8"), mkType("d uint8", "e bytes")), mkType("address", "uint256"), mkType("uint8", mkType("uint256", "address"), mkType("uint256", "address"), "address", "address", "bytes"), mkType("uint256", "bytes"), mkType("uint256", "bytes"))},
}
for i, tt := range tests {
selector, err := ParseSelector(tt.input)
Expand Down
Loading
Loading