From 0b63915430e5921722004345228c5239a8784d6b Mon Sep 17 00:00:00 2001
From: Marius van der Wijden <m.vanderwijden@live.de>
Date: Tue, 12 May 2020 13:02:23 +0200
Subject: [PATCH] accounts/abi: allow overloaded argument names (#21060)

* accounts/abi: allow overloaded argument names

In solidity it is possible to create the following contract:
```
contract Overloader {
    struct F { uint _f; uint __f; uint f; }
    function f(F memory f) public {}
}
```
This however resulted in a panic in the abi package.

* accounts/abi fixed error handling
---
 accounts/abi/abi_test.go | 10 ++++++++--
 accounts/abi/type.go     | 23 ++++++++++++++++++++---
 2 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go
index 713dbebbe..f41c91aa7 100644
--- a/accounts/abi/abi_test.go
+++ b/accounts/abi/abi_test.go
@@ -57,7 +57,8 @@ const jsondata = `
 	{ "type" : "function", "name" : "fixedArrBytes", "stateMutability" : "view", "inputs" : [ { "name" : "bytes", "type" : "bytes" }, { "name" : "fixedArr", "type" : "uint256[2]" } ] },
 	{ "type" : "function", "name" : "mixedArrStr", "stateMutability" : "view", "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr", "type" : "uint256[2]" }, { "name" : "dynArr", "type" : "uint256[]" } ] },
 	{ "type" : "function", "name" : "doubleFixedArrStr", "stateMutability" : "view", "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr1", "type" : "uint256[2]" }, { "name" : "fixedArr2", "type" : "uint256[3]" } ] },
-	{ "type" : "function", "name" : "multipleMixedArrStr", "stateMutability" : "view", "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr1", "type" : "uint256[2]" }, { "name" : "dynArr", "type" : "uint256[]" }, { "name" : "fixedArr2", "type" : "uint256[3]" } ] }
+	{ "type" : "function", "name" : "multipleMixedArrStr", "stateMutability" : "view", "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr1", "type" : "uint256[2]" }, { "name" : "dynArr", "type" : "uint256[]" }, { "name" : "fixedArr2", "type" : "uint256[3]" } ] },
+	{ "type" : "function", "name" : "overloadedNames", "stateMutability" : "view", "inputs": [ { "components": [ { "internalType": "uint256", "name": "_f",	"type": "uint256" }, { "internalType": "uint256", "name": "__f", "type": "uint256"}, { "internalType": "uint256", "name": "f", "type": "uint256"}],"internalType": "struct Overloader.F", "name": "f","type": "tuple"}]}
 ]`
 
 var (
@@ -80,6 +81,10 @@ var (
 	Uint256ArrNested, _ = NewType("uint256[2][2]", "", nil)
 	Uint8ArrNested, _   = NewType("uint8[][2]", "", nil)
 	Uint8SliceNested, _ = NewType("uint8[][]", "", nil)
+	TupleF, _           = NewType("tuple", "struct Overloader.F", []ArgumentMarshaling{
+		{Name: "_f", Type: "uint256"},
+		{Name: "__f", Type: "uint256"},
+		{Name: "f", Type: "uint256"}})
 )
 
 var methods = map[string]Method{
@@ -108,6 +113,7 @@ var methods = map[string]Method{
 	"mixedArrStr":         NewMethod("mixedArrStr", "mixedArrStr", Function, "view", false, false, []Argument{{"str", String, false}, {"fixedArr", Uint256Arr2, false}, {"dynArr", Uint256Arr, false}}, nil),
 	"doubleFixedArrStr":   NewMethod("doubleFixedArrStr", "doubleFixedArrStr", Function, "view", false, false, []Argument{{"str", String, false}, {"fixedArr1", Uint256Arr2, false}, {"fixedArr2", Uint256Arr3, false}}, nil),
 	"multipleMixedArrStr": NewMethod("multipleMixedArrStr", "multipleMixedArrStr", Function, "view", false, false, []Argument{{"str", String, false}, {"fixedArr1", Uint256Arr2, false}, {"dynArr", Uint256Arr, false}, {"fixedArr2", Uint256Arr3, false}}, nil),
+	"overloadedNames":     NewMethod("overloadedNames", "overloadedNames", Function, "view", false, false, []Argument{{"f", TupleF, false}}, nil),
 }
 
 func TestReader(t *testing.T) {
@@ -117,7 +123,7 @@ func TestReader(t *testing.T) {
 
 	exp, err := JSON(strings.NewReader(jsondata))
 	if err != nil {
-		t.Error(err)
+		t.Fatal(err)
 	}
 
 	for name, expM := range exp.Methods {
diff --git a/accounts/abi/type.go b/accounts/abi/type.go
index 09d75fb88..5de8bc9ff 100644
--- a/accounts/abi/type.go
+++ b/accounts/abi/type.go
@@ -163,16 +163,19 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty
 			expression string // canonical parameter expression
 		)
 		expression += "("
+		overloadedNames := make(map[string]string)
 		for idx, c := range components {
 			cType, err := NewType(c.Type, c.InternalType, c.Components)
 			if err != nil {
 				return Type{}, err
 			}
-			if ToCamelCase(c.Name) == "" {
-				return Type{}, errors.New("abi: purely anonymous or underscored field is not supported")
+			fieldName, err := overloadedArgName(c.Name, overloadedNames)
+			if err != nil {
+				return Type{}, err
 			}
+			overloadedNames[fieldName] = fieldName
 			fields = append(fields, reflect.StructField{
-				Name: ToCamelCase(c.Name), // reflect.StructOf will panic for any exported field.
+				Name: fieldName, // reflect.StructOf will panic for any exported field.
 				Type: cType.getType(),
 				Tag:  reflect.StructTag("json:\"" + c.Name + "\""),
 			})
@@ -246,6 +249,20 @@ func (t Type) getType() reflect.Type {
 	}
 }
 
+func overloadedArgName(rawName string, names map[string]string) (string, error) {
+	fieldName := ToCamelCase(rawName)
+	if fieldName == "" {
+		return "", errors.New("abi: purely anonymous or underscored field is not supported")
+	}
+	// Handle overloaded fieldNames
+	_, ok := names[fieldName]
+	for idx := 0; ok; idx++ {
+		fieldName = fmt.Sprintf("%s%d", ToCamelCase(rawName), idx)
+		_, ok = names[fieldName]
+	}
+	return fieldName, nil
+}
+
 // String implements Stringer
 func (t Type) String() (out string) {
 	return t.stringKind
-- 
GitLab