Skip to content

Commit 64bd213

Browse files
jwasingers1nafjl
authored
cmd/abigen, accounts/abi/bind: implement abigen version 2 (#31379)
This PR implements a new version of the abigen utility (v2) which exists along with the pre-existing v1 version. Abigen is a utility command provided by go-ethereum that, given a solidity contract ABI definition, will generate Go code to transact/call the contract methods, converting the method parameters/results and structures defined in the contract into corresponding Go types. This is useful for preventing the need to write custom boilerplate code for contract interactions. Methods in the generated bindings perform encoding between Go types and Solidity ABI-encoded packed bytecode, as well as some action (e.g. `eth_call` or creating and submitting a transaction). This limits the flexibility of how the generated bindings can be used, and prevents easily adding new functionality, as it will make the generated bindings larger for each feature added. Abigen v2 was conceived of by the observation that the only functionality that generated Go bindings ought to perform is conversion between Go types and ABI-encoded packed data. Go-ethereum already provides various APIs which in conjunction with conversion methods generated in v2 bindings can cover all functionality currently provided by v1, and facilitate all other previously-desired use-cases. ## Generating Bindings To generate contract bindings using abigen v2, invoke the `abigen` command with the `--v2` flag. The functionality of all other flags is preserved between the v2 and v1 versions. ## What is Generated in the Bindings The execution of `abigen --v2` generates Go code containing methods which convert between Go types and corresponding ABI-encoded data expected by the contract. For each input-accepting contract method and the constructor, a "packing" method is generated in the binding which converts from Go types to the corresponding packed solidity expected by the contract. If a method returns output, an "unpacking" method is generated to convert this output from ABI-encoded data to the corresponding Go types. For contracts which emit events, an unpacking method is defined for each event to unpack the corresponding raw log to the Go type that it represents. Likewise, where custom errors are defined by contracts, an unpack method is generated to unpack raw error data into a Go type. ## Using the Generated Bindings For a smooth user-experience, abigen v2 comes with a number of utility functions to be used in conjunction with the generated bindings for performing common contract interaction use-cases. These include: * filtering for historical logs of a given topic * watching the chain for emission of logs with a given topic * contract deployment methods * Call/Transact methods https://geth.ethereum.org will be updated to include a new tutorial page for abigen v2 with full code examples. The page currently exists in a PR: #31390 . There are also extensive examples of interactions with contract bindings in [test cases](https://github.com/ethereum/go-ethereum/blob/cc855c7ede460270ae9c83bba278b23cb4f26a00/accounts/abi/bind/v2/lib_test.go) provided with this PR. --------- Co-authored-by: Sina Mahmoodi <[email protected]> Co-authored-by: Felix Lange <[email protected]>
1 parent 1cdf4d6 commit 64bd213

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+8271
-402
lines changed

accounts/abi/bind/bind.go accounts/abi/abigen/bind.go

+51-90
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414
// You should have received a copy of the GNU Lesser General Public License
1515
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
1616

17-
// Package bind generates Ethereum contract Go bindings.
17+
// Package abigen generates Ethereum contract Go bindings.
1818
//
1919
// Detailed usage document and tutorial available on the go-ethereum Wiki page:
20-
// https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts
21-
package bind
20+
// https://geth.ethereum.org/docs/developers/dapp-developer/native-bindings
21+
package abigen
2222

2323
import (
2424
"bytes"
@@ -33,13 +33,6 @@ import (
3333
"github.com/ethereum/go-ethereum/log"
3434
)
3535

36-
// Lang is a target programming language selector to generate bindings for.
37-
type Lang int
38-
39-
const (
40-
LangGo Lang = iota
41-
)
42-
4336
func isKeyWord(arg string) bool {
4437
switch arg {
4538
case "break":
@@ -81,7 +74,7 @@ func isKeyWord(arg string) bool {
8174
// to be used as is in client code, but rather as an intermediate struct which
8275
// enforces compile time type safety and naming convention as opposed to having to
8376
// manually maintain hard coded strings that break on runtime.
84-
func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string) (string, error) {
77+
func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, libs map[string]string, aliases map[string]string) (string, error) {
8578
var (
8679
// contracts is the map of each individual contract requested binding
8780
contracts = make(map[string]*tmplContract)
@@ -125,14 +118,14 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
125118

126119
for _, input := range evmABI.Constructor.Inputs {
127120
if hasStruct(input.Type) {
128-
bindStructType[lang](input.Type, structs)
121+
bindStructType(input.Type, structs)
129122
}
130123
}
131124

132125
for _, original := range evmABI.Methods {
133126
// Normalize the method for capital cases and non-anonymous inputs/outputs
134127
normalized := original
135-
normalizedName := methodNormalizer[lang](alias(aliases, original.Name))
128+
normalizedName := abi.ToCamelCase(alias(aliases, original.Name))
136129
// Ensure there is no duplicated identifier
137130
var identifiers = callIdentifiers
138131
if !original.IsConstant() {
@@ -159,17 +152,17 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
159152
normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
160153
}
161154
if hasStruct(input.Type) {
162-
bindStructType[lang](input.Type, structs)
155+
bindStructType(input.Type, structs)
163156
}
164157
}
165158
normalized.Outputs = make([]abi.Argument, len(original.Outputs))
166159
copy(normalized.Outputs, original.Outputs)
167160
for j, output := range normalized.Outputs {
168161
if output.Name != "" {
169-
normalized.Outputs[j].Name = capitalise(output.Name)
162+
normalized.Outputs[j].Name = abi.ToCamelCase(output.Name)
170163
}
171164
if hasStruct(output.Type) {
172-
bindStructType[lang](output.Type, structs)
165+
bindStructType(output.Type, structs)
173166
}
174167
}
175168
// Append the methods to the call or transact lists
@@ -188,7 +181,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
188181
normalized := original
189182

190183
// Ensure there is no duplicated identifier
191-
normalizedName := methodNormalizer[lang](alias(aliases, original.Name))
184+
normalizedName := abi.ToCamelCase(alias(aliases, original.Name))
192185
// Name shouldn't start with a digit. It will make the generated code invalid.
193186
if len(normalizedName) > 0 && unicode.IsDigit(rune(normalizedName[0])) {
194187
normalizedName = fmt.Sprintf("E%s", normalizedName)
@@ -213,14 +206,14 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
213206
// Event is a bit special, we need to define event struct in binding,
214207
// ensure there is no camel-case-style name conflict.
215208
for index := 0; ; index++ {
216-
if !used[capitalise(normalized.Inputs[j].Name)] {
217-
used[capitalise(normalized.Inputs[j].Name)] = true
209+
if !used[abi.ToCamelCase(normalized.Inputs[j].Name)] {
210+
used[abi.ToCamelCase(normalized.Inputs[j].Name)] = true
218211
break
219212
}
220213
normalized.Inputs[j].Name = fmt.Sprintf("%s%d", normalized.Inputs[j].Name, index)
221214
}
222215
if hasStruct(input.Type) {
223-
bindStructType[lang](input.Type, structs)
216+
bindStructType(input.Type, structs)
224217
}
225218
}
226219
// Append the event to the accumulator list
@@ -233,8 +226,9 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
233226
if evmABI.HasReceive() {
234227
receive = &tmplMethod{Original: evmABI.Receive}
235228
}
229+
236230
contracts[types[i]] = &tmplContract{
237-
Type: capitalise(types[i]),
231+
Type: abi.ToCamelCase(types[i]),
238232
InputABI: strings.ReplaceAll(strippedABI, "\"", "\\\""),
239233
InputBin: strings.TrimPrefix(strings.TrimSpace(bytecodes[i]), "0x"),
240234
Constructor: evmABI.Constructor,
@@ -245,6 +239,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
245239
Events: events,
246240
Libraries: make(map[string]string),
247241
}
242+
248243
// Function 4-byte signatures are stored in the same sequence
249244
// as types, if available.
250245
if len(fsigs) > i {
@@ -270,6 +265,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
270265
_, ok := isLib[types[i]]
271266
contracts[types[i]].Library = ok
272267
}
268+
273269
// Generate the contract template data content and render it
274270
data := &tmplData{
275271
Package: pkg,
@@ -280,36 +276,25 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
280276
buffer := new(bytes.Buffer)
281277

282278
funcs := map[string]interface{}{
283-
"bindtype": bindType[lang],
284-
"bindtopictype": bindTopicType[lang],
285-
"namedtype": namedType[lang],
286-
"capitalise": capitalise,
279+
"bindtype": bindType,
280+
"bindtopictype": bindTopicType,
281+
"capitalise": abi.ToCamelCase,
287282
"decapitalise": decapitalise,
288283
}
289-
tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource[lang]))
284+
tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource))
290285
if err := tmpl.Execute(buffer, data); err != nil {
291286
return "", err
292287
}
293-
// For Go bindings pass the code through gofmt to clean it up
294-
if lang == LangGo {
295-
code, err := format.Source(buffer.Bytes())
296-
if err != nil {
297-
return "", fmt.Errorf("%v\n%s", err, buffer)
298-
}
299-
return string(code), nil
288+
// Pass the code through gofmt to clean it up
289+
code, err := format.Source(buffer.Bytes())
290+
if err != nil {
291+
return "", fmt.Errorf("%v\n%s", err, buffer)
300292
}
301-
// For all others just return as is for now
302-
return buffer.String(), nil
303-
}
304-
305-
// bindType is a set of type binders that convert Solidity types to some supported
306-
// programming language types.
307-
var bindType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{
308-
LangGo: bindTypeGo,
293+
return string(code), nil
309294
}
310295

311-
// bindBasicTypeGo converts basic solidity types(except array, slice and tuple) to Go ones.
312-
func bindBasicTypeGo(kind abi.Type) string {
296+
// bindBasicType converts basic solidity types(except array, slice and tuple) to Go ones.
297+
func bindBasicType(kind abi.Type) string {
313298
switch kind.T {
314299
case abi.AddressTy:
315300
return "common.Address"
@@ -332,32 +317,26 @@ func bindBasicTypeGo(kind abi.Type) string {
332317
}
333318
}
334319

335-
// bindTypeGo converts solidity types to Go ones. Since there is no clear mapping
320+
// bindType converts solidity types to Go ones. Since there is no clear mapping
336321
// from all Solidity types to Go ones (e.g. uint17), those that cannot be exactly
337322
// mapped will use an upscaled type (e.g. BigDecimal).
338-
func bindTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
323+
func bindType(kind abi.Type, structs map[string]*tmplStruct) string {
339324
switch kind.T {
340325
case abi.TupleTy:
341326
return structs[kind.TupleRawName+kind.String()].Name
342327
case abi.ArrayTy:
343-
return fmt.Sprintf("[%d]", kind.Size) + bindTypeGo(*kind.Elem, structs)
328+
return fmt.Sprintf("[%d]", kind.Size) + bindType(*kind.Elem, structs)
344329
case abi.SliceTy:
345-
return "[]" + bindTypeGo(*kind.Elem, structs)
330+
return "[]" + bindType(*kind.Elem, structs)
346331
default:
347-
return bindBasicTypeGo(kind)
332+
return bindBasicType(kind)
348333
}
349334
}
350335

351-
// bindTopicType is a set of type binders that convert Solidity types to some
352-
// supported programming language topic types.
353-
var bindTopicType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{
354-
LangGo: bindTopicTypeGo,
355-
}
356-
357-
// bindTopicTypeGo converts a Solidity topic type to a Go one. It is almost the same
336+
// bindTopicType converts a Solidity topic type to a Go one. It is almost the same
358337
// functionality as for simple types, but dynamic types get converted to hashes.
359-
func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
360-
bound := bindTypeGo(kind, structs)
338+
func bindTopicType(kind abi.Type, structs map[string]*tmplStruct) string {
339+
bound := bindType(kind, structs)
361340

362341
// todo(rjl493456442) according solidity documentation, indexed event
363342
// parameters that are not value types i.e. arrays and structs are not
@@ -371,16 +350,10 @@ func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
371350
return bound
372351
}
373352

374-
// bindStructType is a set of type binders that convert Solidity tuple types to some supported
375-
// programming language struct definition.
376-
var bindStructType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{
377-
LangGo: bindStructTypeGo,
378-
}
379-
380-
// bindStructTypeGo converts a Solidity tuple type to a Go one and records the mapping
381-
// in the given map.
382-
// Notably, this function will resolve and record nested struct recursively.
383-
func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
353+
// bindStructType converts a Solidity tuple type to a Go one and records the mapping
354+
// in the given map. Notably, this function will resolve and record nested struct
355+
// recursively.
356+
func bindStructType(kind abi.Type, structs map[string]*tmplStruct) string {
384357
switch kind.T {
385358
case abi.TupleTy:
386359
// We compose a raw struct name and a canonical parameter expression
@@ -398,37 +371,35 @@ func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
398371
fields []*tmplField
399372
)
400373
for i, elem := range kind.TupleElems {
401-
name := capitalise(kind.TupleRawNames[i])
374+
name := abi.ToCamelCase(kind.TupleRawNames[i])
402375
name = abi.ResolveNameConflict(name, func(s string) bool { return names[s] })
403376
names[name] = true
404-
fields = append(fields, &tmplField{Type: bindStructTypeGo(*elem, structs), Name: name, SolKind: *elem})
377+
fields = append(fields, &tmplField{
378+
Type: bindStructType(*elem, structs),
379+
Name: name,
380+
SolKind: *elem,
381+
})
405382
}
406383
name := kind.TupleRawName
407384
if name == "" {
408385
name = fmt.Sprintf("Struct%d", len(structs))
409386
}
410-
name = capitalise(name)
387+
name = abi.ToCamelCase(name)
411388

412389
structs[id] = &tmplStruct{
413390
Name: name,
414391
Fields: fields,
415392
}
416393
return name
417394
case abi.ArrayTy:
418-
return fmt.Sprintf("[%d]", kind.Size) + bindStructTypeGo(*kind.Elem, structs)
395+
return fmt.Sprintf("[%d]", kind.Size) + bindStructType(*kind.Elem, structs)
419396
case abi.SliceTy:
420-
return "[]" + bindStructTypeGo(*kind.Elem, structs)
397+
return "[]" + bindStructType(*kind.Elem, structs)
421398
default:
422-
return bindBasicTypeGo(kind)
399+
return bindBasicType(kind)
423400
}
424401
}
425402

426-
// namedType is a set of functions that transform language specific types to
427-
// named versions that may be used inside method names.
428-
var namedType = map[Lang]func(string, abi.Type) string{
429-
LangGo: func(string, abi.Type) string { panic("this shouldn't be needed") },
430-
}
431-
432403
// alias returns an alias of the given string based on the aliasing rules
433404
// or returns itself if no rule is matched.
434405
func alias(aliases map[string]string, n string) string {
@@ -438,21 +409,11 @@ func alias(aliases map[string]string, n string) string {
438409
return n
439410
}
440411

441-
// methodNormalizer is a name transformer that modifies Solidity method names to
442-
// conform to target language naming conventions.
443-
var methodNormalizer = map[Lang]func(string) string{
444-
LangGo: abi.ToCamelCase,
445-
}
446-
447-
// capitalise makes a camel-case string which starts with an upper case character.
448-
var capitalise = abi.ToCamelCase
449-
450412
// decapitalise makes a camel-case string which starts with a lower case character.
451413
func decapitalise(input string) string {
452414
if len(input) == 0 {
453415
return input
454416
}
455-
456417
goForm := abi.ToCamelCase(input)
457418
return strings.ToLower(goForm[:1]) + goForm[1:]
458419
}
@@ -471,7 +432,7 @@ func structured(args abi.Arguments) bool {
471432
}
472433
// If the field name is empty when normalized or collides (var, Var, _var, _Var),
473434
// we can't organize into a struct
474-
field := capitalise(out.Name)
435+
field := abi.ToCamelCase(out.Name)
475436
if field == "" || exists[field] {
476437
return false
477438
}

accounts/abi/bind/bind_test.go accounts/abi/abigen/bind_test.go

+8-6
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
// You should have received a copy of the GNU Lesser General Public License
1515
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
1616

17-
package bind
17+
package abigen
1818

1919
import (
2020
"fmt"
@@ -2072,20 +2072,22 @@ var bindTests = []struct {
20722072

20732073
// Tests that packages generated by the binder can be successfully compiled and
20742074
// the requested tester run against it.
2075-
func TestGolangBindings(t *testing.T) {
2075+
func TestBindings(t *testing.T) {
20762076
t.Parallel()
20772077
// Skip the test if no Go command can be found
20782078
gocmd := runtime.GOROOT() + "/bin/go"
20792079
if !common.FileExist(gocmd) {
20802080
t.Skip("go sdk not found for testing")
20812081
}
2082-
// Create a temporary workspace for the test suite
2083-
ws := t.TempDir()
20842082

2085-
pkg := filepath.Join(ws, "bindtest")
2083+
// Create a temporary workspace for the test suite
2084+
path := t.TempDir()
2085+
pkg := filepath.Join(path, "bindtest")
20862086
if err := os.MkdirAll(pkg, 0700); err != nil {
20872087
t.Fatalf("failed to create package: %v", err)
20882088
}
2089+
t.Log("tmpdir", pkg)
2090+
20892091
// Generate the test suite for all the contracts
20902092
for i, tt := range bindTests {
20912093
t.Run(tt.name, func(t *testing.T) {
@@ -2096,7 +2098,7 @@ func TestGolangBindings(t *testing.T) {
20962098
types = []string{tt.name}
20972099
}
20982100
// Generate the binding and create a Go source file in the workspace
2099-
bind, err := Bind(types, tt.abi, tt.bytecode, tt.fsigs, "bindtest", LangGo, tt.libs, tt.aliases)
2101+
bind, err := Bind(types, tt.abi, tt.bytecode, tt.fsigs, "bindtest", tt.libs, tt.aliases)
21002102
if err != nil {
21012103
t.Fatalf("test %d: failed to generate binding: %v", i, err)
21022104
}

0 commit comments

Comments
 (0)