From 3d0330d6a0f2315a3ae22cf866ebc7bf7dca21d6 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Fri, 6 Jan 2023 12:58:43 +0100 Subject: [PATCH 01/12] 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) } From 0f73e938a393f7d08e61589f60d21dafe3864367 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 1 Mar 2023 12:35:07 +0330 Subject: [PATCH 02/12] add copyright header to lib.go --- accounts/abi/bind/lib.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/accounts/abi/bind/lib.go b/accounts/abi/bind/lib.go index 27a8c604b26c..45cd0a412f1a 100644 --- a/accounts/abi/bind/lib.go +++ b/accounts/abi/bind/lib.go @@ -1,3 +1,19 @@ +// Copyright 2023 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 . + package bind import ( From 486fb68fe2b3c54ccfafee6952e4b67cabd5aeaa Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi <1591639+s1na@users.noreply.github.com> Date: Wed, 1 Mar 2023 13:03:08 +0330 Subject: [PATCH 03/12] replace fmt with errors Co-authored-by: Marius van der Wijden --- accounts/abi/bind/template2.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accounts/abi/bind/template2.go b/accounts/abi/bind/template2.go index 23e3f9d2b33a..ba0ce0f86003 100644 --- a/accounts/abi/bind/template2.go +++ b/accounts/abi/bind/template2.go @@ -132,7 +132,7 @@ var ( 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") + return nil, errors.New("event signature mismatch") } out := new({{$contract.Type}}{{.Normalized.Name}}) if len(log.Data) > 0 { From 87e0b4112062434a9d7822fe08e057bdc9e29d2a Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 1 Mar 2023 13:07:00 +0330 Subject: [PATCH 04/12] drop unnecessary imports --- accounts/abi/bind/template2.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/accounts/abi/bind/template2.go b/accounts/abi/bind/template2.go index 23e3f9d2b33a..34ce763626ae 100644 --- a/accounts/abi/bind/template2.go +++ b/accounts/abi/bind/template2.go @@ -11,27 +11,21 @@ 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 ) From 6e180fade2d58721441a129b7ddbdffcfe985cac Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 1 Mar 2023 13:09:28 +0330 Subject: [PATCH 05/12] drop fmt --- accounts/abi/bind/template2.go | 1 - 1 file changed, 1 deletion(-) diff --git a/accounts/abi/bind/template2.go b/accounts/abi/bind/template2.go index bbd6d0ad6350..f5d41d2634fb 100644 --- a/accounts/abi/bind/template2.go +++ b/accounts/abi/bind/template2.go @@ -9,7 +9,6 @@ const tmplSourceGoV2 = ` package {{.Package}} import ( - "fmt" "math/big" "errors" From 7ad90973778486728764f272378d20eb4d825351 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 1 Mar 2023 13:32:34 +0330 Subject: [PATCH 06/12] skip unpack method when no return args --- accounts/abi/bind/bind.go | 2 +- accounts/abi/bind/template2.go | 39 ++++++++++++++++++---------------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index ac1e2142cd46..7ad712baccca 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -129,7 +129,7 @@ func BindV2(types []string, abis []string, bytecodes []string, fsigs []map[strin if call.Structured { continue } - if len(call.Normalized.Outputs) == 1 { + if len(call.Normalized.Outputs) < 2 { continue } // Build up dictionary of existing arg names. diff --git a/accounts/abi/bind/template2.go b/accounts/abi/bind/template2.go index f5d41d2634fb..8f40523cd59d 100644 --- a/accounts/abi/bind/template2.go +++ b/accounts/abi/bind/template2.go @@ -89,27 +89,30 @@ var ( 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 { + {{/* Unpack method is needed only when there are return args */}} + {{if .Normalized.Outputs }} + 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 - } - {{range $i, $t := .Normalized.Outputs}} - outstruct.{{.Name}} = *abi.ConvertType(out[{{$i}}], new({{bindtype .Type $structs}})).(*{{bindtype .Type $structs}}){{end}} + {{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 *outstruct, err - {{else}} - if err != nil { - return {{range $i, $_ := .Normalized.Outputs}}*new({{bindtype .Type $structs}}), {{end}} err + return {{range $i, $t := .Normalized.Outputs}}out{{$i}}, {{end}} err + {{end}} } - {{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}} {{end}} {{range .Events}} From 5a50a5b1141497bd676a2a4489b8bc576e73dd16 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 1 Mar 2023 15:45:45 +0330 Subject: [PATCH 07/12] define contract instance type --- accounts/abi/bind/lib.go | 37 ++++++++++++++++++++++++---------- accounts/abi/bind/template2.go | 9 +++++++++ 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/accounts/abi/bind/lib.go b/accounts/abi/bind/lib.go index 45cd0a412f1a..0b9cc2bf1901 100644 --- a/accounts/abi/bind/lib.go +++ b/accounts/abi/bind/lib.go @@ -25,6 +25,13 @@ import ( "github.com/ethereum/go-ethereum/event" ) +// ContractInstance provides means to interact with +// a deployed contract. +type ContractInstance interface { + Address() common.Address + Backend() ContractBackend +} + 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...)) @@ -35,32 +42,39 @@ func DeployContract2(opts *TransactOpts, bytecode []byte, input []byte, backend 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) { +func Call2[T any](instance ContractInstance, opts *CallOpts, input []byte, unpack func([]byte) (T, error)) (arg T, err error) { var data []byte - data, err = CallRaw(opts, addr, input, backend) + data, err = CallRaw(instance, opts, input) 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) +func CallRaw(instance ContractInstance, opts *CallOpts, input []byte) ([]byte, error) { + backend := instance.Backend() + c := NewBoundContract(instance.Address(), abi.ABI{}, backend, backend, backend) return c.call(opts, input) } -func Transact2(opts *TransactOpts, addr common.Address, input []byte, backend ContractBackend) (*types.Transaction, error) { +func Transact2(instance ContractInstance, opts *TransactOpts, input []byte) (*types.Transaction, error) { + var ( + addr = instance.Address() + backend = instance.Backend() + ) 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) +func Transfer2(instance ContractInstance, opts *TransactOpts) (*types.Transaction, error) { + backend := instance.Backend() + c := NewBoundContract(instance.Address(), 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) +func FilterLogs[T any](instance ContractInstance, opts *FilterOpts, eventID common.Hash, unpack func(types.Log) (*T, error), topics ...[]any) (*EventIterator[T], error) { + backend := instance.Backend() + c := NewBoundContract(instance.Address(), abi.ABI{}, backend, backend, backend) logs, sub, err := c.filterLogs(opts, eventID, topics...) if err != nil { return nil, err @@ -68,8 +82,9 @@ func FilterLogs[T any](opts *FilterOpts, addr common.Address, backend ContractBa 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) +func WatchLogs[T any](instance ContractInstance, opts *WatchOpts, eventID common.Hash, unpack func(types.Log) (*T, error), sink chan<- *T, topics ...[]any) (event.Subscription, error) { + backend := instance.Backend() + c := NewBoundContract(instance.Address(), abi.ABI{}, backend, backend, backend) logs, sub, err := c.watchLogs(opts, eventID, topics...) if err != nil { return nil, err diff --git a/accounts/abi/bind/template2.go b/accounts/abi/bind/template2.go index 8f40523cd59d..4c5aae7654a8 100644 --- a/accounts/abi/bind/template2.go +++ b/accounts/abi/bind/template2.go @@ -61,12 +61,21 @@ var ( type {{.Type}}Instance struct { {{.Type}} address common.Address + backend bind.ContractBackend + } + + func New{{.Type}}Instance(c *{{.Type}}, address common.Address, backend bind.ContractBackend) *{{.Type}}Instance { + return &{{.Type}}Instance{Db: *c, address: address, backend: backend} } func (i *{{$contract.Type}}Instance) Address() common.Address { return i.address } + func (i *{{$contract.Type}}Instance) Backend() bind.ContractBackend { + return i.backend + } + // New{{.Type}} creates a new instance of {{.Type}}. func New{{.Type}}() (*{{.Type}}, error) { parsed, err := {{.Type}}MetaData.GetAbi() From c64c21edc8e25d6d8d95ce0f38080168fc443d6f Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 2 Mar 2023 15:50:26 +0330 Subject: [PATCH 08/12] Add deploy code to contract struct --- accounts/abi/bind/template2.go | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/accounts/abi/bind/template2.go b/accounts/abi/bind/template2.go index 4c5aae7654a8..c5bd3a71b3ea 100644 --- a/accounts/abi/bind/template2.go +++ b/accounts/abi/bind/template2.go @@ -52,11 +52,6 @@ var ( {{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}} @@ -76,14 +71,24 @@ var ( return i.backend } + // {{.Type}} is an auto generated Go binding around an Ethereum contract. + type {{.Type}} struct { + abi abi.ABI + deployCode []byte + } + // New{{.Type}} creates a new instance of {{.Type}}. func New{{.Type}}() (*{{.Type}}, error) { - parsed, err := {{.Type}}MetaData.GetAbi() - if err != nil { - return nil, err - } + parsed, err := {{.Type}}MetaData.GetAbi() + if err != nil { + return nil, err + } + code := common.Hex2Bytes({{.Type}}MetaData.Bin) + return &{{.Type}}{abi: *parsed, deployCode: code}, nil + } - return &{{.Type}}{abi: *parsed}, nil + func (_{{$contract.Type}} *{{$contract.Type}}) DeployCode() []byte { + return _{{$contract.Type}}.deployCode } func (_{{$contract.Type}} *{{$contract.Type}}) PackConstructor({{range .Constructor.Inputs}}, {{.Name}} {{bindtype .Type $structs}} {{end}}) ([]byte, error) { From 0e7bccba6ae4ecd7428dc1ec3fe7d3df4ed51f5f Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 2 Mar 2023 15:59:01 +0330 Subject: [PATCH 09/12] Pass pointer to log for unpack --- accounts/abi/bind/lib.go | 12 ++++++------ accounts/abi/bind/template2.go | 5 +++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/accounts/abi/bind/lib.go b/accounts/abi/bind/lib.go index 0b9cc2bf1901..6d3ea71264a4 100644 --- a/accounts/abi/bind/lib.go +++ b/accounts/abi/bind/lib.go @@ -72,7 +72,7 @@ func Transfer2(instance ContractInstance, opts *TransactOpts) (*types.Transactio return c.Transfer(opts) } -func FilterLogs[T any](instance ContractInstance, opts *FilterOpts, eventID common.Hash, unpack func(types.Log) (*T, error), topics ...[]any) (*EventIterator[T], error) { +func FilterLogs[T any](instance ContractInstance, opts *FilterOpts, eventID common.Hash, unpack func(*types.Log) (*T, error), topics ...[]any) (*EventIterator[T], error) { backend := instance.Backend() c := NewBoundContract(instance.Address(), abi.ABI{}, backend, backend, backend) logs, sub, err := c.filterLogs(opts, eventID, topics...) @@ -82,7 +82,7 @@ func FilterLogs[T any](instance ContractInstance, opts *FilterOpts, eventID comm return &EventIterator[T]{unpack: unpack, logs: logs, sub: sub}, nil } -func WatchLogs[T any](instance ContractInstance, opts *WatchOpts, eventID common.Hash, unpack func(types.Log) (*T, error), sink chan<- *T, topics ...[]any) (event.Subscription, error) { +func WatchLogs[T any](instance ContractInstance, opts *WatchOpts, eventID common.Hash, unpack func(*types.Log) (*T, error), sink chan<- *T, topics ...[]any) (event.Subscription, error) { backend := instance.Backend() c := NewBoundContract(instance.Address(), abi.ABI{}, backend, backend, backend) logs, sub, err := c.watchLogs(opts, eventID, topics...) @@ -95,7 +95,7 @@ func WatchLogs[T any](instance ContractInstance, opts *WatchOpts, eventID common select { case log := <-logs: // New log arrived, parse the event and forward to the user - ev, err := unpack(log) + ev, err := unpack(&log) if err != nil { return err } @@ -120,7 +120,7 @@ func WatchLogs[T any](instance ContractInstance, opts *WatchOpts, eventID common 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 + 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 @@ -140,7 +140,7 @@ func (it *EventIterator[T]) Next() bool { if it.done { select { case log := <-it.logs: - res, err := it.unpack(log) + res, err := it.unpack(&log) if err != nil { it.fail = err return false @@ -155,7 +155,7 @@ func (it *EventIterator[T]) Next() bool { // Iterator still in progress, wait for either a data or an error event select { case log := <-it.logs: - res, err := it.unpack(log) + res, err := it.unpack(&log) if err != nil { it.fail = err return false diff --git a/accounts/abi/bind/template2.go b/accounts/abi/bind/template2.go index c5bd3a71b3ea..f9e53d65eae0 100644 --- a/accounts/abi/bind/template2.go +++ b/accounts/abi/bind/template2.go @@ -133,13 +133,14 @@ var ( // {{$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 + 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) { + 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, errors.New("event signature mismatch") From 8d9181874b103bb2f80d14ec22bf974da8b12a93 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 2 Mar 2023 16:35:06 +0330 Subject: [PATCH 10/12] clean lang selection leftovers --- accounts/abi/bind/bind.go | 133 +++++++++++---------------------- accounts/abi/bind/template.go | 14 +--- accounts/abi/bind/template2.go | 6 +- cmd/abigen/main.go | 17 +---- 4 files changed, 52 insertions(+), 118 deletions(-) diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index 7ad712baccca..179952726a0a 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -33,13 +33,6 @@ import ( "github.com/ethereum/go-ethereum/log" ) -// Lang is a target programming language selector to generate bindings for. -type Lang int - -const ( - LangGo Lang = iota -) - func isKeyWord(arg string) bool { switch arg { case "break": @@ -81,38 +74,33 @@ func isKeyWord(arg string) bool { // to be used as is in client code, but rather as an intermediate struct which // 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) +func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, libs map[string]string, aliases map[string]string) (string, error) { + data, err := bind(types, abis, bytecodes, fsigs, pkg, libs, aliases) if err != nil { return "", err } buffer := new(bytes.Buffer) funcs := map[string]interface{}{ - "bindtype": bindType[lang], - "bindtopictype": bindTopicType[lang], - "namedtype": namedType[lang], + "bindtype": bindType, + "bindtopictype": bindTopicType, "capitalise": capitalise, "decapitalise": decapitalise, } - tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource[lang])) + tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource)) 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 + // Pass the code through gofmt to clean it up + code, err := format.Source(buffer.Bytes()) + if err != nil { + return "", fmt.Errorf("%v\n%s", err, buffer) } - // For all others just return as is for now - return buffer.String(), nil + return string(code), 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) +func BindV2(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, libs map[string]string, aliases map[string]string) (string, error) { + data, err := bind(types, abis, bytecodes, fsigs, pkg, libs, aliases) if err != nil { return "", err } @@ -153,29 +141,24 @@ func BindV2(types []string, abis []string, bytecodes []string, fsigs []map[strin } buffer := new(bytes.Buffer) funcs := map[string]interface{}{ - "bindtype": bindType[lang], - "bindtopictype": bindTopicType[lang], - "namedtype": namedType[lang], + "bindtype": bindType, + "bindtopictype": bindTopicType, "capitalise": capitalise, "decapitalise": decapitalise, } - tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSourceV2[lang])) + tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSourceV2)) 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 + // Pass the code through gofmt to clean it up + code, err := format.Source(buffer.Bytes()) + if err != nil { + return "", fmt.Errorf("%v\n%s", err, buffer) } - // For all others just return as is for now - return buffer.String(), nil + return string(code), 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) { +func bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, 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) @@ -219,14 +202,14 @@ func bind(types []string, abis []string, bytecodes []string, fsigs []map[string] for _, input := range evmABI.Constructor.Inputs { if hasStruct(input.Type) { - bindStructType[lang](input.Type, structs) + bindStructType(input.Type, structs) } } for _, original := range evmABI.Methods { // Normalize the method for capital cases and non-anonymous inputs/outputs normalized := original - normalizedName := methodNormalizer[lang](alias(aliases, original.Name)) + normalizedName := methodNormalizer(alias(aliases, original.Name)) // Ensure there is no duplicated identifier var identifiers = callIdentifiers @@ -246,7 +229,7 @@ func bind(types []string, abis []string, bytecodes []string, fsigs []map[string] normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) } if hasStruct(input.Type) { - bindStructType[lang](input.Type, structs) + bindStructType(input.Type, structs) } } normalized.Outputs = make([]abi.Argument, len(original.Outputs)) @@ -256,7 +239,7 @@ func bind(types []string, abis []string, bytecodes []string, fsigs []map[string] normalized.Outputs[j].Name = capitalise(output.Name) } if hasStruct(output.Type) { - bindStructType[lang](output.Type, structs) + bindStructType(output.Type, structs) } } // Append the methods to the call or transact lists @@ -275,7 +258,7 @@ func bind(types []string, abis []string, bytecodes []string, fsigs []map[string] normalized := original // Ensure there is no duplicated identifier - normalizedName := methodNormalizer[lang](alias(aliases, original.Name)) + normalizedName := methodNormalizer(alias(aliases, original.Name)) if eventIdentifiers[normalizedName] { return nil, fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName) } @@ -299,7 +282,7 @@ func bind(types []string, abis []string, bytecodes []string, fsigs []map[string] normalized.Inputs[j].Name = fmt.Sprintf("%s%d", normalized.Inputs[j].Name, index) } if hasStruct(input.Type) { - bindStructType[lang](input.Type, structs) + bindStructType(input.Type, structs) } } // Append the event to the accumulator list @@ -360,14 +343,8 @@ func bind(types []string, abis []string, bytecodes []string, fsigs []map[string] return data, nil } -// bindType is a set of type binders that convert Solidity types to some supported -// programming language types. -var bindType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{ - LangGo: bindTypeGo, -} - -// bindBasicTypeGo converts basic solidity types(except array, slice and tuple) to Go ones. -func bindBasicTypeGo(kind abi.Type) string { +// bindBasicType converts basic solidity types(except array, slice and tuple) to Go ones. +func bindBasicType(kind abi.Type) string { switch kind.T { case abi.AddressTy: return "common.Address" @@ -390,32 +367,26 @@ func bindBasicTypeGo(kind abi.Type) string { } } -// bindTypeGo converts solidity types to Go ones. Since there is no clear mapping +// bindType converts solidity types to Go ones. Since there is no clear mapping // from all Solidity types to Go ones (e.g. uint17), those that cannot be exactly // mapped will use an upscaled type (e.g. BigDecimal). -func bindTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { +func bindType(kind abi.Type, structs map[string]*tmplStruct) string { switch kind.T { case abi.TupleTy: return structs[kind.TupleRawName+kind.String()].Name case abi.ArrayTy: - return fmt.Sprintf("[%d]", kind.Size) + bindTypeGo(*kind.Elem, structs) + return fmt.Sprintf("[%d]", kind.Size) + bindType(*kind.Elem, structs) case abi.SliceTy: - return "[]" + bindTypeGo(*kind.Elem, structs) + return "[]" + bindType(*kind.Elem, structs) default: - return bindBasicTypeGo(kind) + return bindBasicType(kind) } } -// bindTopicType is a set of type binders that convert Solidity types to some -// supported programming language topic types. -var bindTopicType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{ - LangGo: bindTopicTypeGo, -} - -// bindTopicTypeGo converts a Solidity topic type to a Go one. It is almost the same +// bindTopicType converts a Solidity topic type to a Go one. It is almost the same // functionality as for simple types, but dynamic types get converted to hashes. -func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { - bound := bindTypeGo(kind, structs) +func bindTopicType(kind abi.Type, structs map[string]*tmplStruct) string { + bound := bindType(kind, structs) // todo(rjl493456442) according solidity documentation, indexed event // parameters that are not value types i.e. arrays and structs are not @@ -429,16 +400,10 @@ func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { return bound } -// bindStructType is a set of type binders that convert Solidity tuple types to some supported -// programming language struct definition. -var bindStructType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{ - LangGo: bindStructTypeGo, -} - -// bindStructTypeGo converts a Solidity tuple type to a Go one and records the mapping +// bindStructType converts a Solidity tuple type to a Go one and records the mapping // in the given map. // Notably, this function will resolve and record nested struct recursively. -func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { +func bindStructType(kind abi.Type, structs map[string]*tmplStruct) string { switch kind.T { case abi.TupleTy: // We compose a raw struct name and a canonical parameter expression @@ -459,7 +424,7 @@ func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { name := capitalise(kind.TupleRawNames[i]) name = abi.ResolveNameConflict(name, func(s string) bool { return names[s] }) names[name] = true - fields = append(fields, &tmplField{Type: bindStructTypeGo(*elem, structs), Name: name, SolKind: *elem}) + fields = append(fields, &tmplField{Type: bindStructType(*elem, structs), Name: name, SolKind: *elem}) } name := kind.TupleRawName if name == "" { @@ -473,20 +438,14 @@ func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { } return name case abi.ArrayTy: - return fmt.Sprintf("[%d]", kind.Size) + bindStructTypeGo(*kind.Elem, structs) + return fmt.Sprintf("[%d]", kind.Size) + bindStructType(*kind.Elem, structs) case abi.SliceTy: - return "[]" + bindStructTypeGo(*kind.Elem, structs) + return "[]" + bindStructType(*kind.Elem, structs) default: - return bindBasicTypeGo(kind) + return bindBasicType(kind) } } -// namedType is a set of functions that transform language specific types to -// named versions that may be used inside method names. -var namedType = map[Lang]func(string, abi.Type) string{ - LangGo: func(string, abi.Type) string { panic("this shouldn't be needed") }, -} - // alias returns an alias of the given string based on the aliasing rules // or returns itself if no rule is matched. func alias(aliases map[string]string, n string) string { @@ -497,10 +456,8 @@ func alias(aliases map[string]string, n string) string { } // methodNormalizer is a name transformer that modifies Solidity method names to -// conform to target language naming conventions. -var methodNormalizer = map[Lang]func(string) string{ - LangGo: abi.ToCamelCase, -} +// conform to Go naming conventions. +var methodNormalizer = abi.ToCamelCase // capitalise makes a camel-case string which starts with an upper case character. var capitalise = abi.ToCamelCase diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go index ba1d02032c27..892b833285bd 100644 --- a/accounts/abi/bind/template.go +++ b/accounts/abi/bind/template.go @@ -72,19 +72,9 @@ type tmplStruct struct { Fields []*tmplField // Struct fields definition depends on the binding language. } -// tmplSource is language to template mapping containing all the supported -// programming languages the package can generate to. -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 +// tmplSource is the Go source template that the generated Go contract binding // is based on. -const tmplSourceGo = ` +const tmplSource = ` // Code generated - DO NOT EDIT. // This file is a generated binding and any manual changes will be lost. diff --git a/accounts/abi/bind/template2.go b/accounts/abi/bind/template2.go index f9e53d65eae0..f4acbd9b4793 100644 --- a/accounts/abi/bind/template2.go +++ b/accounts/abi/bind/template2.go @@ -1,8 +1,8 @@ package bind -// tmplSourceGo is the Go source template that the generated Go contract binding -// is based on. -const tmplSourceGoV2 = ` +// tmplSourceV2 is the Go source template that the generated +// Go contract binding V2 is based on. +const tmplSourceV2 = ` // Code generated via abigen V2 - DO NOT EDIT. // This file is a generated binding and any manual changes will be lost. diff --git a/cmd/abigen/main.go b/cmd/abigen/main.go index 087135c3d15a..2ec329687674 100644 --- a/cmd/abigen/main.go +++ b/cmd/abigen/main.go @@ -63,11 +63,6 @@ var ( Name: "out", Usage: "Output file for the generated binding (default = stdout)", } - langFlag = &cli.StringFlag{ - Name: "lang", - Usage: "Destination language for the bindings (go)", - Value: "go", - } aliasFlag = &cli.StringFlag{ Name: "alias", Usage: "Comma separated aliases for function and event renaming, e.g. original1=alias1, original2=alias2", @@ -90,7 +85,6 @@ func init() { excFlag, pkgFlag, outFlag, - langFlag, aliasFlag, v2Flag, } @@ -103,13 +97,6 @@ func abigen(c *cli.Context) error { if c.String(pkgFlag.Name) == "" { utils.Fatalf("No destination package specified (--pkg)") } - var lang bind.Lang - switch c.String(langFlag.Name) { - case "go": - lang = bind.LangGo - default: - utils.Fatalf("Unsupported destination language \"%s\" (--lang)", c.String(langFlag.Name)) - } // If the entire solidity code was specified, build and bind based on that var ( abis []string @@ -226,9 +213,9 @@ func abigen(c *cli.Context) error { err error ) if c.IsSet(v2Flag.Name) { - code, err = bind.BindV2(types, abis, bins, sigs, c.String(pkgFlag.Name), lang, libs, aliases) + code, err = bind.BindV2(types, abis, bins, sigs, c.String(pkgFlag.Name), libs, aliases) } else { - code, err = bind.Bind(types, abis, bins, sigs, c.String(pkgFlag.Name), lang, libs, aliases) + code, err = bind.Bind(types, abis, bins, sigs, c.String(pkgFlag.Name), libs, aliases) } if err != nil { utils.Fatalf("Failed to generate ABI binding: %v", err) From f3d0c2981e0d8e5ead64507d802da5de3104a4e9 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Fri, 3 Mar 2023 12:59:50 +0330 Subject: [PATCH 11/12] fix test --- accounts/abi/bind/bind_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index cbbce7b30889..04dd8afc9c3a 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -2043,7 +2043,7 @@ var bindTests = []struct { // Tests that packages generated by the binder can be successfully compiled and // the requested tester run against it. -func TestGolangBindings(t *testing.T) { +func TestBindings(t *testing.T) { // Skip the test if no Go command can be found gocmd := runtime.GOROOT() + "/bin/go" if !common.FileExist(gocmd) { @@ -2066,7 +2066,7 @@ func TestGolangBindings(t *testing.T) { types = []string{tt.name} } // Generate the binding and create a Go source file in the workspace - bind, err := Bind(types, tt.abi, tt.bytecode, tt.fsigs, "bindtest", LangGo, tt.libs, tt.aliases) + bind, err := Bind(types, tt.abi, tt.bytecode, tt.fsigs, "bindtest", tt.libs, tt.aliases) if err != nil { t.Fatalf("test %d: failed to generate binding: %v", i, err) } From 41b7aed553303122596d48d7aaa05ada8e55a93f Mon Sep 17 00:00:00 2001 From: George Knee Date: Thu, 20 Jul 2023 10:13:14 +0100 Subject: [PATCH 12/12] fix bug in template I am using unnamed parameters. It might be slightly more maintainable to use named parameters, but I get an error when trying to use {{.Type}} inside a struct literal. --- accounts/abi/bind/template2.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accounts/abi/bind/template2.go b/accounts/abi/bind/template2.go index f4acbd9b4793..ed68d75d1baf 100644 --- a/accounts/abi/bind/template2.go +++ b/accounts/abi/bind/template2.go @@ -60,7 +60,7 @@ var ( } func New{{.Type}}Instance(c *{{.Type}}, address common.Address, backend bind.ContractBackend) *{{.Type}}Instance { - return &{{.Type}}Instance{Db: *c, address: address, backend: backend} + return &{{.Type}}Instance{*c,address,backend} } func (i *{{$contract.Type}}Instance) Address() common.Address {