accounts/abi/bind/v2: clean up lib.go:

* improve function and type comments (adding a TODO for FilterEvents/WatchEvents descriptions)
* simplify EventIterator implementation, change signature of Next to return an error if it occurred.  Don't prevent further iteration if the subscription was cancelled before all the logs could be read from the channel (this needs a test case).
This commit is contained in:
Jared Wasinger 2025-02-11 20:01:58 -08:00
parent 9d5003c4c4
commit 9d7001c1f6
2 changed files with 77 additions and 65 deletions

View file

@ -32,7 +32,8 @@ type ContractEvent interface {
ContractEventName() string ContractEventName() string
} }
// FilterEvents returns an EventIterator instance for filtering historical events based on the event id and a block range. // FilterEvents filters a historical block range for instances of emission of a
// specific event type from a specified contract. It returns an error if... (TODO: enumerate error scenarios)
func FilterEvents[Ev ContractEvent](c *BoundContract, opts *FilterOpts, unpack func(*types.Log) (*Ev, error), topics ...[]any) (*EventIterator[Ev], error) { func FilterEvents[Ev ContractEvent](c *BoundContract, opts *FilterOpts, unpack func(*types.Log) (*Ev, error), topics ...[]any) (*EventIterator[Ev], error) {
var e Ev var e Ev
logs, sub, err := c.FilterLogs(opts, e.ContractEventName(), topics...) logs, sub, err := c.FilterLogs(opts, e.ContractEventName(), topics...)
@ -42,10 +43,11 @@ func FilterEvents[Ev ContractEvent](c *BoundContract, opts *FilterOpts, unpack f
return &EventIterator[Ev]{unpack: unpack, logs: logs, sub: sub}, nil return &EventIterator[Ev]{unpack: unpack, logs: logs, sub: sub}, nil
} }
// WatchEvents causes logs emitted with a given event id from a specified // WatchEvents creates an event subscription to notify when logs of the specified event type are emitted from the given contract.
// contract to be intercepted, unpacked, and forwarded to sink. If // Received logs are unpacked and forwarded to sink. If topics are specified, only events are forwarded which match the
// unpack returns an error, the returned subscription is closed with the // topics.
// error. //
// WatchEvents returns a subscription or an error if ... (TODO: enumerate error scenarios)
func WatchEvents[Ev ContractEvent](c *BoundContract, opts *WatchOpts, unpack func(*types.Log) (*Ev, error), sink chan<- *Ev, topics ...[]any) (event.Subscription, error) { func WatchEvents[Ev ContractEvent](c *BoundContract, opts *WatchOpts, unpack func(*types.Log) (*Ev, error), sink chan<- *Ev, topics ...[]any) (event.Subscription, error) {
var e Ev var e Ev
logs, sub, err := c.WatchLogs(opts, e.ContractEventName(), topics...) logs, sub, err := c.WatchLogs(opts, e.ContractEventName(), topics...)
@ -79,86 +81,81 @@ func WatchEvents[Ev ContractEvent](c *BoundContract, opts *WatchOpts, unpack fun
}), nil }), nil
} }
// EventIterator is returned from FilterLogs and is used to iterate over the raw // EventIterator is an object for iterating over the results of a event log filter call.
// logs and unpacked data for events.
type EventIterator[T any] struct { type EventIterator[T any] struct {
event *T // event containing the contract specifics and raw log current *T
unpack func(*types.Log) (*T, error)
unpack func(*types.Log) (*T, error) // Unpack function for the event logs <-chan types.Log
sub ethereum.Subscription
logs <-chan types.Log // Log channel receiving the found contract events fail error // error to hold reason for iteration failure
sub ethereum.Subscription // Subscription for solc_errors, completion and termination closed bool // true if Close has been called
done bool // Whether the subscription completed delivering logs
fail error // Occurred error to stop iteration
} }
// Value returns the current value of the iterator, or nil if there isn't one. // Value returns the current value of the iterator, or nil if there isn't one.
func (it *EventIterator[T]) Value() *T { func (it *EventIterator[T]) Value() *T {
return it.event return it.current
} }
// Next advances the iterator to the subsequent event, returning whether there // Next advances the iterator to the subsequent event (if there is one),
// are any more events found. In case of a retrieval or parsing error, false is // returning true if the iterator advanced.
// returned and Error() can be queried for the exact failure. //
func (it *EventIterator[T]) Next() bool { // If the attempt to convert the raw log object to an instance of T using the
// If the iterator failed, stop iterating // unpack function provided via FilterEvents returns an error: that error is returned and subsequent calls to Next will
if it.fail != nil { // not advance the iterator.
return false func (it *EventIterator[T]) Next() (advanced bool, err error) {
// If the iterator failed with an error, don't proceed
if it.fail != nil || it.closed {
return false, it.fail
} }
// If the iterator completed, deliver directly whatever's available // if the iterator is still active, block until a log is received or the
if it.done { // underlying subscription terminates.
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 { select {
case log := <-it.logs: case log := <-it.logs:
res, err := it.unpack(&log) res, err := it.unpack(&log)
if err != nil { if err != nil {
it.fail = err it.fail = err
return false return false, it.fail
}
it.current = res
return true, it.fail
case <-it.sub.Err():
// regardless of how the subscription ends, still be able to iterate
// over any unread logs.
select {
case log := <-it.logs:
res, err := it.unpack(&log)
if err != nil {
it.fail = err
return false, it.fail
}
it.current = res
return true, it.fail
default:
return false, it.fail
} }
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. // Error returns an error if iteration has failed.
func (it *EventIterator[T]) Error() error { func (it *EventIterator[T]) Error() error {
return it.fail return it.fail
} }
// Close terminates the iteration process, releasing any pending underlying // Close releases any pending underlying resources. Any subsequent calls to
// resources. // Next will not advance the iterator, but the current value remains accessible.
func (it *EventIterator[T]) Close() error { func (it *EventIterator[T]) Close() error {
it.closed = true
it.sub.Unsubscribe() it.sub.Unsubscribe()
return nil return nil
} }
// Call performs an eth_call on the given bound contract instance, using the provided // Call performs an eth_call to a contract with optional call data.
// ABI-encoded input.
// //
// To call a function that doesn't return any output, pass nil as the unpack function. // To call a function that doesn't return any output, pass nil as the unpack function.
// This can be useful if you just want to check that the function doesn't revert. // This can be useful if you just want to check that the function doesn't revert.
func Call[T any](c *BoundContract, opts *CallOpts, packedInput []byte, unpack func([]byte) (T, error)) (T, error) { func Call[T any](c *BoundContract, opts *CallOpts, calldata []byte, unpack func([]byte) (T, error)) (T, error) {
var defaultResult T var defaultResult T
packedOutput, err := c.CallRaw(opts, packedInput) packedOutput, err := c.CallRaw(opts, calldata)
if err != nil { if err != nil {
return defaultResult, err return defaultResult, err
} }
@ -175,19 +172,19 @@ func Call[T any](c *BoundContract, opts *CallOpts, packedInput []byte, unpack fu
return res, err return res, err
} }
// Transact initiates a transaction with the given raw calldata as the input. // Transact creates and submits a transaction to a contract with optional input data.
func Transact(c *BoundContract, opt *TransactOpts, packedInput []byte) (*types.Transaction, error) { func Transact(c *BoundContract, opt *TransactOpts, data []byte) (*types.Transaction, error) {
addr := c.address addr := c.address
return c.transact(opt, &addr, packedInput) return c.transact(opt, &addr, data)
} }
// DeployContract deploys a contract onto the Ethereum blockchain and binds the // DeployContract creates and submits a deployment transaction based on the deployer bytecode and
// deployment address with a Go wrapper. It expects its parameters to be abi-encoded // optional ABI-encoded constructor input. It returns the address and creation transaction of the
// bytes. // pending contract, or an error if the creation failed.
func DeployContract(opts *TransactOpts, bytecode []byte, backend ContractBackend, packedParams []byte) (common.Address, *types.Transaction, error) { func DeployContract(opts *TransactOpts, bytecode []byte, backend ContractBackend, constructorInput []byte) (common.Address, *types.Transaction, error) {
c := NewBoundContract(common.Address{}, abi.ABI{}, backend, backend, backend) c := NewBoundContract(common.Address{}, abi.ABI{}, backend, backend, backend)
tx, err := c.RawCreationTransact(opts, append(bytecode, packedParams...)) tx, err := c.RawCreationTransact(opts, append(bytecode, constructorInput...))
if err != nil { if err != nil {
return common.Address{}, nil, err return common.Address{}, nil, err
} }

View file

@ -283,12 +283,27 @@ done:
if err != nil { if err != nil {
t.Fatalf("error filtering logs %v\n", err) t.Fatalf("error filtering logs %v\n", err)
} }
e1Count = 0 e1Count = 0
e2Count = 0 e2Count = 0
for it.Next() { for {
advanced, err := it.Next()
if err != nil {
t.Fatalf("got error while iterating events for e1: %v", err)
}
if !advanced {
break
}
e1Count++ e1Count++
} }
for it2.Next() { for {
advanced, err := it2.Next()
if err != nil {
t.Fatalf("got error while iterating events for e2: %v", err)
}
if !advanced {
break
}
e2Count++ e2Count++
} }
if e1Count != 2 { if e1Count != 2 {