diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index eb24b4b977..a928a46c95 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -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 { diff --git a/accounts/abi/bind/testdata/v2/events/bindings.go b/accounts/abi/bind/testdata/v2/events/bindings.go new file mode 100644 index 0000000000..720f2716b5 --- /dev/null +++ b/accounts/abi/bind/testdata/v2/events/bindings.go @@ -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 +} + diff --git a/accounts/abi/bind/testdata/v2/events/combined-abi.json b/accounts/abi/bind/testdata/v2/events/combined-abi.json new file mode 100644 index 0000000000..85ab4178c2 --- /dev/null +++ b/accounts/abi/bind/testdata/v2/events/combined-abi.json @@ -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"} diff --git a/accounts/abi/bind/testdata/v2/events/contract.sol b/accounts/abi/bind/testdata/v2/events/contract.sol index 1e26c0e04d..bb55e3a249 100644 --- a/accounts/abi/bind/testdata/v2/events/contract.sol +++ b/accounts/abi/bind/testdata/v2/events/contract.sol @@ -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 + } } \ No newline at end of file diff --git a/accounts/abi/bind/testdata/v2/simple/contract.sol b/accounts/abi/bind/testdata/v2/simple/contract.sol new file mode 100644 index 0000000000..1e26c0e04d --- /dev/null +++ b/accounts/abi/bind/testdata/v2/simple/contract.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +contract C { + +} \ No newline at end of file diff --git a/accounts/abi/bind/v2/lib.go b/accounts/abi/bind/v2/lib.go index 0895d446c8..ed741e2397 100644 --- a/accounts/abi/bind/v2/lib.go +++ b/accounts/abi/bind/v2/lib.go @@ -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) } diff --git a/accounts/abi/bind/v2/lib_test.go b/accounts/abi/bind/v2/lib_test.go index 84fb4b28b7..a891b5eb36 100644 --- a/accounts/abi/bind/v2/lib_test.go +++ b/accounts/abi/bind/v2/lib_test.go @@ -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) + } }