Skip to content

Commit 84b3272

Browse files
lwhilerjl493456442
andauthored
accounts/abi/bind: fix duplicate field names in the generated go struct (ethereum#24924)
* accounts/abi/bind: fix duplicate field names in the generated go struct ethereum#24627 * accounts, cmd/abigen: resolve name conflicts * ci lint, accounts/abi: remove unused function overloadedArgName Co-authored-by: Gary Rong <[email protected]>
1 parent d9566e3 commit 84b3272

File tree

8 files changed

+136
-44
lines changed

8 files changed

+136
-44
lines changed

accounts/abi/abi.go

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ func (abi *ABI) UnmarshalJSON(data []byte) error {
164164
case "constructor":
165165
abi.Constructor = NewMethod("", "", Constructor, field.StateMutability, field.Constant, field.Payable, field.Inputs, nil)
166166
case "function":
167-
name := overloadedName(field.Name, func(s string) bool { _, ok := abi.Methods[s]; return ok })
167+
name := ResolveNameConflict(field.Name, func(s string) bool { _, ok := abi.Methods[s]; return ok })
168168
abi.Methods[name] = NewMethod(name, field.Name, Function, field.StateMutability, field.Constant, field.Payable, field.Inputs, field.Outputs)
169169
case "fallback":
170170
// New introduced function type in v0.6.0, check more detail
@@ -184,9 +184,11 @@ func (abi *ABI) UnmarshalJSON(data []byte) error {
184184
}
185185
abi.Receive = NewMethod("", "", Receive, field.StateMutability, field.Constant, field.Payable, nil, nil)
186186
case "event":
187-
name := overloadedName(field.Name, func(s string) bool { _, ok := abi.Events[s]; return ok })
187+
name := ResolveNameConflict(field.Name, func(s string) bool { _, ok := abi.Events[s]; return ok })
188188
abi.Events[name] = NewEvent(name, field.Name, field.Anonymous, field.Inputs)
189189
case "error":
190+
// Errors cannot be overloaded or overridden but are inherited,
191+
// no need to resolve the name conflict here.
190192
abi.Errors[field.Name] = NewError(field.Name, field.Inputs)
191193
default:
192194
return fmt.Errorf("abi: could not recognize type %v of field %v", field.Type, field.Name)
@@ -251,20 +253,3 @@ func UnpackRevert(data []byte) (string, error) {
251253
}
252254
return unpacked[0].(string), nil
253255
}
254-
255-
// overloadedName returns the next available name for a given thing.
256-
// Needed since solidity allows for overloading.
257-
//
258-
// e.g. if the abi contains Methods send, send1
259-
// overloadedName would return send2 for input send.
260-
//
261-
// overloadedName works for methods, events and errors.
262-
func overloadedName(rawName string, isAvail func(string) bool) string {
263-
name := rawName
264-
ok := isAvail(name)
265-
for idx := 0; ok; idx++ {
266-
name = fmt.Sprintf("%s%d", rawName, idx)
267-
ok = isAvail(name)
268-
}
269-
return name
270-
}

accounts/abi/bind/bind.go

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
9999
// Normalize the method for capital cases and non-anonymous inputs/outputs
100100
normalized := original
101101
normalizedName := methodNormalizer[lang](alias(aliases, original.Name))
102+
102103
// Ensure there is no duplicated identifier
103104
var identifiers = callIdentifiers
104105
if !original.IsConstant() {
@@ -108,6 +109,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
108109
return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName)
109110
}
110111
identifiers[normalizedName] = true
112+
111113
normalized.Name = normalizedName
112114
normalized.Inputs = make([]abi.Argument, len(original.Inputs))
113115
copy(normalized.Inputs, original.Inputs)
@@ -152,12 +154,22 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
152154
eventIdentifiers[normalizedName] = true
153155
normalized.Name = normalizedName
154156

157+
used := make(map[string]bool)
155158
normalized.Inputs = make([]abi.Argument, len(original.Inputs))
156159
copy(normalized.Inputs, original.Inputs)
157160
for j, input := range normalized.Inputs {
158161
if input.Name == "" {
159162
normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
160163
}
164+
// Event is a bit special, we need to define event struct in binding,
165+
// ensure there is no camel-case-style name conflict.
166+
for index := 0; ; index++ {
167+
if !used[capitalise(normalized.Inputs[j].Name)] {
168+
used[capitalise(normalized.Inputs[j].Name)] = true
169+
break
170+
}
171+
normalized.Inputs[j].Name = fmt.Sprintf("%s%d", normalized.Inputs[j].Name, index)
172+
}
161173
if hasStruct(input.Type) {
162174
bindStructType[lang](input.Type, structs)
163175
}
@@ -432,15 +444,22 @@ func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
432444
if s, exist := structs[id]; exist {
433445
return s.Name
434446
}
435-
var fields []*tmplField
447+
var (
448+
names = make(map[string]bool)
449+
fields []*tmplField
450+
)
436451
for i, elem := range kind.TupleElems {
437-
field := bindStructTypeGo(*elem, structs)
438-
fields = append(fields, &tmplField{Type: field, Name: capitalise(kind.TupleRawNames[i]), SolKind: *elem})
452+
name := capitalise(kind.TupleRawNames[i])
453+
name = abi.ResolveNameConflict(name, func(s string) bool { return names[s] })
454+
names[name] = true
455+
fields = append(fields, &tmplField{Type: bindStructTypeGo(*elem, structs), Name: name, SolKind: *elem})
439456
}
440457
name := kind.TupleRawName
441458
if name == "" {
442459
name = fmt.Sprintf("Struct%d", len(structs))
443460
}
461+
name = capitalise(name)
462+
444463
structs[id] = &tmplStruct{
445464
Name: name,
446465
Fields: fields,

accounts/abi/bind/bind_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1948,6 +1948,54 @@ var bindTests = []struct {
19481948
}
19491949
sim.Commit()
19501950
1951+
if _, err = bind.WaitDeployed(nil, sim, tx); err != nil {
1952+
t.Logf("Deployment tx: %+v", tx)
1953+
t.Errorf("bind.WaitDeployed(nil, %T, <deployment tx>) got err %v; want nil err", sim, err)
1954+
}
1955+
`,
1956+
},
1957+
{
1958+
name: `NameConflict`,
1959+
contract: `
1960+
// SPDX-License-Identifier: GPL-3.0
1961+
pragma solidity >=0.4.22 <0.9.0;
1962+
contract oracle {
1963+
struct request {
1964+
bytes data;
1965+
bytes _data;
1966+
}
1967+
event log (int msg, int _msg);
1968+
function addRequest(request memory req) public pure {}
1969+
function getRequest() pure public returns (request memory) {
1970+
return request("", "");
1971+
}
1972+
}
1973+
`,
1974+
bytecode: []string{`0x608060405234801561001057600080fd5b5061042b806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063c2bb515f1461003b578063cce7b04814610059575b600080fd5b610043610075565b60405161005091906101af565b60405180910390f35b610073600480360381019061006e91906103ac565b6100b5565b005b61007d6100b8565b604051806040016040528060405180602001604052806000815250815260200160405180602001604052806000815250815250905090565b50565b604051806040016040528060608152602001606081525090565b600081519050919050565b600082825260208201905092915050565b60005b8381101561010c5780820151818401526020810190506100f1565b8381111561011b576000848401525b50505050565b6000601f19601f8301169050919050565b600061013d826100d2565b61014781856100dd565b93506101578185602086016100ee565b61016081610121565b840191505092915050565b600060408301600083015184820360008601526101888282610132565b915050602083015184820360208601526101a28282610132565b9150508091505092915050565b600060208201905081810360008301526101c9818461016b565b905092915050565b6000604051905090565b600080fd5b600080fd5b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61022282610121565b810181811067ffffffffffffffff82111715610241576102406101ea565b5b80604052505050565b60006102546101d1565b90506102608282610219565b919050565b600080fd5b600080fd5b600080fd5b600067ffffffffffffffff82111561028f5761028e6101ea565b5b61029882610121565b9050602081019050919050565b82818337600083830152505050565b60006102c76102c284610274565b61024a565b9050828152602081018484840111156102e3576102e261026f565b5b6102ee8482856102a5565b509392505050565b600082601f83011261030b5761030a61026a565b5b813561031b8482602086016102b4565b91505092915050565b60006040828403121561033a576103396101e5565b5b610344604061024a565b9050600082013567ffffffffffffffff81111561036457610363610265565b5b610370848285016102f6565b600083015250602082013567ffffffffffffffff81111561039457610393610265565b5b6103a0848285016102f6565b60208301525092915050565b6000602082840312156103c2576103c16101db565b5b600082013567ffffffffffffffff8111156103e0576103df6101e0565b5b6103ec84828501610324565b9150509291505056fea264697066735822122033bca1606af9b6aeba1673f98c52003cec19338539fb44b86690ce82c51483b564736f6c634300080e0033`},
1975+
abi: []string{`[ { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "int256", "name": "msg", "type": "int256" }, { "indexed": false, "internalType": "int256", "name": "_msg", "type": "int256" } ], "name": "log", "type": "event" }, { "inputs": [ { "components": [ { "internalType": "bytes", "name": "data", "type": "bytes" }, { "internalType": "bytes", "name": "_data", "type": "bytes" } ], "internalType": "struct oracle.request", "name": "req", "type": "tuple" } ], "name": "addRequest", "outputs": [], "stateMutability": "pure", "type": "function" }, { "inputs": [], "name": "getRequest", "outputs": [ { "components": [ { "internalType": "bytes", "name": "data", "type": "bytes" }, { "internalType": "bytes", "name": "_data", "type": "bytes" } ], "internalType": "struct oracle.request", "name": "", "type": "tuple" } ], "stateMutability": "pure", "type": "function" } ]`},
1976+
imports: `
1977+
"math/big"
1978+
1979+
"github.com/ethereum/go-ethereum/accounts/abi/bind"
1980+
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
1981+
"github.com/ethereum/go-ethereum/core"
1982+
"github.com/ethereum/go-ethereum/crypto"
1983+
"github.com/ethereum/go-ethereum/eth/ethconfig"
1984+
`,
1985+
tester: `
1986+
var (
1987+
key, _ = crypto.GenerateKey()
1988+
user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
1989+
sim = backends.NewSimulatedBackend(core.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil)
1990+
)
1991+
defer sim.Close()
1992+
1993+
_, tx, _, err := DeployNameConflict(user, sim)
1994+
if err != nil {
1995+
t.Fatalf("DeployNameConflict() got err %v; want nil err", err)
1996+
}
1997+
sim.Commit()
1998+
19511999
if _, err = bind.WaitDeployed(nil, sim, tx); err != nil {
19522000
t.Logf("Deployment tx: %+v", tx)
19532001
t.Errorf("bind.WaitDeployed(nil, %T, <deployment tx>) got err %v; want nil err", sim, err)

accounts/abi/error.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,13 @@ type Error struct {
3030
Name string
3131
Inputs Arguments
3232
str string
33+
3334
// Sig contains the string signature according to the ABI spec.
34-
// e.g. event foo(uint32 a, int b) = "foo(uint32,int256)"
35+
// e.g. error foo(uint32 a, int b) = "foo(uint32,int256)"
3536
// Please note that "int" is substitute for its canonical representation "int256"
3637
Sig string
37-
// ID returns the canonical representation of the event's signature used by the
38+
39+
// ID returns the canonical representation of the error's signature used by the
3840
// abi definition to identify event names and types.
3941
ID common.Hash
4042
}

accounts/abi/event.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,24 +29,27 @@ import (
2929
// don't get the signature canonical representation as the first LOG topic.
3030
type Event struct {
3131
// Name is the event name used for internal representation. It's derived from
32-
// the raw name and a suffix will be added in the case of a event overload.
32+
// the raw name and a suffix will be added in the case of event overloading.
3333
//
3434
// e.g.
3535
// These are two events that have the same name:
3636
// * foo(int,int)
3737
// * foo(uint,uint)
38-
// The event name of the first one wll be resolved as foo while the second one
38+
// The event name of the first one will be resolved as foo while the second one
3939
// will be resolved as foo0.
4040
Name string
41+
4142
// RawName is the raw event name parsed from ABI.
4243
RawName string
4344
Anonymous bool
4445
Inputs Arguments
4546
str string
47+
4648
// Sig contains the string signature according to the ABI spec.
4749
// e.g. event foo(uint32 a, int b) = "foo(uint32,int256)"
4850
// Please note that "int" is substitute for its canonical representation "int256"
4951
Sig string
52+
5053
// ID returns the canonical representation of the event's signature used by the
5154
// abi definition to identify event names and types.
5255
ID common.Hash

accounts/abi/type.go

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -163,22 +163,26 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty
163163
elems []*Type
164164
names []string
165165
expression string // canonical parameter expression
166+
used = make(map[string]bool)
166167
)
167168
expression += "("
168-
overloadedNames := make(map[string]string)
169169
for idx, c := range components {
170170
cType, err := NewType(c.Type, c.InternalType, c.Components)
171171
if err != nil {
172172
return Type{}, err
173173
}
174-
fieldName, err := overloadedArgName(c.Name, overloadedNames)
174+
name := ToCamelCase(c.Name)
175+
if name == "" {
176+
return Type{}, errors.New("abi: purely anonymous or underscored field is not supported")
177+
}
178+
fieldName := ResolveNameConflict(name, func(s string) bool { return used[s] })
175179
if err != nil {
176180
return Type{}, err
177181
}
182+
used[fieldName] = true
178183
if !isValidFieldName(fieldName) {
179184
return Type{}, fmt.Errorf("field %d has invalid name", idx)
180185
}
181-
overloadedNames[fieldName] = fieldName
182186
fields = append(fields, reflect.StructField{
183187
Name: fieldName, // reflect.StructOf will panic for any exported field.
184188
Type: cType.GetType(),
@@ -255,20 +259,6 @@ func (t Type) GetType() reflect.Type {
255259
}
256260
}
257261

258-
func overloadedArgName(rawName string, names map[string]string) (string, error) {
259-
fieldName := ToCamelCase(rawName)
260-
if fieldName == "" {
261-
return "", errors.New("abi: purely anonymous or underscored field is not supported")
262-
}
263-
// Handle overloaded fieldNames
264-
_, ok := names[fieldName]
265-
for idx := 0; ok; idx++ {
266-
fieldName = fmt.Sprintf("%s%d", ToCamelCase(rawName), idx)
267-
_, ok = names[fieldName]
268-
}
269-
return fieldName, nil
270-
}
271-
272262
// String implements Stringer.
273263
func (t Type) String() (out string) {
274264
return t.stringKind

accounts/abi/utils.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright 2022 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package abi
18+
19+
import "fmt"
20+
21+
// ResolveNameConflict returns the next available name for a given thing.
22+
// This helper can be used for lots of purposes:
23+
//
24+
// - In solidity function overloading is supported, this function can fix
25+
// the name conflicts of overloaded functions.
26+
// - In golang binding generation, the parameter(in function, event, error,
27+
// and struct definition) name will be converted to camelcase style which
28+
// may eventually lead to name conflicts.
29+
//
30+
// Name conflicts are mostly resolved by adding number suffix.
31+
// e.g. if the abi contains Methods send, send1
32+
// ResolveNameConflict would return send2 for input send.
33+
func ResolveNameConflict(rawName string, used func(string) bool) string {
34+
name := rawName
35+
ok := used(name)
36+
for idx := 0; ok; idx++ {
37+
name = fmt.Sprintf("%s%d", rawName, idx)
38+
ok = used(name)
39+
}
40+
return name
41+
}

cmd/abigen/main.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,11 @@ func abigen(c *cli.Context) error {
198198
nameParts := strings.Split(name, ":")
199199
types = append(types, nameParts[len(nameParts)-1])
200200

201-
libPattern := crypto.Keccak256Hash([]byte(name)).String()[2:36]
201+
// Derive the library placeholder which is a 34 character prefix of the
202+
// hex encoding of the keccak256 hash of the fully qualified library name.
203+
// Note that the fully qualified library name is the path of its source
204+
// file and the library name separated by ":".
205+
libPattern := crypto.Keccak256Hash([]byte(name)).String()[2:36] // the first 2 chars are 0x
202206
libs[libPattern] = nameParts[len(nameParts)-1]
203207
}
204208
}

0 commit comments

Comments
 (0)