add test coverage for events watching

This commit is contained in:
Jared Wasinger 2024-11-27 21:17:13 +07:00 committed by Felix Lange
parent 1e8aca95e9
commit cd18d99180
7 changed files with 425 additions and 13 deletions

View file

@ -404,7 +404,6 @@ func (c *BoundContract) estimateGasLimit(opts *TransactOpts, contract *common.Ad
}
res, err := c.transactor.EstimateGas(ensureContext(opts.Context), msg)
if err != nil {
fmt.Printf("msg data is %x\n", msg.Data)
panic(err)
}
return res, nil
@ -519,6 +518,12 @@ func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]inter
return c.watchLogs(opts, c.abi.Events[name].ID, query...)
}
// WatchLogsForId filters subscribes to contract logs for future blocks, returning a
// subscription object that can be used to tear down the watcher.
func (c *BoundContract) WatchLogsForId(opts *WatchOpts, id common.Hash, query ...[]interface{}) (chan types.Log, event.Subscription, error) {
return c.watchLogs(opts, 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 {

View file

@ -0,0 +1,140 @@
// Code generated via abigen V2 - DO NOT EDIT.
// This file is a generated binding and any manual changes will be lost.
package events
import (
"errors"
"math/big"
"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"
)
// Reference imports to suppress errors if they are not otherwise used.
var (
_ = errors.New
_ = big.NewInt
_ = bind.Bind
_ = common.Big1
_ = types.BloomLookup
_ = abi.ConvertType
)
var CLibraryDeps = []*bind.MetaData{}
// TODO: convert this type to value type after everything works.
// CMetaData contains all meta data concerning the C contract.
var CMetaData = &bind.MetaData{
ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"data\",\"type\":\"uint256\"}],\"name\":\"basic1\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bool\",\"name\":\"flag\",\"type\":\"bool\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"data\",\"type\":\"uint256\"}],\"name\":\"basic2\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"EmitMulti\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"EmitOne\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]",
Pattern: "55ef3c19a0ab1c1845f9e347540c1e51f5",
Bin: "0x6080604052348015600e575f80fd5b506101a08061001c5f395ff3fe608060405234801561000f575f80fd5b5060043610610034575f3560e01c8063cb49374914610038578063e8e49a7114610042575b5f80fd5b61004061004c565b005b61004a6100fd565b005b60017f8f17dc823e2f9fcdf730b8182c935574691e811e7d46399fe0ff0087795cd207600260405161007e9190610151565b60405180910390a260037f8f17dc823e2f9fcdf730b8182c935574691e811e7d46399fe0ff0087795cd20760046040516100b89190610151565b60405180910390a25f15157f3b29b9f6d15ba80d866afb3d70b7548ab1ffda3ef6e65f35f1cb05b0e2b29f4e60016040516100f39190610151565b60405180910390a2565b60017f8f17dc823e2f9fcdf730b8182c935574691e811e7d46399fe0ff0087795cd207600260405161012f9190610151565b60405180910390a2565b5f819050919050565b61014b81610139565b82525050565b5f6020820190506101645f830184610142565b9291505056fea26469706673582212203624d263fed93ccf2a175b7c701e773f413c64394a51928aa2b1968299798fe664736f6c634300081a0033",
}
// C is an auto generated Go binding around an Ethereum contract.
type C struct {
abi abi.ABI
}
// NewC creates a new instance of C.
func NewC() (*C, error) {
parsed, err := CMetaData.GetAbi()
if err != nil {
return nil, err
}
return &C{abi: *parsed}, nil
}
// TODO: create custom exported types where unpack would generate a struct return.
// TODO: test constructor with inputs
func (_C *C) PackConstructor() ([]byte, error) {
return _C.abi.Pack("")
}
// EmitMulti is a free data retrieval call binding the contract method 0xcb493749.
//
// Solidity: function EmitMulti() returns()
func (_C *C) PackEmitMulti() ([]byte, error) {
return _C.abi.Pack("EmitMulti")
}
// EmitOne is a free data retrieval call binding the contract method 0xe8e49a71.
//
// Solidity: function EmitOne() returns()
func (_C *C) PackEmitOne() ([]byte, error) {
return _C.abi.Pack("EmitOne")
}
// CBasic1 represents a Basic1 event raised by the C contract.
type CBasic1 struct {
Id *big.Int
Data *big.Int
Raw *types.Log // Blockchain specific contextual infos
}
func CBasic1EventID() common.Hash {
return common.HexToHash("0x8f17dc823e2f9fcdf730b8182c935574691e811e7d46399fe0ff0087795cd207")
}
func (_C *C) UnpackBasic1Event(log *types.Log) (*CBasic1, error) {
event := "Basic1"
if log.Topics[0] != _C.abi.Events[event].ID {
return nil, errors.New("event signature mismatch")
}
out := new(CBasic1)
if len(log.Data) > 0 {
if err := _C.abi.UnpackIntoInterface(out, event, log.Data); err != nil {
return nil, err
}
}
var indexed abi.Arguments
for _, arg := range _C.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
}
// CBasic2 represents a Basic2 event raised by the C contract.
type CBasic2 struct {
Flag bool
Data *big.Int
Raw *types.Log // Blockchain specific contextual infos
}
func CBasic2EventID() common.Hash {
return common.HexToHash("0x3b29b9f6d15ba80d866afb3d70b7548ab1ffda3ef6e65f35f1cb05b0e2b29f4e")
}
func (_C *C) UnpackBasic2Event(log *types.Log) (*CBasic2, error) {
event := "Basic2"
if log.Topics[0] != _C.abi.Events[event].ID {
return nil, errors.New("event signature mismatch")
}
out := new(CBasic2)
if len(log.Data) > 0 {
if err := _C.abi.UnpackIntoInterface(out, event, log.Data); err != nil {
return nil, err
}
}
var indexed abi.Arguments
for _, arg := range _C.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
}

View file

@ -0,0 +1 @@
{"contracts":{"contract.sol:C":{"abi":[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"data","type":"uint256"}],"name":"basic1","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bool","name":"flag","type":"bool"},{"indexed":false,"internalType":"uint256","name":"data","type":"uint256"}],"name":"basic2","type":"event"},{"inputs":[],"name":"EmitMulti","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"EmitOne","outputs":[],"stateMutability":"nonpayable","type":"function"}],"bin":"6080604052348015600e575f80fd5b506101a08061001c5f395ff3fe608060405234801561000f575f80fd5b5060043610610034575f3560e01c8063cb49374914610038578063e8e49a7114610042575b5f80fd5b61004061004c565b005b61004a6100fd565b005b60017f8f17dc823e2f9fcdf730b8182c935574691e811e7d46399fe0ff0087795cd207600260405161007e9190610151565b60405180910390a260037f8f17dc823e2f9fcdf730b8182c935574691e811e7d46399fe0ff0087795cd20760046040516100b89190610151565b60405180910390a25f15157f3b29b9f6d15ba80d866afb3d70b7548ab1ffda3ef6e65f35f1cb05b0e2b29f4e60016040516100f39190610151565b60405180910390a2565b60017f8f17dc823e2f9fcdf730b8182c935574691e811e7d46399fe0ff0087795cd207600260405161012f9190610151565b60405180910390a2565b5f819050919050565b61014b81610139565b82525050565b5f6020820190506101645f830184610142565b9291505056fea26469706673582212203624d263fed93ccf2a175b7c701e773f413c64394a51928aa2b1968299798fe664736f6c634300081a0033"}},"version":"0.8.26+commit.8a97fa7a.Darwin.appleclang"}

View file

@ -2,5 +2,36 @@
pragma solidity ^0.8.26;
contract C {
event basic1(
uint256 indexed id,
uint256 data
);
event basic2(
bool indexed flag,
uint256 data
);
// TODO: consider log test where data is hashed? maybe not necessary for v2 coverage
function EmitOne() public {
emit basic1(
uint256(1),
uint256(2));
}
// emit multiple events, different types
function EmitMulti() public {
emit basic1(
uint256(1),
uint256(2));
emit basic1(
uint256(3),
uint256(4));
emit basic2(
false,
uint256(1));
}
constructor() {
// do something with these
}
}

View file

@ -0,0 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract C {
}

View file

@ -29,6 +29,7 @@ import (
"strings"
)
// TODO: it's weird to have a mirror interface of this in the bind package...
type ContractInstance struct {
Address common.Address
Backend bind.ContractBackend
@ -233,10 +234,10 @@ func FilterLogs[T any](instance *ContractInstance, opts *bind.FilterOpts, eventI
}
// TODO: adding docs soon (jwasinger)
func WatchLogs[T any](instance *ContractInstance, opts *bind.WatchOpts, eventID common.Hash, unpack func(*types.Log) (*T, error), sink chan<- *T, topics ...[]any) (event.Subscription, error) {
func WatchLogs[T any](instance *ContractInstance, abi abi.ABI, opts *bind.WatchOpts, eventID common.Hash, unpack func(*types.Log) (*T, error), sink chan<- *T, topics ...[]any) (event.Subscription, error) {
backend := instance.Backend
c := bind.NewBoundContract(instance.Address, abi.ABI{}, backend, backend, backend)
logs, sub, err := c.WatchLogs(opts, eventID.String(), topics...)
c := bind.NewBoundContract(instance.Address, abi, backend, backend, backend)
logs, sub, err := c.WatchLogsForId(opts, eventID, topics...)
if err != nil {
return nil, err
}
@ -335,10 +336,10 @@ func (it *EventIterator[T]) Close() error {
// Transact creates and submits a transaction to the bound contract instance
// using the provided abi-encoded input (or nil).
func Transact(instance bind.ContractInstance, opts *bind.TransactOpts, input []byte) (*types.Transaction, error) {
func Transact(instance *ContractInstance, opts *bind.TransactOpts, input []byte) (*types.Transaction, error) {
var (
addr = instance.Address()
backend = instance.Backend()
addr = instance.Address
backend = instance.Backend
)
c := bind.NewBoundContract(addr, abi.ABI{}, backend, backend, backend)
return c.RawTransact(opts, input)
@ -346,8 +347,8 @@ func Transact(instance bind.ContractInstance, opts *bind.TransactOpts, input []b
// Call performs an eth_call on the given bound contract instance, using the
// provided abi-encoded input (or nil).
func Call(instance bind.ContractInstance, opts *bind.CallOpts, input []byte) ([]byte, error) {
backend := instance.Backend()
c := bind.NewBoundContract(instance.Address(), abi.ABI{}, backend, backend, backend)
func Call(instance *ContractInstance, opts *bind.CallOpts, input []byte) ([]byte, error) {
backend := instance.Backend
c := bind.NewBoundContract(instance.Address, abi.ABI{}, backend, backend, backend)
return c.CallRaw(opts, input)
}

View file

@ -23,6 +23,7 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/accounts/abi/bind/testdata/v2/events"
"github.com/ethereum/go-ethereum/accounts/abi/bind/testdata/v2/nested_libraries"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
@ -35,6 +36,7 @@ import (
"math/big"
"strings"
"testing"
"time"
)
var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
@ -67,7 +69,7 @@ func testSetup() (*bind.TransactOpts, *backends.SimulatedBackend, error) {
}
signer := types.LatestSigner(params.AllDevChainProtocolChanges)
opts := bind.TransactOpts{
opts := &bind.TransactOpts{
From: testAddr,
Nonce: nil,
Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) {
@ -92,12 +94,17 @@ func testSetup() (*bind.TransactOpts, *backends.SimulatedBackend, error) {
Backend: backend,
Client: backend.Client(),
}
return &opts, &bindBackend, nil
return opts, &bindBackend, nil
}
// test deployment and interaction for a basic contract with no library deps
func TestDeployment(t *testing.T) {
}
// test that deploying a contract with library dependencies works,
// verifying by calling the deployed contract.
func TestDeployment(t *testing.T) {
func TestDeploymentLibraries(t *testing.T) {
opts, bindBackend, err := testSetup()
if err != nil {
t.Fatalf("err setting up test: %v", err)
@ -278,6 +285,227 @@ func TestDeploymentWithOverrides(t *testing.T) {
}
*/
// note to self: see how to filter logs in eth_call.
func TestEvents(t *testing.T) {
// test watch/filter logs method on a contract that emits various kinds of events (struct-containing, etc.)
txAuth, backend, err := testSetup()
if err != nil {
t.Fatalf("error setting up testing env: %v", err)
}
deploymentParams := DeploymentParams{
Contracts: []ContractDeployParams{
{
Meta: events.CMetaData,
},
},
}
res, err := LinkAndDeploy(txAuth, backend, deploymentParams)
if err != nil {
t.Fatalf("error deploying contract for testing: %v", err)
}
backend.Commit()
if _, err := bind.WaitDeployed(context.Background(), backend, res.Txs[events.CMetaData.Pattern]); err != nil {
t.Fatalf("WaitDeployed failed %v", err)
}
ctrct, err := events.NewC()
if err != nil {
t.Fatalf("error instantiating contract instance: %v", err)
}
abi, err := events.CMetaData.GetAbi()
if err != nil {
t.Fatalf("error getting contract abi: %v", err)
}
// TODO: why did I introduce separate type, and not just use bound contract?
boundContract := ContractInstance{
res.Addrs[events.CMetaData.Pattern],
backend,
}
unpackBasic := func(raw *types.Log) (*events.CBasic1, error) {
return &events.CBasic1{
Id: (new(big.Int)).SetBytes(raw.Topics[0].Bytes()),
Data: (new(big.Int)).SetBytes(raw.Data),
}, nil
}
unpackBasic2 := func(raw *types.Log) (*events.CBasic2, error) {
return &events.CBasic2{
Flag: false, // TODO: how to unpack different types to go types? this should be exposed via abi package.
Data: (new(big.Int)).SetBytes(raw.Data),
}, nil
}
newCBasic1Ch := make(chan *events.CBasic1)
newCBasic2Ch := make(chan *events.CBasic2)
watchOpts := &bind.WatchOpts{
Start: nil,
Context: context.Background(),
}
sub1, err := WatchLogs[events.CBasic1](&boundContract, *abi, watchOpts, events.CBasic1EventID(), unpackBasic, newCBasic1Ch)
sub2, err := WatchLogs[events.CBasic2](&boundContract, *abi, watchOpts, events.CBasic2EventID(), unpackBasic2, newCBasic2Ch)
defer sub1.Unsubscribe()
defer sub2.Unsubscribe()
fmt.Printf("watching for event with id %x\n", events.CBasic1EventID())
fmt.Printf("watching for event with id %x\n", events.CBasic2EventID())
//wtf do I do with this subscriptions??
_ = sub1
_ = sub2
crtctInstance := &ContractInstance{
Address: res.Addrs[events.CMetaData.Pattern],
Backend: backend,
}
_ = ctrct
packedCall, err := ctrct.PackEmitMulti()
if err != nil {
t.Fatalf("failed to pack EmitMulti arguments")
}
tx, err := Transact(crtctInstance, txAuth, packedCall)
if err != nil {
t.Fatalf("failed to send transaction...")
}
backend.Commit()
if _, err := bind.WaitMined(context.Background(), backend, tx); err != nil {
t.Fatalf("error waiting for tx to be mined: %v", err)
}
timeout := time.NewTimer(2 * time.Second)
e1Count := 0
e2Count := 0
for {
select {
case _ = <-newCBasic1Ch:
e1Count++
case _ = <-newCBasic2Ch:
e2Count++
case _ = <-timeout.C:
goto done
}
if e1Count == 2 && e2Count == 1 {
break
}
}
done:
if e1Count != 2 {
t.Fatalf("expected event type 1 count to be 2. got %d", e1Count)
}
if e2Count != 1 {
t.Fatalf("expected event type 2 count to be 1. got %d", e2Count)
}
// commit a few blocks...
// now filter for the previously-emitted events.
// TODO: test that returning error from unpack prevents event from being received by sink.
}
func TestEventsUnpackFailure(t *testing.T) {
// test watch/filter logs method on a contract that emits various kinds of events (struct-containing, etc.)
txAuth, backend, err := testSetup()
if err != nil {
t.Fatalf("error setting up testing env: %v", err)
}
deploymentParams := DeploymentParams{
Contracts: []ContractDeployParams{
{
Meta: events.CMetaData,
},
},
}
res, err := LinkAndDeploy(txAuth, backend, deploymentParams)
if err != nil {
t.Fatalf("error deploying contract for testing: %v", err)
}
backend.Commit()
if _, err := bind.WaitDeployed(context.Background(), backend, res.Txs[events.CMetaData.Pattern]); err != nil {
t.Fatalf("WaitDeployed failed %v", err)
}
ctrct, err := events.NewC()
if err != nil {
t.Fatalf("error instantiating contract instance: %v", err)
}
abi, err := events.CMetaData.GetAbi()
if err != nil {
t.Fatalf("error getting contract abi: %v", err)
}
// TODO: why did I introduce separate type, and not just use bound contract?
boundContract := ContractInstance{
res.Addrs[events.CMetaData.Pattern],
backend,
}
unpackBasic := func(raw *types.Log) (*events.CBasic1, error) {
return nil, fmt.Errorf("could not unpack event")
}
unpackBasic2 := func(raw *types.Log) (*events.CBasic2, error) {
return &events.CBasic2{
Flag: false, // TODO: how to unpack different types to go types? this should be exposed via abi package.
Data: (new(big.Int)).SetBytes(raw.Data),
}, nil
}
newCBasic1Ch := make(chan *events.CBasic1)
newCBasic2Ch := make(chan *events.CBasic2)
watchOpts := &bind.WatchOpts{
Start: nil,
Context: context.Background(),
}
sub1, err := WatchLogs[events.CBasic1](&boundContract, *abi, watchOpts, events.CBasic1EventID(), unpackBasic, newCBasic1Ch)
sub2, err := WatchLogs[events.CBasic2](&boundContract, *abi, watchOpts, events.CBasic2EventID(), unpackBasic2, newCBasic2Ch)
defer sub1.Unsubscribe()
defer sub2.Unsubscribe()
//wtf do I do with this subscriptions??
_ = sub1
_ = sub2
crtctInstance := &ContractInstance{
Address: res.Addrs[events.CMetaData.Pattern],
Backend: backend,
}
_ = ctrct
packedCall, err := ctrct.PackEmitMulti()
if err != nil {
t.Fatalf("failed to pack EmitMulti arguments")
}
tx, err := Transact(crtctInstance, txAuth, packedCall)
if err != nil {
t.Fatalf("failed to send transaction...")
}
backend.Commit()
if _, err := bind.WaitMined(context.Background(), backend, tx); err != nil {
t.Fatalf("error waiting for tx to be mined: %v", err)
}
timeout := time.NewTimer(2 * time.Second)
e1Count := 0
e2Count := 0
for {
select {
case _ = <-newCBasic1Ch:
e1Count++
case _ = <-newCBasic2Ch:
e2Count++
case _ = <-timeout.C:
goto done
}
if e1Count == 2 && e2Count == 1 {
break
}
}
done:
if e1Count != 0 {
t.Fatalf("expected event type 1 count to be 0. got %d", e1Count)
}
if e2Count != 1 {
t.Fatalf("expected event type 2 count to be 1. got %d", e2Count)
}
}