mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-17 09:23:48 +00:00
cmd/abigen, accounts/abi: add support for log subscriptions
This commit is contained in:
parent
c0a1f1c907
commit
7943c71912
5 changed files with 109 additions and 9 deletions
|
|
@ -90,8 +90,14 @@ type ContractTransactor interface {
|
||||||
SendTransaction(ctx context.Context, tx *types.Transaction) error
|
SendTransaction(ctx context.Context, tx *types.Transaction) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContractEventer defines methods to listen for events raised by the contract.
|
||||||
|
type ContractEventer interface {
|
||||||
|
SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error)
|
||||||
|
}
|
||||||
|
|
||||||
// ContractBackend defines the methods needed to work with contracts on a read-write basis.
|
// ContractBackend defines the methods needed to work with contracts on a read-write basis.
|
||||||
type ContractBackend interface {
|
type ContractBackend interface {
|
||||||
ContractCaller
|
ContractCaller
|
||||||
ContractTransactor
|
ContractTransactor
|
||||||
|
ContractEventer
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,16 +63,18 @@ type BoundContract struct {
|
||||||
abi abi.ABI // Reflect based ABI to access the correct Ethereum methods
|
abi abi.ABI // Reflect based ABI to access the correct Ethereum methods
|
||||||
caller ContractCaller // Read interface to interact with the blockchain
|
caller ContractCaller // Read interface to interact with the blockchain
|
||||||
transactor ContractTransactor // Write interface to interact with the blockchain
|
transactor ContractTransactor // Write interface to interact with the blockchain
|
||||||
|
eventer ContractEventer // Listen for events raised by the contract
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBoundContract creates a low level contract interface through which calls
|
// NewBoundContract creates a low level contract interface through which calls
|
||||||
// and transactions may be made through.
|
// and transactions may be made through.
|
||||||
func NewBoundContract(address common.Address, abi abi.ABI, caller ContractCaller, transactor ContractTransactor) *BoundContract {
|
func NewBoundContract(address common.Address, abi abi.ABI, caller ContractCaller, transactor ContractTransactor, eventer ContractEventer) *BoundContract {
|
||||||
return &BoundContract{
|
return &BoundContract{
|
||||||
address: address,
|
address: address,
|
||||||
abi: abi,
|
abi: abi,
|
||||||
caller: caller,
|
caller: caller,
|
||||||
transactor: transactor,
|
transactor: transactor,
|
||||||
|
eventer: eventer,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,7 +82,7 @@ func NewBoundContract(address common.Address, abi abi.ABI, caller ContractCaller
|
||||||
// deployment address with a Go wrapper.
|
// deployment address with a Go wrapper.
|
||||||
func DeployContract(opts *TransactOpts, abi abi.ABI, bytecode []byte, backend ContractBackend, params ...interface{}) (common.Address, *types.Transaction, *BoundContract, error) {
|
func DeployContract(opts *TransactOpts, abi abi.ABI, bytecode []byte, backend ContractBackend, params ...interface{}) (common.Address, *types.Transaction, *BoundContract, error) {
|
||||||
// Otherwise try to deploy the contract
|
// Otherwise try to deploy the contract
|
||||||
c := NewBoundContract(common.Address{}, abi, backend, backend)
|
c := NewBoundContract(common.Address{}, abi, backend, backend, backend)
|
||||||
|
|
||||||
input, err := c.abi.Pack("", params...)
|
input, err := c.abi.Pack("", params...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -225,6 +227,18 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i
|
||||||
return signedTx, nil
|
return signedTx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SubscribeFilterLogs creates a log subscription that streams logs raised by this contract
|
||||||
|
// with topics matching
|
||||||
|
func (c *BoundContract) SubscribeFilterLogs(opts *CallOpts, topics [][]common.Hash, ch chan<- types.Log) (ethereum.Subscription, error) {
|
||||||
|
ctx := ensureContext(opts.Context)
|
||||||
|
q := ethereum.FilterQuery{
|
||||||
|
Addresses: []common.Address{c.address},
|
||||||
|
Topics: topics,
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.eventer.SubscribeFilterLogs(ctx, q, ch)
|
||||||
|
}
|
||||||
|
|
||||||
func ensureContext(ctx context.Context) context.Context {
|
func ensureContext(ctx context.Context) context.Context {
|
||||||
if ctx == nil {
|
if ctx == nil {
|
||||||
return context.TODO()
|
return context.TODO()
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La
|
||||||
var (
|
var (
|
||||||
calls = make(map[string]*tmplMethod)
|
calls = make(map[string]*tmplMethod)
|
||||||
transacts = make(map[string]*tmplMethod)
|
transacts = make(map[string]*tmplMethod)
|
||||||
|
events = make(map[string]*tmplEvent)
|
||||||
)
|
)
|
||||||
for _, original := range evmABI.Methods {
|
for _, original := range evmABI.Methods {
|
||||||
// Normalize the method for capital cases and non-anonymous inputs/outputs
|
// Normalize the method for capital cases and non-anonymous inputs/outputs
|
||||||
|
|
@ -94,6 +95,16 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La
|
||||||
transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original)}
|
transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, e := range evmABI.Events {
|
||||||
|
events[e.Name] = &tmplEvent{
|
||||||
|
Name: e.Name,
|
||||||
|
Inputs: e.Inputs,
|
||||||
|
ID: fmt.Sprintf("%x", e.Id()),
|
||||||
|
Anonymous: e.Anonymous,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
contracts[types[i]] = &tmplContract{
|
contracts[types[i]] = &tmplContract{
|
||||||
Type: capitalise(types[i]),
|
Type: capitalise(types[i]),
|
||||||
InputABI: strings.Replace(strippedABI, "\"", "\\\"", -1),
|
InputABI: strings.Replace(strippedABI, "\"", "\\\"", -1),
|
||||||
|
|
@ -101,6 +112,7 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La
|
||||||
Constructor: evmABI.Constructor,
|
Constructor: evmABI.Constructor,
|
||||||
Calls: calls,
|
Calls: calls,
|
||||||
Transacts: transacts,
|
Transacts: transacts,
|
||||||
|
Events: events, // TODO: add event subscription support to the java template
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Generate the contract template data content and render it
|
// Generate the contract template data content and render it
|
||||||
|
|
@ -116,6 +128,7 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La
|
||||||
"capitalise": capitalise,
|
"capitalise": capitalise,
|
||||||
"decapitalise": decapitalise,
|
"decapitalise": decapitalise,
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource[lang]))
|
tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource[lang]))
|
||||||
if err := tmpl.Execute(buffer, data); err != nil {
|
if err := tmpl.Execute(buffer, data); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,12 @@
|
||||||
|
|
||||||
package bind
|
package bind
|
||||||
|
|
||||||
import "github.com/ethereum/go-ethereum/accounts/abi"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||||
|
)
|
||||||
|
|
||||||
// tmplData is the data structure required to fill the binding template.
|
// tmplData is the data structure required to fill the binding template.
|
||||||
type tmplData struct {
|
type tmplData struct {
|
||||||
|
|
@ -32,6 +37,7 @@ type tmplContract struct {
|
||||||
Constructor abi.Method // Contract constructor for deploy parametrization
|
Constructor abi.Method // Contract constructor for deploy parametrization
|
||||||
Calls map[string]*tmplMethod // Contract calls that only read state data
|
Calls map[string]*tmplMethod // Contract calls that only read state data
|
||||||
Transacts map[string]*tmplMethod // Contract calls that write state data
|
Transacts map[string]*tmplMethod // Contract calls that write state data
|
||||||
|
Events map[string]*tmplEvent // Contract events
|
||||||
}
|
}
|
||||||
|
|
||||||
// tmplMethod is a wrapper around an abi.Method that contains a few preprocessed
|
// tmplMethod is a wrapper around an abi.Method that contains a few preprocessed
|
||||||
|
|
@ -42,6 +48,39 @@ type tmplMethod struct {
|
||||||
Structured bool // Whether the returns should be accumulated into a contract
|
Structured bool // Whether the returns should be accumulated into a contract
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tmplEvent is a wrapper around an abi.Event that contains a few preprocessed
|
||||||
|
// and cached data fields.
|
||||||
|
type tmplEvent struct {
|
||||||
|
Name string
|
||||||
|
Inputs []abi.Argument
|
||||||
|
ID string
|
||||||
|
Anonymous bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e tmplEvent) Canonical() string {
|
||||||
|
var (
|
||||||
|
args []string
|
||||||
|
anonymous string
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, a := range e.Inputs {
|
||||||
|
indexed := ""
|
||||||
|
if a.Indexed {
|
||||||
|
indexed = " indexed"
|
||||||
|
}
|
||||||
|
if a.Name != "" {
|
||||||
|
args = append(args, fmt.Sprintf("%s%s %s", a.Type, indexed, a.Name))
|
||||||
|
} else {
|
||||||
|
args = append(args, fmt.Sprintf("%s%s", a.Type, indexed))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Anonymous {
|
||||||
|
anonymous = " anonymous"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s(%s)%s", e.Name, strings.Join(args, ", "), anonymous)
|
||||||
|
}
|
||||||
|
|
||||||
// tmplSource is language to template mapping containing all the supported
|
// tmplSource is language to template mapping containing all the supported
|
||||||
// programming languages the package can generate to.
|
// programming languages the package can generate to.
|
||||||
var tmplSource = map[Lang]string{
|
var tmplSource = map[Lang]string{
|
||||||
|
|
@ -83,6 +122,7 @@ package {{.Package}}
|
||||||
type {{.Type}} struct {
|
type {{.Type}} struct {
|
||||||
{{.Type}}Caller // Read-only binding to the contract
|
{{.Type}}Caller // Read-only binding to the contract
|
||||||
{{.Type}}Transactor // Write-only binding to the contract
|
{{.Type}}Transactor // Write-only binding to the contract
|
||||||
|
{{.Type}}Eventer // Event listener binding to the contract
|
||||||
}
|
}
|
||||||
|
|
||||||
// {{.Type}}Caller is an auto generated read-only Go binding around an Ethereum contract.
|
// {{.Type}}Caller is an auto generated read-only Go binding around an Ethereum contract.
|
||||||
|
|
@ -95,6 +135,12 @@ package {{.Package}}
|
||||||
contract *bind.BoundContract // Generic contract wrapper for the low level calls
|
contract *bind.BoundContract // Generic contract wrapper for the low level calls
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// {{.Type}}Eventer is an auto generated write-only Go binding around an Ethereum contract.
|
||||||
|
type {{.Type}}Eventer struct {
|
||||||
|
contract *bind.BoundContract // Generic contract wrapper for the low level calls
|
||||||
|
address common.Address // Contract address
|
||||||
|
}
|
||||||
|
|
||||||
// {{.Type}}Session is an auto generated Go binding around an Ethereum contract,
|
// {{.Type}}Session is an auto generated Go binding around an Ethereum contract,
|
||||||
// with pre-set call and transact options.
|
// with pre-set call and transact options.
|
||||||
type {{.Type}}Session struct {
|
type {{.Type}}Session struct {
|
||||||
|
|
@ -134,7 +180,7 @@ package {{.Package}}
|
||||||
|
|
||||||
// New{{.Type}} creates a new instance of {{.Type}}, bound to a specific deployed contract.
|
// New{{.Type}} creates a new instance of {{.Type}}, bound to a specific deployed contract.
|
||||||
func New{{.Type}}(address common.Address, backend bind.ContractBackend) (*{{.Type}}, error) {
|
func New{{.Type}}(address common.Address, backend bind.ContractBackend) (*{{.Type}}, error) {
|
||||||
contract, err := bind{{.Type}}(address, backend, backend)
|
contract, err := bind{{.Type}}(address, backend, backend, backend)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -143,7 +189,7 @@ package {{.Package}}
|
||||||
|
|
||||||
// New{{.Type}}Caller creates a new read-only instance of {{.Type}}, bound to a specific deployed contract.
|
// New{{.Type}}Caller creates a new read-only instance of {{.Type}}, bound to a specific deployed contract.
|
||||||
func New{{.Type}}Caller(address common.Address, caller bind.ContractCaller) (*{{.Type}}Caller, error) {
|
func New{{.Type}}Caller(address common.Address, caller bind.ContractCaller) (*{{.Type}}Caller, error) {
|
||||||
contract, err := bind{{.Type}}(address, caller, nil)
|
contract, err := bind{{.Type}}(address, caller, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -152,20 +198,29 @@ package {{.Package}}
|
||||||
|
|
||||||
// New{{.Type}}Transactor creates a new write-only instance of {{.Type}}, bound to a specific deployed contract.
|
// New{{.Type}}Transactor creates a new write-only instance of {{.Type}}, bound to a specific deployed contract.
|
||||||
func New{{.Type}}Transactor(address common.Address, transactor bind.ContractTransactor) (*{{.Type}}Transactor, error) {
|
func New{{.Type}}Transactor(address common.Address, transactor bind.ContractTransactor) (*{{.Type}}Transactor, error) {
|
||||||
contract, err := bind{{.Type}}(address, nil, transactor)
|
contract, err := bind{{.Type}}(address, nil, transactor, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &{{.Type}}Transactor{contract: contract}, nil
|
return &{{.Type}}Transactor{contract: contract}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New{{.Type}}Eventer creates a new listen only instance of {{.Type}}, bound to a specific deployed contract.
|
||||||
|
func New{{.Type}}Eventer(address common.Address, eventer bind.ContractEventer) (*{{.Type}}Eventer, error) {
|
||||||
|
contract, err := bind{{.Type}}(address, nil, nil, eventer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &{{.Type}}Eventer{contract: contract, address: address}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// bind{{.Type}} binds a generic wrapper to an already deployed contract.
|
// bind{{.Type}} binds a generic wrapper to an already deployed contract.
|
||||||
func bind{{.Type}}(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor) (*bind.BoundContract, error) {
|
func bind{{.Type}}(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, eventer bind.ContractEventer) (*bind.BoundContract, error) {
|
||||||
parsed, err := abi.JSON(strings.NewReader({{.Type}}ABI))
|
parsed, err := abi.JSON(strings.NewReader({{.Type}}ABI))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return bind.NewBoundContract(address, parsed, caller, transactor), nil
|
return bind.NewBoundContract(address, parsed, caller, transactor, eventer), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call invokes the (constant) contract method with params as input values and
|
// Call invokes the (constant) contract method with params as input values and
|
||||||
|
|
@ -263,6 +318,18 @@ package {{.Package}}
|
||||||
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}})
|
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}})
|
||||||
}
|
}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
{{range .Events}}
|
||||||
|
// Solidity: event {{.Canonical}}
|
||||||
|
//
|
||||||
|
// {{if not .Anonymous}}Note: this method will fill in the Event ID topic{{end}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Caller) Subscribe{{.Name}}(opts *bind.CallOpts, ch chan<- types.Log, topics ...[]common.Hash) (ethereum.Subscription, error) {
|
||||||
|
{{if not .Anonymous}}id := []common.Hash{common.HexToHash("{{.ID}}")}
|
||||||
|
topics = append([][]common.Hash{id}, topics...)
|
||||||
|
{{end}}
|
||||||
|
return _{{$contract.Type}}.contract.SubscribeFilterLogs(opts, topics, ch)
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -138,7 +138,7 @@ func BindContract(address *Address, abiJSON string, client *EthereumClient) (con
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &BoundContract{
|
return &BoundContract{
|
||||||
contract: bind.NewBoundContract(address.address, parsed, client.client, client.client),
|
contract: bind.NewBoundContract(address.address, parsed, client.client, client.client, client.client),
|
||||||
address: address.address,
|
address: address.address,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue