From 3d0330d6a0f2315a3ae22cf866ebc7bf7dca21d6 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Fri, 6 Jan 2023 12:58:43 +0100 Subject: [PATCH] accounts/abi: abigen v2 --- accounts/abi/bind/base.go | 60 ++++++++----- accounts/abi/bind/bind.go | 125 ++++++++++++++++++++------ accounts/abi/bind/lib.go | 152 +++++++++++++++++++++++++++++++ accounts/abi/bind/template.go | 4 + accounts/abi/bind/template2.go | 157 +++++++++++++++++++++++++++++++++ cmd/abigen/main.go | 15 +++- 6 files changed, 464 insertions(+), 49 deletions(-) create mode 100644 accounts/abi/bind/lib.go create mode 100644 accounts/abi/bind/template2.go diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index df3f52a403e7..4cb2867673c2 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -149,10 +149,6 @@ func DeployContract(opts *TransactOpts, abi abi.ABI, bytecode []byte, backend Co // returns, a slice of interfaces for anonymous returns and a struct for named // returns. func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method string, params ...interface{}) error { - // Don't crash on a lazy user - if opts == nil { - opts = new(CallOpts) - } if results == nil { results = new([]interface{}) } @@ -161,51 +157,64 @@ func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method stri if err != nil { return err } + output, err := c.call(opts, input) + if err != nil { + return err + } + + if len(*results) == 0 { + res, err := c.abi.Unpack(method, output) + *results = res + return err + } + res := *results + return c.abi.UnpackIntoInterface(res[0], method, output) +} + +func (c *BoundContract) call(opts *CallOpts, input []byte) ([]byte, error) { + // Don't crash on a lazy user + if opts == nil { + opts = new(CallOpts) + } var ( msg = ethereum.CallMsg{From: opts.From, To: &c.address, Data: input} ctx = ensureContext(opts.Context) code []byte output []byte + err error ) if opts.Pending { pb, ok := c.caller.(PendingContractCaller) if !ok { - return ErrNoPendingState + return nil, ErrNoPendingState } output, err = pb.PendingCallContract(ctx, msg) if err != nil { - return err + return nil, err } if len(output) == 0 { // Make sure we have a contract to operate on, and bail out otherwise. if code, err = pb.PendingCodeAt(ctx, c.address); err != nil { - return err + return nil, err } else if len(code) == 0 { - return ErrNoCode + return nil, ErrNoCode } } } else { output, err = c.caller.CallContract(ctx, msg, opts.BlockNumber) if err != nil { - return err + return nil, err } if len(output) == 0 { // Make sure we have a contract to operate on, and bail out otherwise. if code, err = c.caller.CodeAt(ctx, c.address, opts.BlockNumber); err != nil { - return err + return nil, err } else if len(code) == 0 { - return ErrNoCode + return nil, ErrNoCode } } } - - if len(*results) == 0 { - res, err := c.abi.Unpack(method, output) - *results = res - return err - } - res := *results - return c.abi.UnpackIntoInterface(res[0], method, output) + return output, nil } // Transact invokes the (paid) contract method with params as input values. @@ -409,13 +418,16 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i // FilterLogs filters contract logs for past blocks, returning the necessary // channels to construct a strongly typed bound iterator on top of them. func (c *BoundContract) FilterLogs(opts *FilterOpts, name string, query ...[]interface{}) (chan types.Log, event.Subscription, error) { + return c.filterLogs(opts, c.abi.Events[name].ID, query...) +} + +func (c *BoundContract) filterLogs(opts *FilterOpts, eventID common.Hash, query ...[]interface{}) (chan types.Log, event.Subscription, error) { // Don't crash on a lazy user if opts == nil { opts = new(FilterOpts) } // Append the event selector to the query parameters and construct the topic set - query = append([][]interface{}{{c.abi.Events[name].ID}}, query...) - + query = append([][]interface{}{{eventID}}, query...) topics, err := abi.MakeTopics(query...) if err != nil { return nil, nil, err @@ -458,12 +470,16 @@ func (c *BoundContract) FilterLogs(opts *FilterOpts, name string, query ...[]int // WatchLogs filters subscribes to contract logs for future blocks, returning a // subscription object that can be used to tear down the watcher. func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]interface{}) (chan types.Log, event.Subscription, error) { + return c.watchLogs(opts, c.abi.Events[name].ID, query...) +} + +func (c *BoundContract) watchLogs(opts *WatchOpts, eventID common.Hash, query ...[]interface{}) (chan types.Log, event.Subscription, error) { // Don't crash on a lazy user if opts == nil { opts = new(WatchOpts) } // Append the event selector to the query parameters and construct the topic set - query = append([][]interface{}{{c.abi.Events[name].ID}}, query...) + query = append([][]interface{}{{eventID}}, query...) topics, err := abi.MakeTopics(query...) if err != nil { diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index 05cca8e90b3a..ac1e2142cd46 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -82,6 +82,100 @@ func isKeyWord(arg string) bool { // enforces compile time type safety and naming convention opposed to having to // manually maintain hard coded strings that break on runtime. 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) { + data, err := bind(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases) + if err != nil { + return "", err + } + buffer := new(bytes.Buffer) + + funcs := map[string]interface{}{ + "bindtype": bindType[lang], + "bindtopictype": bindTopicType[lang], + "namedtype": namedType[lang], + "capitalise": capitalise, + "decapitalise": decapitalise, + } + tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource[lang])) + if err := tmpl.Execute(buffer, data); err != nil { + return "", err + } + // For Go bindings pass the code through gofmt to clean it up + if lang == LangGo { + code, err := format.Source(buffer.Bytes()) + if err != nil { + return "", fmt.Errorf("%v\n%s", err, buffer) + } + return string(code), nil + } + // For all others just return as is for now + return buffer.String(), nil +} + +func BindV2(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string) (string, error) { + data, err := bind(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases) + if err != nil { + return "", err + } + for _, c := range data.Contracts { + // We want pack/unpack methods for all existing methods. + for name, t := range c.Transacts { + c.Calls[name] = t + } + c.Transacts = nil + + // Make sure we return one argument. If multiple exist + // merge them into a struct. + for _, call := range c.Calls { + if call.Structured { + continue + } + if len(call.Normalized.Outputs) == 1 { + continue + } + // Build up dictionary of existing arg names. + keys := make(map[string]struct{}) + for _, o := range call.Normalized.Outputs { + if o.Name != "" { + keys[strings.ToLower(o.Name)] = struct{}{} + } + } + // Assign names to anonymous fields. + for i, o := range call.Normalized.Outputs { + if o.Name != "" { + continue + } + o.Name = capitalise(abi.ResolveNameConflict("arg", func(name string) bool { _, ok := keys[name]; return ok })) + call.Normalized.Outputs[i] = o + keys[strings.ToLower(o.Name)] = struct{}{} + } + call.Structured = true + } + } + buffer := new(bytes.Buffer) + funcs := map[string]interface{}{ + "bindtype": bindType[lang], + "bindtopictype": bindTopicType[lang], + "namedtype": namedType[lang], + "capitalise": capitalise, + "decapitalise": decapitalise, + } + tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSourceV2[lang])) + if err := tmpl.Execute(buffer, data); err != nil { + return "", err + } + // For Go bindings pass the code through gofmt to clean it up + if lang == LangGo { + code, err := format.Source(buffer.Bytes()) + if err != nil { + return "", fmt.Errorf("%v\n%s", err, buffer) + } + return string(code), nil + } + // For all others just return as is for now + return buffer.String(), nil +} + +func bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string) (*tmplData, error) { var ( // contracts is the map of each individual contract requested binding contracts = make(map[string]*tmplContract) @@ -96,7 +190,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] // Parse the actual ABI to generate the binding for evmABI, err := abi.JSON(strings.NewReader(abis[i])) if err != nil { - return "", err + return nil, err } // Strip any whitespace from the JSON ABI strippedABI := strings.Map(func(r rune) rune { @@ -140,7 +234,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] identifiers = transactIdentifiers } if identifiers[normalizedName] { - return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName) + return nil, fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName) } identifiers[normalizedName] = true @@ -183,7 +277,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] // Ensure there is no duplicated identifier normalizedName := methodNormalizer[lang](alias(aliases, original.Name)) if eventIdentifiers[normalizedName] { - return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName) + return nil, fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName) } eventIdentifiers[normalizedName] = true normalized.Name = normalizedName @@ -218,6 +312,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] if evmABI.HasReceive() { receive = &tmplMethod{Original: evmABI.Receive} } + contracts[types[i]] = &tmplContract{ Type: capitalise(types[i]), InputABI: strings.ReplaceAll(strippedABI, "\"", "\\\""), @@ -262,29 +357,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] Libraries: libs, Structs: structs, } - buffer := new(bytes.Buffer) - - funcs := map[string]interface{}{ - "bindtype": bindType[lang], - "bindtopictype": bindTopicType[lang], - "namedtype": namedType[lang], - "capitalise": capitalise, - "decapitalise": decapitalise, - } - tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource[lang])) - if err := tmpl.Execute(buffer, data); err != nil { - return "", err - } - // For Go bindings pass the code through gofmt to clean it up - if lang == LangGo { - code, err := format.Source(buffer.Bytes()) - if err != nil { - return "", fmt.Errorf("%v\n%s", err, buffer) - } - return string(code), nil - } - // For all others just return as is for now - return buffer.String(), nil + return data, nil } // bindType is a set of type binders that convert Solidity types to some supported diff --git a/accounts/abi/bind/lib.go b/accounts/abi/bind/lib.go new file mode 100644 index 000000000000..27a8c604b26c --- /dev/null +++ b/accounts/abi/bind/lib.go @@ -0,0 +1,152 @@ +package bind + +import ( + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/event" +) + +func DeployContract2(opts *TransactOpts, bytecode []byte, input []byte, backend ContractBackend) (common.Address, *types.Transaction, error) { + c := NewBoundContract(common.Address{}, abi.ABI{}, backend, backend, backend) + tx, err := c.transact(opts, nil, append(bytecode, input...)) + if err != nil { + return common.Address{}, nil, err + } + address := crypto.CreateAddress(opts.From, tx.Nonce()) + return address, tx, nil +} + +func Call2[T any](opts *CallOpts, addr common.Address, input []byte, backend ContractBackend, unpack func([]byte) (T, error)) (arg T, err error) { + var data []byte + data, err = CallRaw(opts, addr, input, backend) + if err != nil { + return + } + return unpack(data) +} + +func CallRaw(opts *CallOpts, addr common.Address, input []byte, backend ContractBackend) ([]byte, error) { + c := NewBoundContract(addr, abi.ABI{}, backend, backend, backend) + return c.call(opts, input) +} + +func Transact2(opts *TransactOpts, addr common.Address, input []byte, backend ContractBackend) (*types.Transaction, error) { + c := NewBoundContract(addr, abi.ABI{}, backend, backend, backend) + return c.transact(opts, &addr, input) +} + +func Transfer2(opts *TransactOpts, addr common.Address, backend ContractBackend) (*types.Transaction, error) { + c := NewBoundContract(addr, abi.ABI{}, backend, backend, backend) + return c.Transfer(opts) +} + +func FilterLogs[T any](opts *FilterOpts, addr common.Address, backend ContractBackend, eventID common.Hash, unpack func(types.Log) (*T, error), topics ...[]any) (*EventIterator[T], error) { + c := NewBoundContract(addr, abi.ABI{}, backend, backend, backend) + logs, sub, err := c.filterLogs(opts, eventID, topics...) + if err != nil { + return nil, err + } + return &EventIterator[T]{unpack: unpack, logs: logs, sub: sub}, nil +} + +func WatchLogs[T any](opts *WatchOpts, addr common.Address, backend ContractBackend, eventID common.Hash, unpack func(types.Log) (*T, error), sink chan<- *T, topics ...[]any) (event.Subscription, error) { + c := NewBoundContract(addr, abi.ABI{}, backend, backend, backend) + logs, sub, err := c.watchLogs(opts, eventID, topics...) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + ev, err := unpack(log) + if err != nil { + return err + } + + select { + case sink <- ev: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// EventIterator is returned from FilterLogs and is used to iterate over the raw logs and unpacked data for events. +type EventIterator[T any] struct { + Event *T // Event containing the contract specifics and raw log + + unpack func(types.Log) (*T, error) // Unpack function for the event + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *EventIterator[T]) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + res, err := it.unpack(log) + if err != nil { + it.fail = err + return false + } + it.Event = res + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + res, err := it.unpack(log) + if err != nil { + it.fail = err + return false + } + it.Event = res + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *EventIterator[T]) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *EventIterator[T]) Close() error { + it.sub.Unsubscribe() + return nil +} diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go index c22eb4ae8432..ba1d02032c27 100644 --- a/accounts/abi/bind/template.go +++ b/accounts/abi/bind/template.go @@ -78,6 +78,10 @@ var tmplSource = map[Lang]string{ LangGo: tmplSourceGo, } +var tmplSourceV2 = map[Lang]string{ + LangGo: tmplSourceGoV2, +} + // tmplSourceGo is the Go source template that the generated Go contract binding // is based on. const tmplSourceGo = ` diff --git a/accounts/abi/bind/template2.go b/accounts/abi/bind/template2.go new file mode 100644 index 000000000000..23e3f9d2b33a --- /dev/null +++ b/accounts/abi/bind/template2.go @@ -0,0 +1,157 @@ +package bind + +// tmplSourceGo is the Go source template that the generated Go contract binding +// is based on. +const tmplSourceGoV2 = ` +// Code generated via abigen V2 - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package {{.Package}} + +import ( + "fmt" + "math/big" + "strings" + "errors" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +{{$structs := .Structs}} +{{range $structs}} + // {{.Name}} is an auto generated low-level Go binding around an user-defined struct. + type {{.Name}} struct { + {{range $field := .Fields}} + {{$field.Name}} {{$field.Type}}{{end}} + } +{{end}} + +{{range $contract := .Contracts}} + // {{.Type}}MetaData contains all meta data concerning the {{.Type}} contract. + var {{.Type}}MetaData = &bind.MetaData{ + ABI: "{{.InputABI}}", + {{if $contract.FuncSigs -}} + Sigs: map[string]string{ + {{range $strsig, $binsig := .FuncSigs}}"{{$binsig}}": "{{$strsig}}", + {{end}} + }, + {{end -}} + {{if .InputBin -}} + Bin: "0x{{.InputBin}}", + {{end}} + } + + // {{.Type}} is an auto generated Go binding around an Ethereum contract. + type {{.Type}} struct { + abi abi.ABI + } + + // {{.Type}}Instance represents a deployed instance of the {{.Type}} contract. + type {{.Type}}Instance struct { + {{.Type}} + address common.Address + } + + func (i *{{$contract.Type}}Instance) Address() common.Address { + return i.address + } + + // New{{.Type}} creates a new instance of {{.Type}}. + func New{{.Type}}() (*{{.Type}}, error) { + parsed, err := {{.Type}}MetaData.GetAbi() + if err != nil { + return nil, err + } + + return &{{.Type}}{abi: *parsed}, nil + } + + func (_{{$contract.Type}} *{{$contract.Type}}) PackConstructor({{range .Constructor.Inputs}}, {{.Name}} {{bindtype .Type $structs}} {{end}}) ([]byte, error) { + return _{{$contract.Type}}.abi.Pack("" {{range .Constructor.Inputs}}, {{.Name}}{{end}}) + } + + {{range .Calls}} + // {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}) Pack{{.Normalized.Name}}({{range .Normalized.Inputs}} {{.Name}} {{bindtype .Type $structs}}, {{end}}) ([]byte, error) { + return _{{$contract.Type}}.abi.Pack("{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}}) + } + + func (_{{$contract.Type}} *{{$contract.Type}}) Unpack{{.Normalized.Name}}(data []byte) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} },{{else}}{{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}}{{end}} error) { + out, err := _{{$contract.Type}}.abi.Unpack("{{.Original.Name}}", data) + {{if .Structured}} + outstruct := new(struct{ {{range .Normalized.Outputs}} {{.Name}} {{bindtype .Type $structs}}; {{end}} }) + if err != nil { + return *outstruct, err + } + {{range $i, $t := .Normalized.Outputs}} + outstruct.{{.Name}} = *abi.ConvertType(out[{{$i}}], new({{bindtype .Type $structs}})).(*{{bindtype .Type $structs}}){{end}} + + return *outstruct, err + {{else}} + if err != nil { + return {{range $i, $_ := .Normalized.Outputs}}*new({{bindtype .Type $structs}}), {{end}} err + } + {{range $i, $t := .Normalized.Outputs}} + out{{$i}} := *abi.ConvertType(out[{{$i}}], new({{bindtype .Type $structs}})).(*{{bindtype .Type $structs}}){{end}} + + return {{range $i, $t := .Normalized.Outputs}}out{{$i}}, {{end}} err + {{end}} + } + {{end}} + + {{range .Events}} + // {{$contract.Type}}{{.Normalized.Name}} represents a {{.Normalized.Name}} event raised by the {{$contract.Type}} contract. + type {{$contract.Type}}{{.Normalized.Name}} struct { {{range .Normalized.Inputs}} + {{capitalise .Name}} {{if .Indexed}}{{bindtopictype .Type $structs}}{{else}}{{bindtype .Type $structs}}{{end}}; {{end}} + Raw types.Log // Blockchain specific contextual infos + } + func (_{{$contract.Type}} *{{$contract.Type}}) {{.Normalized.Name}}EventID() common.Hash { + return common.HexToHash("{{.Original.ID}}") + } + + func (_{{$contract.Type}} *{{$contract.Type}}) Unpack{{.Normalized.Name}}Event(log types.Log) (*{{$contract.Type}}{{.Normalized.Name}}, error) { + event := "{{.Normalized.Name}}" + if log.Topics[0] != _{{$contract.Type}}.abi.Events[event].ID { + return nil, fmt.Errorf("event signature mismatch") + } + out := new({{$contract.Type}}{{.Normalized.Name}}) + if len(log.Data) > 0 { + if err := _{{$contract.Type}}.abi.UnpackIntoInterface(out, event, log.Data); err != nil { + return nil, err + } + } + var indexed abi.Arguments + for _, arg := range _{{$contract.Type}}.abi.Events[event].Inputs { + if arg.Indexed { + indexed = append(indexed, arg) + } + } + if err := abi.ParseTopics(out, indexed, log.Topics[1:]); err != nil { + return nil, err + } + out.Raw = log + return out, nil + } + {{end}} +{{end}} +` diff --git a/cmd/abigen/main.go b/cmd/abigen/main.go index 221f45c07849..087135c3d15a 100644 --- a/cmd/abigen/main.go +++ b/cmd/abigen/main.go @@ -72,6 +72,10 @@ var ( Name: "alias", Usage: "Comma separated aliases for function and event renaming, e.g. original1=alias1, original2=alias2", } + v2Flag = &cli.BoolFlag{ + Name: "v2", + Usage: "Generates v2 bindings", + } ) var app = flags.NewApp("Ethereum ABI wrapper code generator") @@ -88,6 +92,7 @@ func init() { outFlag, langFlag, aliasFlag, + v2Flag, } app.Action = abigen } @@ -216,7 +221,15 @@ func abigen(c *cli.Context) error { } } // Generate the contract binding - code, err := bind.Bind(types, abis, bins, sigs, c.String(pkgFlag.Name), lang, libs, aliases) + var ( + code string + err error + ) + if c.IsSet(v2Flag.Name) { + code, err = bind.BindV2(types, abis, bins, sigs, c.String(pkgFlag.Name), lang, libs, aliases) + } else { + code, err = bind.Bind(types, abis, bins, sigs, c.String(pkgFlag.Name), lang, libs, aliases) + } if err != nil { utils.Fatalf("Failed to generate ABI binding: %v", err) }