diff --git a/ethergo/README.md b/ethergo/README.md
index 4a474621a3..02cfa8a70b 100644
--- a/ethergo/README.md
+++ b/ethergo/README.md
@@ -29,15 +29,16 @@ root
│ ├── geth: Contains an embedded geth backend. This is useful for testing against a local geth instance without forking capabilities. This does not require docker and runs fully embedded in the go application, as such it is faster than the docker-based backends, but less versatile. Used when an rpc address is needed for a localnet.
│ ├── preset: Contains a number of preset backends for testing.
│ ├── simulated: The fastest backend, this does not expose an rpc endpoint and uses geth's [simulated backend](https://goethereumbook.org/en/client-simulated/)
-├── chain: Contains a client for interacting with the chain. This will be removed in a future version. Please use [client](./client) going forward.
+├── chain: Contains a client for interacting with the chain. This will be removed in a future version. Please use [client](./client) going forward.
│ ├── chainwatcher: Watches the chain for events, blocks and logs
│ ├── client: Contains eth clients w/ rate limiting, workarounds for bugs in some chains, etc.
│ ├── gas: Contains a deterministic gas estimator
-│ ├── watcher: Client interface for chain watcher.
+│ ├── watcher: Client interface for chain watcher.
├── contracts: Contains interfaces for using contracts with the deployer + manager
├── client: Contains an open tracing compatible ethclient with batching.
├── example: Contains a full featured example of how to use deployer + manager
├── forker: Allows the use of fork tests in live chains without docker using an anvil binary.
+├── listener: Drop-in contract listener
├── manager: Manages contract deployments.
├── mocks: Contains mocks for testing various data types (transactions, addresses, logs, etc)
├── parser: Parse hardhat deployments
diff --git a/ethergo/chain/listener/listener_test.go b/ethergo/chain/listener/listener_test.go
deleted file mode 100644
index 9634a07bfe..0000000000
--- a/ethergo/chain/listener/listener_test.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package listener_test
-
-import (
- "context"
- "math/big"
- "sync"
- "time"
-
- "github.com/brianvoe/gofakeit/v6"
- "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/crypto"
- "github.com/synapsecns/sanguine/ethergo/chain/listener"
- "github.com/synapsecns/sanguine/services/rfq/contracts/testcontracts/fastbridgemock"
-)
-
-func (l *ListenerTestSuite) TestListenForEvents() {
- _, handle := l.manager.GetMockFastBridge(l.GetTestContext(), l.backend)
- var wg sync.WaitGroup
- const iterations = 50
- for i := 0; i < iterations; i++ {
- i := i
- go func(num int) {
- wg.Add(1)
- defer wg.Done()
-
- testAddress := common.BigToAddress(big.NewInt(int64(i)))
- auth := l.backend.GetTxContext(l.GetTestContext(), nil)
-
- //nolint:typecheck
- txID := [32]byte(crypto.Keccak256(testAddress.Bytes()))
- bridgeRequestTX, err := handle.MockBridgeRequest(auth.TransactOpts, txID, testAddress, fastbridgemock.IFastBridgeBridgeParams{
- DstChainId: gofakeit.Uint32(),
- Sender: testAddress,
- To: testAddress,
- OriginToken: testAddress,
- DestToken: testAddress,
- OriginAmount: new(big.Int).SetUint64(gofakeit.Uint64()),
- DestAmount: new(big.Int).SetUint64(gofakeit.Uint64()),
- SendChainGas: false,
- Deadline: new(big.Int).SetUint64(uint64(time.Now().Add(-1 * time.Second * time.Duration(gofakeit.Uint16())).Unix())),
- })
- l.NoError(err)
- l.NotNil(bridgeRequestTX)
-
- l.backend.WaitForConfirmation(l.GetTestContext(), bridgeRequestTX)
-
- bridgeResponseTX, err := handle.MockBridgeRelayer(auth.TransactOpts,
- // transactionID
- txID,
- // relayer
- testAddress,
- // to
- testAddress,
- // originChainID
- uint32(gofakeit.Uint16()),
- // originToken
- testAddress,
- // destToken
- testAddress,
- // originAmount
- new(big.Int).SetUint64(gofakeit.Uint64()),
- // destAmount
- new(big.Int).SetUint64(gofakeit.Uint64()),
- // gasAmount
- new(big.Int).SetUint64(gofakeit.Uint64()))
- l.NoError(err)
- l.NotNil(bridgeResponseTX)
- l.backend.WaitForConfirmation(l.GetTestContext(), bridgeResponseTX)
- }(i)
- }
-
- wg.Wait()
-
- startBlock, err := handle.DeployBlock(&bind.CallOpts{Context: l.GetTestContext()})
- l.NoError(err)
-
- cl, err := listener.NewChainListener(l.backend, l.store, handle.Address(), uint64(startBlock.Int64()), l.metrics)
- l.NoError(err)
-
- eventCount := 0
-
- // TODO: check for timeout,but it will be extremely obvious if it gets hit.
- listenCtx, cancel := context.WithCancel(l.GetTestContext())
- err = cl.Listen(listenCtx, func(ctx context.Context, log types.Log) error {
- eventCount++
-
- if eventCount == iterations*2 {
- cancel()
- }
-
- return nil
- })
-}
diff --git a/ethergo/chain/listener/suite_test.go b/ethergo/chain/listener/suite_test.go
deleted file mode 100644
index d81ea6fe32..0000000000
--- a/ethergo/chain/listener/suite_test.go
+++ /dev/null
@@ -1,98 +0,0 @@
-package listener_test
-
-import (
- "math/big"
- "testing"
-
- "github.com/Flaque/filet"
- "github.com/ethereum/go-ethereum/accounts/abi/bind"
- "github.com/stretchr/testify/suite"
- "github.com/synapsecns/sanguine/core/metrics"
- "github.com/synapsecns/sanguine/core/testsuite"
- "github.com/synapsecns/sanguine/ethergo/backends"
- "github.com/synapsecns/sanguine/ethergo/backends/geth"
- "github.com/synapsecns/sanguine/ethergo/chain/listener"
- "github.com/synapsecns/sanguine/ethergo/contracts"
- "github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge"
- "github.com/synapsecns/sanguine/services/rfq/relayer/reldb"
- "github.com/synapsecns/sanguine/services/rfq/relayer/reldb/sqlite"
- "github.com/synapsecns/sanguine/services/rfq/testutil"
-)
-
-const chainID = 10
-
-type ListenerTestSuite struct {
- *testsuite.TestSuite
- manager *testutil.DeployManager
- backend backends.SimulatedTestBackend
- store reldb.Service
- metrics metrics.Handler
- fastBridge *fastbridge.FastBridgeRef
- fastBridgeMetadata contracts.DeployedContract
-}
-
-func NewListenerSuite(tb testing.TB) *ListenerTestSuite {
- return &ListenerTestSuite{
- TestSuite: testsuite.NewTestSuite(tb),
- }
-}
-
-func TestListenerSuite(t *testing.T) {
- suite.Run(t, NewListenerSuite(t))
-}
-
-func (l *ListenerTestSuite) SetupTest() {
- l.TestSuite.SetupTest()
-
- l.manager = testutil.NewDeployManager(l.T())
- l.backend = geth.NewEmbeddedBackendForChainID(l.GetTestContext(), l.T(), big.NewInt(chainID))
- var err error
- l.metrics = metrics.NewNullHandler()
- l.store, err = sqlite.NewSqliteStore(l.GetTestContext(), filet.TmpDir(l.T(), ""), l.metrics)
- l.Require().NoError(err)
-
- l.fastBridgeMetadata, l.fastBridge = l.manager.GetFastBridge(l.GetTestContext(), l.backend)
-}
-
-func (l *ListenerTestSuite) TestGetMetadataNoStore() {
- deployBlock, err := l.fastBridge.DeployBlock(&bind.CallOpts{Context: l.GetTestContext()})
- l.NoError(err)
-
- // nothing stored, should use start block
- cl := listener.NewTestChainListener(listener.TestChainListenerArgs{
- Address: l.fastBridge.Address(),
- InitialBlock: deployBlock.Uint64(),
- Client: l.backend,
- Store: l.store,
- Handler: l.metrics,
- })
-
- startBlock, myChainID, err := cl.GetMetadata(l.GetTestContext())
- l.NoError(err)
- l.Equal(myChainID, uint64(chainID))
- l.Equal(startBlock, deployBlock.Uint64())
-}
-
-func (l *ListenerTestSuite) TestStartBlock() {
- cl := listener.NewTestChainListener(listener.TestChainListenerArgs{
- Address: l.fastBridge.Address(),
- Client: l.backend,
- Store: l.store,
- Handler: l.metrics,
- })
-
- deployBlock, err := l.fastBridge.DeployBlock(&bind.CallOpts{Context: l.GetTestContext()})
- l.NoError(err)
-
- expectedLastIndexed := deployBlock.Uint64() + 10
- err = l.store.PutLatestBlock(l.GetTestContext(), chainID, expectedLastIndexed)
- l.NoError(err)
-
- startBlock, cid, err := cl.GetMetadata(l.GetTestContext())
- l.Equal(cid, uint64(chainID))
- l.Equal(startBlock, expectedLastIndexed)
-}
-
-func (l *ListenerTestSuite) TestListen() {
-
-}
diff --git a/ethergo/example/README.md b/ethergo/example/README.md
index 824cdaa050..6ad31612ee 100644
--- a/ethergo/example/README.md
+++ b/ethergo/example/README.md
@@ -195,5 +195,77 @@
}
```
+ 4. (Optional): Create a typecast getter:
+ To avoid naked casts of contract handle, we can potionally create a typecast getter.
+ To do this, we're going to create a thin wrapper around deploymanager.
+
+ ```go
+ package example
+ import (
+ "context"
+ "github.com/synapsecns/sanguine/ethergo/backends"
+ "github.com/synapsecns/sanguine/ethergo/contracts"
+ "github.com/synapsecns/sanguine/ethergo/manager"
+ "testing"
+ )
+
+ // DeployManager wraps DeployManager and allows typed contract handles to be returned.
+ type DeployManager struct {
+ *manager.DeployerManager
+ }
+
+ // NewDeployManager creates a new DeployManager.
+ func NewDeployManager(t *testing.T) *DeployManager {
+ t.Helper()
+
+ parentManager := manager.NewDeployerManager(t, NewCounterDeployer)
+ return &DeployManager{parentManager}
+ }
+ ```
+
+ Now we can create a handle to get the contract for us;
+
+ ```go
+ package example
+ // see above for imports
+
+ import (
+ "context"
+ "github.com/synapsecns/sanguine/ethergo/backends"
+ "github.com/synapsecns/sanguine/ethergo/contracts"
+ "github.com/synapsecns/sanguine/ethergo/example/counter"
+ "github.com/synapsecns/sanguine/ethergo/manager"
+ "testing"
+ )
+
+ // GetCounter gets the pre-created counter.
+ func (d *DeployManager) GetCounter(ctx context.Context, backend backends.SimulatedTestBackend) (contract contracts.DeployedContract, handle *counter.CounterRef) {
+ d.T().Helper()
+
+ return manager.GetContract[*counter.CounterRef](ctx, d.T(), d, backend, CounterType)
+ }
+ ```
+
+ 5. (Optional) Make sure are dependencies are correct: We can also create a test to assert our dependencides are correctly listed in each deployer. That looks like this:
+
+ ```go
+ package example_test
+
+ import (
+ "context"
+ "github.com/synapsecns/sanguine/ethergo/backends"
+ "github.com/synapsecns/sanguine/ethergo/contracts"
+ "github.com/synapsecns/sanguine/ethergo/example"
+ "github.com/synapsecns/sanguine/ethergo/manager"
+ "testing"
+ )
+
+
+ func TestDependenciesCorrect(t *testing.T) {
+ manager.AssertDependenciesCorrect(context.Background(), t, func() manager.IDeployManager {
+ return example.NewDeployerManager(t)
+ })
+ }
+ ```
That's it! You should be done. As you can see, there's a lot more that can be done here. Passing in a list of all your deployers every time doesn't make sense. You'll want to create a standard testutil and extend it. We also haven't covered that any backend here is interchangable: you can use simulated, ganache, or embedded geth. This tutorial should've covered the basics though
diff --git a/ethergo/example/counter/counter.abigen.go b/ethergo/example/counter/counter.abigen.go
index 8dfa0b19ec..9b4eac4e66 100644
--- a/ethergo/example/counter/counter.abigen.go
+++ b/ethergo/example/counter/counter.abigen.go
@@ -31,15 +31,16 @@ var (
// CounterMetaData contains all meta data concerning the Counter contract.
var CounterMetaData = &bind.MetaData{
- ABI: "[{\"inputs\":[],\"name\":\"decrementCounter\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCount\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getVitalikCount\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"incrementCounter\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"vitalikIncrement\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]",
+ ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"count\",\"type\":\"int256\"}],\"name\":\"Decremented\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"count\",\"type\":\"int256\"}],\"name\":\"Incremented\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"count\",\"type\":\"int256\"}],\"name\":\"IncrementedByUser\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"decrementCounter\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"deployBlock\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCount\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getVitalikCount\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"incrementCounter\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"vitalikIncrement\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]",
Sigs: map[string]string{
"f5c5ad83": "decrementCounter()",
+ "a3ec191a": "deployBlock()",
"a87d942c": "getCount()",
"9f6f1ec1": "getVitalikCount()",
"5b34b966": "incrementCounter()",
"6c573535": "vitalikIncrement()",
},
- Bin: "0x608060405260008055600060015534801561001957600080fd5b506102b0806100296000396000f3fe608060405234801561001057600080fd5b50600436106100675760003560e01c80639f6f1ec1116100505780639f6f1ec11461007e578063a87d942c14610094578063f5c5ad831461009c57600080fd5b80635b34b9661461006c5780636c57353514610076575b600080fd5b6100746100a4565b005b6100746100bd565b6001545b60405190815260200160405180910390f35b600054610082565b610074610151565b60016000808282546100b69190610163565b9091555050565b3373d8da6bf26964af9d7eed9e03e53415d37aa960451461013e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f4f6e6c7920566974616c696b2063616e20636f756e7420627920313000000000604482015260640160405180910390fd5b600a600160008282546100b69190610163565b60016000808282546100b691906101d7565b6000808212827f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0384138115161561019d5761019d61024b565b827f80000000000000000000000000000000000000000000000000000000000000000384128116156101d1576101d161024b565b50500190565b6000808312837f8000000000000000000000000000000000000000000000000000000000000000018312811516156102115761021161024b565b837f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0183138116156102455761024561024b565b50500390565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fdfea26469706673582212201e03c8b68dcbcef6344fae810afa5d485b33bc238c0e8cb1508114b9c0ca702964736f6c63430008040033",
+ Bin: "0x60a060405260008055600060015534801561001957600080fd5b5043608052608051610390610038600039600060a401526103906000f3fe608060405234801561001057600080fd5b50600436106100725760003560e01c8063a3ec191a11610050578063a3ec191a1461009f578063a87d942c146100c6578063f5c5ad83146100ce57600080fd5b80635b34b966146100775780636c573535146100815780639f6f1ec114610089575b600080fd5b61007f6100d6565b005b61007f610126565b6001545b60405190815260200160405180910390f35b61008d7f000000000000000000000000000000000000000000000000000000000000000081565b60005461008d565b61007f6101f9565b60016000808282546100e89190610243565b90915550506000546040519081527fda0bc8b9b52da793a50e130494716550dab510a10a485be3f1b23d4da60ff4be906020015b60405180910390a1565b3373d8da6bf26964af9d7eed9e03e53415d37aa96045146101a7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f4f6e6c7920566974616c696b2063616e20636f756e7420627920313000000000604482015260640160405180910390fd5b600a600160008282546101ba9190610243565b90915550506001546040805133815260208101929092527f5832be325e40e91e7a991db4415bdfa9c689e8007072fdb8de3be47757a14557910161011c565b600160008082825461020b91906102b7565b90915550506000546040519081527f22ccb5ba3d32a9221c3efe39ffab06d1ddc4bd6684975ea75fa60f95ccff53de9060200161011c565b6000808212827f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0384138115161561027d5761027d61032b565b827f80000000000000000000000000000000000000000000000000000000000000000384128116156102b1576102b161032b565b50500190565b6000808312837f8000000000000000000000000000000000000000000000000000000000000000018312811516156102f1576102f161032b565b837f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0183138116156103255761032561032b565b50500390565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fdfea2646970667358221220dd6810ba2d049a0f7354bf12f6a017744c806f0470f592679a80ef552f69b85164736f6c63430008040033",
}
// CounterABI is the input ABI used to generate the binding from.
@@ -213,6 +214,37 @@ func (_Counter *CounterTransactorRaw) Transact(opts *bind.TransactOpts, method s
return _Counter.Contract.contract.Transact(opts, method, params...)
}
+// DeployBlock is a free data retrieval call binding the contract method 0xa3ec191a.
+//
+// Solidity: function deployBlock() view returns(uint256)
+func (_Counter *CounterCaller) DeployBlock(opts *bind.CallOpts) (*big.Int, error) {
+ var out []interface{}
+ err := _Counter.contract.Call(opts, &out, "deployBlock")
+
+ if err != nil {
+ return *new(*big.Int), err
+ }
+
+ out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int)
+
+ return out0, err
+
+}
+
+// DeployBlock is a free data retrieval call binding the contract method 0xa3ec191a.
+//
+// Solidity: function deployBlock() view returns(uint256)
+func (_Counter *CounterSession) DeployBlock() (*big.Int, error) {
+ return _Counter.Contract.DeployBlock(&_Counter.CallOpts)
+}
+
+// DeployBlock is a free data retrieval call binding the contract method 0xa3ec191a.
+//
+// Solidity: function deployBlock() view returns(uint256)
+func (_Counter *CounterCallerSession) DeployBlock() (*big.Int, error) {
+ return _Counter.Contract.DeployBlock(&_Counter.CallOpts)
+}
+
// GetCount is a free data retrieval call binding the contract method 0xa87d942c.
//
// Solidity: function getCount() view returns(int256)
@@ -337,3 +369,406 @@ func (_Counter *CounterSession) VitalikIncrement() (*types.Transaction, error) {
func (_Counter *CounterTransactorSession) VitalikIncrement() (*types.Transaction, error) {
return _Counter.Contract.VitalikIncrement(&_Counter.TransactOpts)
}
+
+// CounterDecrementedIterator is returned from FilterDecremented and is used to iterate over the raw logs and unpacked data for Decremented events raised by the Counter contract.
+type CounterDecrementedIterator struct {
+ Event *CounterDecremented // Event containing the contract specifics and raw log
+
+ contract *bind.BoundContract // Generic contract to use for unpacking event data
+ event string // Event name to use for unpacking event data
+
+ 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 *CounterDecrementedIterator) 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:
+ it.Event = new(CounterDecremented)
+ if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+ it.fail = err
+ return false
+ }
+ it.Event.Raw = log
+ return true
+
+ default:
+ return false
+ }
+ }
+ // Iterator still in progress, wait for either a data or an error event
+ select {
+ case log := <-it.logs:
+ it.Event = new(CounterDecremented)
+ if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+ it.fail = err
+ return false
+ }
+ it.Event.Raw = log
+ 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 *CounterDecrementedIterator) Error() error {
+ return it.fail
+}
+
+// Close terminates the iteration process, releasing any pending underlying
+// resources.
+func (it *CounterDecrementedIterator) Close() error {
+ it.sub.Unsubscribe()
+ return nil
+}
+
+// CounterDecremented represents a Decremented event raised by the Counter contract.
+type CounterDecremented struct {
+ Count *big.Int
+ Raw types.Log // Blockchain specific contextual infos
+}
+
+// FilterDecremented is a free log retrieval operation binding the contract event 0x22ccb5ba3d32a9221c3efe39ffab06d1ddc4bd6684975ea75fa60f95ccff53de.
+//
+// Solidity: event Decremented(int256 count)
+func (_Counter *CounterFilterer) FilterDecremented(opts *bind.FilterOpts) (*CounterDecrementedIterator, error) {
+
+ logs, sub, err := _Counter.contract.FilterLogs(opts, "Decremented")
+ if err != nil {
+ return nil, err
+ }
+ return &CounterDecrementedIterator{contract: _Counter.contract, event: "Decremented", logs: logs, sub: sub}, nil
+}
+
+// WatchDecremented is a free log subscription operation binding the contract event 0x22ccb5ba3d32a9221c3efe39ffab06d1ddc4bd6684975ea75fa60f95ccff53de.
+//
+// Solidity: event Decremented(int256 count)
+func (_Counter *CounterFilterer) WatchDecremented(opts *bind.WatchOpts, sink chan<- *CounterDecremented) (event.Subscription, error) {
+
+ logs, sub, err := _Counter.contract.WatchLogs(opts, "Decremented")
+ 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
+ event := new(CounterDecremented)
+ if err := _Counter.contract.UnpackLog(event, "Decremented", log); err != nil {
+ return err
+ }
+ event.Raw = log
+
+ select {
+ case sink <- event:
+ case err := <-sub.Err():
+ return err
+ case <-quit:
+ return nil
+ }
+ case err := <-sub.Err():
+ return err
+ case <-quit:
+ return nil
+ }
+ }
+ }), nil
+}
+
+// ParseDecremented is a log parse operation binding the contract event 0x22ccb5ba3d32a9221c3efe39ffab06d1ddc4bd6684975ea75fa60f95ccff53de.
+//
+// Solidity: event Decremented(int256 count)
+func (_Counter *CounterFilterer) ParseDecremented(log types.Log) (*CounterDecremented, error) {
+ event := new(CounterDecremented)
+ if err := _Counter.contract.UnpackLog(event, "Decremented", log); err != nil {
+ return nil, err
+ }
+ event.Raw = log
+ return event, nil
+}
+
+// CounterIncrementedIterator is returned from FilterIncremented and is used to iterate over the raw logs and unpacked data for Incremented events raised by the Counter contract.
+type CounterIncrementedIterator struct {
+ Event *CounterIncremented // Event containing the contract specifics and raw log
+
+ contract *bind.BoundContract // Generic contract to use for unpacking event data
+ event string // Event name to use for unpacking event data
+
+ 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 *CounterIncrementedIterator) 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:
+ it.Event = new(CounterIncremented)
+ if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+ it.fail = err
+ return false
+ }
+ it.Event.Raw = log
+ return true
+
+ default:
+ return false
+ }
+ }
+ // Iterator still in progress, wait for either a data or an error event
+ select {
+ case log := <-it.logs:
+ it.Event = new(CounterIncremented)
+ if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+ it.fail = err
+ return false
+ }
+ it.Event.Raw = log
+ 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 *CounterIncrementedIterator) Error() error {
+ return it.fail
+}
+
+// Close terminates the iteration process, releasing any pending underlying
+// resources.
+func (it *CounterIncrementedIterator) Close() error {
+ it.sub.Unsubscribe()
+ return nil
+}
+
+// CounterIncremented represents a Incremented event raised by the Counter contract.
+type CounterIncremented struct {
+ Count *big.Int
+ Raw types.Log // Blockchain specific contextual infos
+}
+
+// FilterIncremented is a free log retrieval operation binding the contract event 0xda0bc8b9b52da793a50e130494716550dab510a10a485be3f1b23d4da60ff4be.
+//
+// Solidity: event Incremented(int256 count)
+func (_Counter *CounterFilterer) FilterIncremented(opts *bind.FilterOpts) (*CounterIncrementedIterator, error) {
+
+ logs, sub, err := _Counter.contract.FilterLogs(opts, "Incremented")
+ if err != nil {
+ return nil, err
+ }
+ return &CounterIncrementedIterator{contract: _Counter.contract, event: "Incremented", logs: logs, sub: sub}, nil
+}
+
+// WatchIncremented is a free log subscription operation binding the contract event 0xda0bc8b9b52da793a50e130494716550dab510a10a485be3f1b23d4da60ff4be.
+//
+// Solidity: event Incremented(int256 count)
+func (_Counter *CounterFilterer) WatchIncremented(opts *bind.WatchOpts, sink chan<- *CounterIncremented) (event.Subscription, error) {
+
+ logs, sub, err := _Counter.contract.WatchLogs(opts, "Incremented")
+ 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
+ event := new(CounterIncremented)
+ if err := _Counter.contract.UnpackLog(event, "Incremented", log); err != nil {
+ return err
+ }
+ event.Raw = log
+
+ select {
+ case sink <- event:
+ case err := <-sub.Err():
+ return err
+ case <-quit:
+ return nil
+ }
+ case err := <-sub.Err():
+ return err
+ case <-quit:
+ return nil
+ }
+ }
+ }), nil
+}
+
+// ParseIncremented is a log parse operation binding the contract event 0xda0bc8b9b52da793a50e130494716550dab510a10a485be3f1b23d4da60ff4be.
+//
+// Solidity: event Incremented(int256 count)
+func (_Counter *CounterFilterer) ParseIncremented(log types.Log) (*CounterIncremented, error) {
+ event := new(CounterIncremented)
+ if err := _Counter.contract.UnpackLog(event, "Incremented", log); err != nil {
+ return nil, err
+ }
+ event.Raw = log
+ return event, nil
+}
+
+// CounterIncrementedByUserIterator is returned from FilterIncrementedByUser and is used to iterate over the raw logs and unpacked data for IncrementedByUser events raised by the Counter contract.
+type CounterIncrementedByUserIterator struct {
+ Event *CounterIncrementedByUser // Event containing the contract specifics and raw log
+
+ contract *bind.BoundContract // Generic contract to use for unpacking event data
+ event string // Event name to use for unpacking event data
+
+ 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 *CounterIncrementedByUserIterator) 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:
+ it.Event = new(CounterIncrementedByUser)
+ if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+ it.fail = err
+ return false
+ }
+ it.Event.Raw = log
+ return true
+
+ default:
+ return false
+ }
+ }
+ // Iterator still in progress, wait for either a data or an error event
+ select {
+ case log := <-it.logs:
+ it.Event = new(CounterIncrementedByUser)
+ if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+ it.fail = err
+ return false
+ }
+ it.Event.Raw = log
+ 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 *CounterIncrementedByUserIterator) Error() error {
+ return it.fail
+}
+
+// Close terminates the iteration process, releasing any pending underlying
+// resources.
+func (it *CounterIncrementedByUserIterator) Close() error {
+ it.sub.Unsubscribe()
+ return nil
+}
+
+// CounterIncrementedByUser represents a IncrementedByUser event raised by the Counter contract.
+type CounterIncrementedByUser struct {
+ User common.Address
+ Count *big.Int
+ Raw types.Log // Blockchain specific contextual infos
+}
+
+// FilterIncrementedByUser is a free log retrieval operation binding the contract event 0x5832be325e40e91e7a991db4415bdfa9c689e8007072fdb8de3be47757a14557.
+//
+// Solidity: event IncrementedByUser(address user, int256 count)
+func (_Counter *CounterFilterer) FilterIncrementedByUser(opts *bind.FilterOpts) (*CounterIncrementedByUserIterator, error) {
+
+ logs, sub, err := _Counter.contract.FilterLogs(opts, "IncrementedByUser")
+ if err != nil {
+ return nil, err
+ }
+ return &CounterIncrementedByUserIterator{contract: _Counter.contract, event: "IncrementedByUser", logs: logs, sub: sub}, nil
+}
+
+// WatchIncrementedByUser is a free log subscription operation binding the contract event 0x5832be325e40e91e7a991db4415bdfa9c689e8007072fdb8de3be47757a14557.
+//
+// Solidity: event IncrementedByUser(address user, int256 count)
+func (_Counter *CounterFilterer) WatchIncrementedByUser(opts *bind.WatchOpts, sink chan<- *CounterIncrementedByUser) (event.Subscription, error) {
+
+ logs, sub, err := _Counter.contract.WatchLogs(opts, "IncrementedByUser")
+ 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
+ event := new(CounterIncrementedByUser)
+ if err := _Counter.contract.UnpackLog(event, "IncrementedByUser", log); err != nil {
+ return err
+ }
+ event.Raw = log
+
+ select {
+ case sink <- event:
+ case err := <-sub.Err():
+ return err
+ case <-quit:
+ return nil
+ }
+ case err := <-sub.Err():
+ return err
+ case <-quit:
+ return nil
+ }
+ }
+ }), nil
+}
+
+// ParseIncrementedByUser is a log parse operation binding the contract event 0x5832be325e40e91e7a991db4415bdfa9c689e8007072fdb8de3be47757a14557.
+//
+// Solidity: event IncrementedByUser(address user, int256 count)
+func (_Counter *CounterFilterer) ParseIncrementedByUser(log types.Log) (*CounterIncrementedByUser, error) {
+ event := new(CounterIncrementedByUser)
+ if err := _Counter.contract.UnpackLog(event, "IncrementedByUser", log); err != nil {
+ return nil, err
+ }
+ event.Raw = log
+ return event, nil
+}
diff --git a/ethergo/example/counter/counter.contractinfo.json b/ethergo/example/counter/counter.contractinfo.json
index 0766d19bc4..3117834c3b 100644
--- a/ethergo/example/counter/counter.contractinfo.json
+++ b/ethergo/example/counter/counter.contractinfo.json
@@ -1 +1 @@
-{"/solidity/counter.sol:Counter":{"code":"0x608060405260008055600060015534801561001957600080fd5b506102b0806100296000396000f3fe608060405234801561001057600080fd5b50600436106100675760003560e01c80639f6f1ec1116100505780639f6f1ec11461007e578063a87d942c14610094578063f5c5ad831461009c57600080fd5b80635b34b9661461006c5780636c57353514610076575b600080fd5b6100746100a4565b005b6100746100bd565b6001545b60405190815260200160405180910390f35b600054610082565b610074610151565b60016000808282546100b69190610163565b9091555050565b3373d8da6bf26964af9d7eed9e03e53415d37aa960451461013e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f4f6e6c7920566974616c696b2063616e20636f756e7420627920313000000000604482015260640160405180910390fd5b600a600160008282546100b69190610163565b60016000808282546100b691906101d7565b6000808212827f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0384138115161561019d5761019d61024b565b827f80000000000000000000000000000000000000000000000000000000000000000384128116156101d1576101d161024b565b50500190565b6000808312837f8000000000000000000000000000000000000000000000000000000000000000018312811516156102115761021161024b565b837f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0183138116156102455761024561024b565b50500390565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fdfea26469706673582212201e03c8b68dcbcef6344fae810afa5d485b33bc238c0e8cb1508114b9c0ca702964736f6c63430008040033","runtime-code":"0x608060405234801561001057600080fd5b50600436106100675760003560e01c80639f6f1ec1116100505780639f6f1ec11461007e578063a87d942c14610094578063f5c5ad831461009c57600080fd5b80635b34b9661461006c5780636c57353514610076575b600080fd5b6100746100a4565b005b6100746100bd565b6001545b60405190815260200160405180910390f35b600054610082565b610074610151565b60016000808282546100b69190610163565b9091555050565b3373d8da6bf26964af9d7eed9e03e53415d37aa960451461013e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f4f6e6c7920566974616c696b2063616e20636f756e7420627920313000000000604482015260640160405180910390fd5b600a600160008282546100b69190610163565b60016000808282546100b691906101d7565b6000808212827f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0384138115161561019d5761019d61024b565b827f80000000000000000000000000000000000000000000000000000000000000000384128116156101d1576101d161024b565b50500190565b6000808312837f8000000000000000000000000000000000000000000000000000000000000000018312811516156102115761021161024b565b837f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0183138116156102455761024561024b565b50500390565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fdfea26469706673582212201e03c8b68dcbcef6344fae810afa5d485b33bc238c0e8cb1508114b9c0ca702964736f6c63430008040033","info":{"source":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\ncontract Counter {\n // this is used for testing account impersonation\n address constant VITALIK = address(0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045);\n\n int private count = 0;\n int private vitaikCount = 0;\n\n function incrementCounter() public {\n count += 1;\n }\n function decrementCounter() public {\n count -= 1;\n }\n\n function vitalikIncrement() public {\n require(msg.sender == VITALIK, \"Only Vitalik can count by 10\");\n vitaikCount += 10;\n }\n\n function getCount() public view returns (int) {\n return count;\n }\n\n function getVitalikCount() public view returns (int) {\n return vitaikCount;\n }\n}\n","language":"Solidity","languageVersion":"0.8.4","compilerVersion":"0.8.4","compilerOptions":"--combined-json bin,bin-runtime,srcmap,srcmap-runtime,abi,userdoc,devdoc,metadata,hashes --optimize --optimize-runs 10000 --allow-paths ., ./, ../","srcMap":"57:676:0:-:0;;;239:1;219:21;;272:1;246:27;;57:676;;;;;;;;;;;;;;;;","srcMapRuntime":"57:676:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;280:62;;;:::i;:::-;;415:141;;;:::i;643:88::-;713:11;;643:88;;;158:25:1;;;146:2;131:18;643:88:0;;;;;;;562:75;603:3;625:5;562:75;;347:62;;;:::i;280:::-;334:1;325:5;;:10;;;;;;;:::i;:::-;;;;-1:-1:-1;;280:62:0:o;415:141::-;468:10;169:42;468:21;460:62;;;;;;;396:2:1;460:62:0;;;378:21:1;435:2;415:18;;;408:30;474;454:18;;;447:58;522:18;;460:62:0;;;;;;;;547:2;532:11;;:17;;;;;;;:::i;347:62::-;401:1;392:5;;:10;;;;;;;:::i;551:369:1:-;590:3;625;622:1;618:11;736:1;668:66;664:74;661:1;657:82;652:2;645:10;641:99;638:2;;;743:18;;:::i;:::-;862:1;794:66;790:74;787:1;783:82;779:2;775:91;772:2;;;869:18;;:::i;:::-;-1:-1:-1;;905:9:1;;598:322::o;925:372::-;964:4;1000;997:1;993:12;1112:1;1044:66;1040:74;1037:1;1033:82;1028:2;1021:10;1017:99;1014:2;;;1119:18;;:::i;:::-;1238:1;1170:66;1166:74;1163:1;1159:82;1155:2;1151:91;1148:2;;;1245:18;;:::i;:::-;-1:-1:-1;;1282:9:1;;973:324::o;1302:184::-;1354:77;1351:1;1344:88;1451:4;1448:1;1441:15;1475:4;1472:1;1465:15","abiDefinition":[{"inputs":[],"name":"decrementCounter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getCount","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVitalikCount","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"incrementCounter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"vitalikIncrement","outputs":[],"stateMutability":"nonpayable","type":"function"}],"userDoc":{"kind":"user","methods":{},"version":1},"developerDoc":{"kind":"dev","methods":{},"version":1},"metadata":"{\"compiler\":{\"version\":\"0.8.4+commit.c7e474f2\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"name\":\"decrementCounter\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCount\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getVitalikCount\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"incrementCounter\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"vitalikIncrement\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"/solidity/counter.sol\":\"Counter\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":10000},\"remappings\":[]},\"sources\":{\"/solidity/counter.sol\":{\"keccak256\":\"0x42676ddc10b9e27a3896bfe453fc4e32f321449b6f1ad9bd61dc69248df0eea1\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://237ef43b2c1b1136dd04f57595b5e54f952a37bf02b0f56fc62407eea868171d\",\"dweb:/ipfs/QmZnBMnf1xZ2yfwqVjBZRR4W4CtLcLEn1FyHvRgAEShu7k\"]}},\"version\":1}"},"hashes":{"decrementCounter()":"f5c5ad83","getCount()":"a87d942c","getVitalikCount()":"9f6f1ec1","incrementCounter()":"5b34b966","vitalikIncrement()":"6c573535"}}}
\ No newline at end of file
+{"/solidity/counter.sol:Counter":{"code":"0x60a060405260008055600060015534801561001957600080fd5b5043608052608051610390610038600039600060a401526103906000f3fe608060405234801561001057600080fd5b50600436106100725760003560e01c8063a3ec191a11610050578063a3ec191a1461009f578063a87d942c146100c6578063f5c5ad83146100ce57600080fd5b80635b34b966146100775780636c573535146100815780639f6f1ec114610089575b600080fd5b61007f6100d6565b005b61007f610126565b6001545b60405190815260200160405180910390f35b61008d7f000000000000000000000000000000000000000000000000000000000000000081565b60005461008d565b61007f6101f9565b60016000808282546100e89190610243565b90915550506000546040519081527fda0bc8b9b52da793a50e130494716550dab510a10a485be3f1b23d4da60ff4be906020015b60405180910390a1565b3373d8da6bf26964af9d7eed9e03e53415d37aa96045146101a7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f4f6e6c7920566974616c696b2063616e20636f756e7420627920313000000000604482015260640160405180910390fd5b600a600160008282546101ba9190610243565b90915550506001546040805133815260208101929092527f5832be325e40e91e7a991db4415bdfa9c689e8007072fdb8de3be47757a14557910161011c565b600160008082825461020b91906102b7565b90915550506000546040519081527f22ccb5ba3d32a9221c3efe39ffab06d1ddc4bd6684975ea75fa60f95ccff53de9060200161011c565b6000808212827f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0384138115161561027d5761027d61032b565b827f80000000000000000000000000000000000000000000000000000000000000000384128116156102b1576102b161032b565b50500190565b6000808312837f8000000000000000000000000000000000000000000000000000000000000000018312811516156102f1576102f161032b565b837f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0183138116156103255761032561032b565b50500390565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fdfea2646970667358221220dd6810ba2d049a0f7354bf12f6a017744c806f0470f592679a80ef552f69b85164736f6c63430008040033","runtime-code":"0x608060405234801561001057600080fd5b50600436106100725760003560e01c8063a3ec191a11610050578063a3ec191a1461009f578063a87d942c146100c6578063f5c5ad83146100ce57600080fd5b80635b34b966146100775780636c573535146100815780639f6f1ec114610089575b600080fd5b61007f6100d6565b005b61007f610126565b6001545b60405190815260200160405180910390f35b61008d7f000000000000000000000000000000000000000000000000000000000000000081565b60005461008d565b61007f6101f9565b60016000808282546100e89190610243565b90915550506000546040519081527fda0bc8b9b52da793a50e130494716550dab510a10a485be3f1b23d4da60ff4be906020015b60405180910390a1565b3373d8da6bf26964af9d7eed9e03e53415d37aa96045146101a7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f4f6e6c7920566974616c696b2063616e20636f756e7420627920313000000000604482015260640160405180910390fd5b600a600160008282546101ba9190610243565b90915550506001546040805133815260208101929092527f5832be325e40e91e7a991db4415bdfa9c689e8007072fdb8de3be47757a14557910161011c565b600160008082825461020b91906102b7565b90915550506000546040519081527f22ccb5ba3d32a9221c3efe39ffab06d1ddc4bd6684975ea75fa60f95ccff53de9060200161011c565b6000808212827f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0384138115161561027d5761027d61032b565b827f80000000000000000000000000000000000000000000000000000000000000000384128116156102b1576102b161032b565b50500190565b6000808312837f8000000000000000000000000000000000000000000000000000000000000000018312811516156102f1576102f161032b565b837f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0183138116156103255761032561032b565b50500390565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fdfea2646970667358221220dd6810ba2d049a0f7354bf12f6a017744c806f0470f592679a80ef552f69b85164736f6c63430008040033","info":{"source":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\ncontract Counter {\n // this is used for testing account impersonation\n address constant VITALIK = address(0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045);\n\n event Incremented(int count);\n event Decremented(int count);\n event IncrementedByUser(address user, int count);\n\n int private count = 0;\n int private vitaikCount = 0;\n\n // @dev the block the contract was deployed at\n uint256 public immutable deployBlock;\n\n constructor() {\n deployBlock = block.number;\n }\n\n\n function incrementCounter() public {\n count += 1;\n emit Incremented(count);\n }\n function decrementCounter() public {\n count -= 1;\n emit Decremented(count);\n }\n\n function vitalikIncrement() public {\n require(msg.sender == VITALIK, \"Only Vitalik can count by 10\");\n vitaikCount += 10;\n emit IncrementedByUser(msg.sender, vitaikCount);\n }\n\n function getCount() public view returns (int) {\n return count;\n }\n\n function getVitalikCount() public view returns (int) {\n return vitaikCount;\n }\n}\n","language":"Solidity","languageVersion":"0.8.4","compilerVersion":"0.8.4","compilerOptions":"--combined-json bin,bin-runtime,srcmap,srcmap-runtime,abi,userdoc,devdoc,metadata,hashes --optimize --optimize-runs 10000 --allow-paths ., ./, ../","srcMap":"57:1081:0:-:0;;;362:1;342:21;;395:1;369:27;;497:58;;;;;;;;;-1:-1:-1;536:12:0;522:26;;57:1081;;;;;;;;;;","srcMapRuntime":"57:1081:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;562:95;;;:::i;:::-;;763:198;;;:::i;1048:88::-;1118:11;;1048:88;;;458:25:1;;;446:2;431:18;1048:88:0;;;;;;;454:36;;;;;967:75;1008:3;1030:5;967:75;;662:95;;;:::i;562:::-;616:1;607:5;;:10;;;;;;;:::i;:::-;;;;-1:-1:-1;;644:5:0;;632:18;;458:25:1;;;632:18:0;;446:2:1;431:18;632::0;;;;;;;;562:95::o;763:198::-;816:10;169:42;816:21;808:62;;;;;;;696:2:1;808:62:0;;;678:21:1;735:2;715:18;;;708:30;774;754:18;;;747:58;822:18;;808:62:0;;;;;;;;895:2;880:11;;:17;;;;;;;:::i;:::-;;;;-1:-1:-1;;942:11:0;;912:42;;;930:10;186:74:1;;291:2;276:18;;269:34;;;;912:42:0;;159:18:1;912:42:0;141:168:1;662:95:0;716:1;707:5;;:10;;;;;;;:::i;:::-;;;;-1:-1:-1;;744:5:0;;732:18;;458:25:1;;;732:18:0;;446:2:1;431:18;732::0;413:76:1;1033:369;1072:3;1107;1104:1;1100:11;1218:1;1150:66;1146:74;1143:1;1139:82;1134:2;1127:10;1123:99;1120:2;;;1225:18;;:::i;:::-;1344:1;1276:66;1272:74;1269:1;1265:82;1261:2;1257:91;1254:2;;;1351:18;;:::i;:::-;-1:-1:-1;;1387:9:1;;1080:322::o;1407:372::-;1446:4;1482;1479:1;1475:12;1594:1;1526:66;1522:74;1519:1;1515:82;1510:2;1503:10;1499:99;1496:2;;;1601:18;;:::i;:::-;1720:1;1652:66;1648:74;1645:1;1641:82;1637:2;1633:91;1630:2;;;1727:18;;:::i;:::-;-1:-1:-1;;1764:9:1;;1455:324::o;1784:184::-;1836:77;1833:1;1826:88;1933:4;1930:1;1923:15;1957:4;1954:1;1947:15","abiDefinition":[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"int256","name":"count","type":"int256"}],"name":"Decremented","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"int256","name":"count","type":"int256"}],"name":"Incremented","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"int256","name":"count","type":"int256"}],"name":"IncrementedByUser","type":"event"},{"inputs":[],"name":"decrementCounter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"deployBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCount","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVitalikCount","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"incrementCounter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"vitalikIncrement","outputs":[],"stateMutability":"nonpayable","type":"function"}],"userDoc":{"kind":"user","methods":{},"version":1},"developerDoc":{"kind":"dev","methods":{},"version":1},"metadata":"{\"compiler\":{\"version\":\"0.8.4+commit.c7e474f2\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"count\",\"type\":\"int256\"}],\"name\":\"Decremented\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"count\",\"type\":\"int256\"}],\"name\":\"Incremented\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"count\",\"type\":\"int256\"}],\"name\":\"IncrementedByUser\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"decrementCounter\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"deployBlock\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCount\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getVitalikCount\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"incrementCounter\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"vitalikIncrement\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"/solidity/counter.sol\":\"Counter\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":10000},\"remappings\":[]},\"sources\":{\"/solidity/counter.sol\":{\"keccak256\":\"0x390b53ff5f95e07097f3bde2e637f0987013723a5694b49068f9f3cf9c6638a9\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://2343068f1a3e9757b578ce9c0e3fa8c6a447f9ae2986b381c25960ab07c4fec8\",\"dweb:/ipfs/QmXLh2vW9mvc8Zfe56eoJzpYkx9fUByeTVwNMDApCKcraH\"]}},\"version\":1}"},"hashes":{"decrementCounter()":"f5c5ad83","deployBlock()":"a3ec191a","getCount()":"a87d942c","getVitalikCount()":"9f6f1ec1","incrementCounter()":"5b34b966","vitalikIncrement()":"6c573535"}}}
\ No newline at end of file
diff --git a/ethergo/example/counter/counter.sol b/ethergo/example/counter/counter.sol
index 581c3ceca8..49da60c0e9 100644
--- a/ethergo/example/counter/counter.sol
+++ b/ethergo/example/counter/counter.sol
@@ -5,19 +5,34 @@ contract Counter {
// this is used for testing account impersonation
address constant VITALIK = address(0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045);
+ event Incremented(int count);
+ event Decremented(int count);
+ event IncrementedByUser(address user, int count);
+
int private count = 0;
int private vitaikCount = 0;
+ // @dev the block the contract was deployed at
+ uint256 public immutable deployBlock;
+
+ constructor() {
+ deployBlock = block.number;
+ }
+
+
function incrementCounter() public {
count += 1;
+ emit Incremented(count);
}
function decrementCounter() public {
count -= 1;
+ emit Decremented(count);
}
function vitalikIncrement() public {
require(msg.sender == VITALIK, "Only Vitalik can count by 10");
vitaikCount += 10;
+ emit IncrementedByUser(msg.sender, vitaikCount);
}
function getCount() public view returns (int) {
diff --git a/ethergo/example/deploymanager.go b/ethergo/example/deploymanager.go
new file mode 100644
index 0000000000..8f86031b7d
--- /dev/null
+++ b/ethergo/example/deploymanager.go
@@ -0,0 +1,30 @@
+package example
+
+import (
+ "context"
+ "github.com/synapsecns/sanguine/ethergo/backends"
+ "github.com/synapsecns/sanguine/ethergo/contracts"
+ "github.com/synapsecns/sanguine/ethergo/example/counter"
+ "github.com/synapsecns/sanguine/ethergo/manager"
+ "testing"
+)
+
+// DeployManager wraps DeployManager and allows typed contract handles to be returned.
+type DeployManager struct {
+ *manager.DeployerManager
+}
+
+// NewDeployManager creates a new DeployManager.
+func NewDeployManager(t *testing.T) *DeployManager {
+ t.Helper()
+
+ parentManager := manager.NewDeployerManager(t, NewCounterDeployer)
+ return &DeployManager{parentManager}
+}
+
+// GetCounter gets the pre-created counter.
+func (d *DeployManager) GetCounter(ctx context.Context, backend backends.SimulatedTestBackend) (contract contracts.DeployedContract, handle *counter.CounterRef) {
+ d.T().Helper()
+
+ return manager.GetContract[*counter.CounterRef](ctx, d.T(), d, backend, CounterType)
+}
diff --git a/ethergo/listener/db/doc.go b/ethergo/listener/db/doc.go
new file mode 100644
index 0000000000..cf130b4543
--- /dev/null
+++ b/ethergo/listener/db/doc.go
@@ -0,0 +1,2 @@
+// Package db provides the database layer for the chain listener.
+package db
diff --git a/ethergo/listener/db/service.go b/ethergo/listener/db/service.go
new file mode 100644
index 0000000000..fb2e7d922c
--- /dev/null
+++ b/ethergo/listener/db/service.go
@@ -0,0 +1,41 @@
+package db
+
+import (
+ "context"
+ "gorm.io/gorm"
+ "time"
+)
+
+// ChainListenerDB is the interface for the chain listener database.
+type ChainListenerDB interface {
+ // PutLatestBlock upsers the latest block on a given chain id to be new height.
+ PutLatestBlock(ctx context.Context, chainID, height uint64) error
+ // LatestBlockForChain gets the latest block for a given chain id.
+ // will return ErrNoLatestBlockForChainID if no block exists for the chain.
+ LatestBlockForChain(ctx context.Context, chainID uint64) (uint64, error)
+}
+
+// LastIndexed is used to make sure we haven't missed any events while offline.
+// since we event source - rather than use a state machine this is needed to make sure we haven't missed any events
+// by allowing us to go back and source any events we may have missed.
+//
+// this does not inherit from gorm.model to allow us to use ChainID as a primary key.
+type LastIndexed struct {
+ // CreatedAt is the creation time
+ CreatedAt time.Time
+ // UpdatedAt is the update time
+ UpdatedAt time.Time
+ // DeletedAt time
+ DeletedAt gorm.DeletedAt `gorm:"index"`
+ // ChainID is the chain id of the chain we're watching blocks on. This is our primary index.
+ ChainID uint64 `gorm:"column:chain_id;primaryKey;autoIncrement:false"`
+ // BlockHeight is the highest height we've seen on the chain
+ BlockNumber int `gorm:"block_number"`
+}
+
+// GetAllModels gets all models to migrate
+// see: https://medium.com/@SaifAbid/slice-interfaces-8c78f8b6345d for an explanation of why we can't do this at initialization time
+func GetAllModels() (allModels []interface{}) {
+ allModels = []interface{}{&LastIndexed{}}
+ return allModels
+}
diff --git a/ethergo/listener/db/store.go b/ethergo/listener/db/store.go
new file mode 100644
index 0000000000..396d2177e7
--- /dev/null
+++ b/ethergo/listener/db/store.go
@@ -0,0 +1,71 @@
+package db
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "github.com/synapsecns/sanguine/core/dbcommon"
+ "github.com/synapsecns/sanguine/core/metrics"
+ "gorm.io/gorm"
+ "gorm.io/gorm/clause"
+)
+
+// NewChainListenerStore creates a new transaction store.
+func NewChainListenerStore(db *gorm.DB, metrics metrics.Handler) *Store {
+ return &Store{
+ db: db,
+ metrics: metrics,
+ }
+}
+
+// Store is the sqlite store. It extends the base store for sqlite specific queries.
+type Store struct {
+ db *gorm.DB
+ metrics metrics.Handler
+}
+
+// PutLatestBlock upserts the latest block into the database.
+func (s Store) PutLatestBlock(ctx context.Context, chainID, height uint64) error {
+ tx := s.db.WithContext(ctx).Clauses(clause.OnConflict{
+ Columns: []clause.Column{{Name: chainIDFieldName}},
+ DoUpdates: clause.AssignmentColumns([]string{chainIDFieldName, blockNumberFieldName}),
+ }).Create(&LastIndexed{
+ ChainID: chainID,
+ BlockNumber: int(height),
+ })
+
+ if tx.Error != nil {
+ return fmt.Errorf("could not block updated: %w", tx.Error)
+ }
+ return nil
+}
+
+// LatestBlockForChain gets the latest block for a chain.
+func (s Store) LatestBlockForChain(ctx context.Context, chainID uint64) (uint64, error) {
+ blockWatchModel := LastIndexed{ChainID: chainID}
+ err := s.db.WithContext(ctx).First(&blockWatchModel).Error
+ if err != nil {
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ return 0, ErrNoLatestBlockForChainID
+ }
+ return 0, fmt.Errorf("could not fetch latest block: %w", err)
+ }
+
+ return uint64(blockWatchModel.BlockNumber), nil
+}
+
+func init() {
+ namer := dbcommon.NewNamer(GetAllModels())
+ chainIDFieldName = namer.GetConsistentName("ChainID")
+ blockNumberFieldName = namer.GetConsistentName("BlockNumber")
+}
+
+var (
+ // chainIDFieldName gets the chain id field name.
+ chainIDFieldName string
+ // blockNumberFieldName is the name of the block number field.
+ blockNumberFieldName string
+)
+
+// ErrNoLatestBlockForChainID is returned when no block exists for the chain.
+var ErrNoLatestBlockForChainID = errors.New("no latest block for chainId")
diff --git a/ethergo/chain/listener/doc.go b/ethergo/listener/doc.go
similarity index 100%
rename from ethergo/chain/listener/doc.go
rename to ethergo/listener/doc.go
diff --git a/ethergo/chain/listener/export_test.go b/ethergo/listener/export_test.go
similarity index 91%
rename from ethergo/chain/listener/export_test.go
rename to ethergo/listener/export_test.go
index 469ae09670..16c1a3faf1 100644
--- a/ethergo/chain/listener/export_test.go
+++ b/ethergo/listener/export_test.go
@@ -2,11 +2,10 @@ package listener
import (
"context"
-
"github.com/ethereum/go-ethereum/common"
"github.com/synapsecns/sanguine/core/metrics"
"github.com/synapsecns/sanguine/ethergo/client"
- "github.com/synapsecns/sanguine/services/rfq/relayer/reldb"
+ "github.com/synapsecns/sanguine/ethergo/listener/db"
)
// TestChainListener wraps chain listener for testing.
@@ -24,7 +23,7 @@ type TestChainListenerArgs struct {
Address common.Address
InitialBlock uint64
Client client.EVM
- Store reldb.Service
+ Store db.ChainListenerDB
Handler metrics.Handler
}
diff --git a/ethergo/chain/listener/listener.go b/ethergo/listener/listener.go
similarity index 77%
rename from ethergo/chain/listener/listener.go
rename to ethergo/listener/listener.go
index 793c109e76..3a9fa00749 100644
--- a/ethergo/chain/listener/listener.go
+++ b/ethergo/listener/listener.go
@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
+ db2 "github.com/synapsecns/sanguine/ethergo/listener/db"
"math/big"
"time"
@@ -14,9 +15,9 @@ import (
"github.com/jpillora/backoff"
"github.com/synapsecns/sanguine/core/metrics"
"github.com/synapsecns/sanguine/ethergo/client"
- "github.com/synapsecns/sanguine/services/rfq/relayer/reldb"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
+ "golang.org/x/sync/errgroup"
)
// ContractListener listens for chain events and calls HandleLog.
@@ -38,7 +39,7 @@ type chainListener struct {
client client.EVM
address common.Address
initialBlock uint64
- store reldb.Service
+ store db2.ChainListenerDB
handler metrics.Handler
backoff *backoff.Backoff
// IMPORTANT! These fields cannot be used until they has been set. They are NOT
@@ -48,10 +49,14 @@ type chainListener struct {
// latestBlock uint64
}
-var logger = log.Logger("chainlistener-logger")
+var (
+ logger = log.Logger("chainlistener-logger")
+ // ErrNoLatestBlockForChainID is returned when no block exists for the chain.
+ ErrNoLatestBlockForChainID = db2.ErrNoLatestBlockForChainID
+)
// NewChainListener creates a new chain listener.
-func NewChainListener(omnirpcClient client.EVM, store reldb.Service, address common.Address, initialBlock uint64, handler metrics.Handler) (ContractListener, error) {
+func NewChainListener(omnirpcClient client.EVM, store db2.ChainListenerDB, address common.Address, initialBlock uint64, handler metrics.Handler) (ContractListener, error) {
return &chainListener{
handler: handler,
address: address,
@@ -166,23 +171,34 @@ func (c chainListener) getMetadata(parentCtx context.Context) (startBlock, chain
// TODO: consider some kind of backoff here in case rpcs are down at boot.
// this becomes more of an issue as we add more chains
- // TODO: one thing I've been going back and forth on is whether or not this method should be chain aware
- // passing in the chain ID would allow us to pull everything directly from the config, but be less testable
- // for now, this is probably the best solution for testability, but it's certainly a bit annoying we need to do
- // an rpc call in order to get the chain id
- //
- rpcChainID, err := c.client.ChainID(ctx)
+ g, ctx := errgroup.WithContext(ctx)
+ g.Go(func() error {
+ // TODO: one thing I've been going back and forth on is whether or not this method should be chain aware
+ // passing in the chain ID would allow us to pull everything directly from the config, but be less testable
+ // for now, this is probably the best solution for testability, but it's certainly a bit annoying we need to do
+ // an rpc call in order to get the chain id
+ //
+ rpcChainID, err := c.client.ChainID(ctx)
+ if err != nil {
+ return fmt.Errorf("could not get chain ID: %w", err)
+ }
+ chainID = rpcChainID.Uint64()
+
+ lastIndexed, err = c.store.LatestBlockForChain(ctx, chainID)
+ if errors.Is(err, ErrNoLatestBlockForChainID) {
+ // TODO: consider making this negative 1, requires type change
+ lastIndexed = 0
+ return nil
+ }
+ if err != nil {
+ return fmt.Errorf("could not get the latest block for chainID: %w", err)
+ }
+ return nil
+ })
+
+ err = g.Wait()
if err != nil {
- return 0, 0, fmt.Errorf("could not get chain ID: %w", err)
- }
- chainID = rpcChainID.Uint64()
-
- lastIndexed, err = c.store.LatestBlockForChain(ctx, chainID)
- if errors.Is(err, reldb.ErrNoLatestBlockForChainID) {
- // TODO: consider making this negative 1, requires type change
- lastIndexed = 0
- } else if err != nil {
- return 0, 0, fmt.Errorf("could not get the latest block for chainID: %w", err)
+ return 0, 0, fmt.Errorf("could not get metadata: %w", err)
}
if lastIndexed > c.startBlock {
diff --git a/ethergo/listener/listener_test.go b/ethergo/listener/listener_test.go
new file mode 100644
index 0000000000..24b314afa9
--- /dev/null
+++ b/ethergo/listener/listener_test.go
@@ -0,0 +1,58 @@
+package listener_test
+
+import (
+ "context"
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/synapsecns/sanguine/ethergo/listener"
+ "sync"
+)
+
+func (l *ListenerTestSuite) TestListenForEvents() {
+ _, handle := l.manager.GetCounter(l.GetTestContext(), l.backend)
+ var wg sync.WaitGroup
+ const iterations = 10
+ for i := 0; i < iterations; i++ {
+ i := i
+ wg.Add(1)
+ go func(_ int) {
+ defer wg.Done()
+
+ auth := l.backend.GetTxContext(l.GetTestContext(), nil)
+
+ //nolint:typecheck
+ bridgeRequestTX, err := handle.IncrementCounter(auth.TransactOpts)
+ l.NoError(err)
+ l.NotNil(bridgeRequestTX)
+
+ l.backend.WaitForConfirmation(l.GetTestContext(), bridgeRequestTX)
+
+ bridgeResponseTX, err := handle.DecrementCounter(auth.TransactOpts)
+ l.NoError(err)
+ l.NotNil(bridgeResponseTX)
+ l.backend.WaitForConfirmation(l.GetTestContext(), bridgeResponseTX)
+ }(i)
+ }
+
+ wg.Wait()
+
+ startBlock, err := handle.DeployBlock(&bind.CallOpts{Context: l.GetTestContext()})
+ l.NoError(err)
+
+ cl, err := listener.NewChainListener(l.backend, l.store, handle.Address(), uint64(startBlock.Int64()), l.metrics)
+ l.NoError(err)
+
+ eventCount := 0
+
+ // TODO: check for timeout,but it will be extremely obvious if it gets hit.
+ listenCtx, cancel := context.WithCancel(l.GetTestContext())
+ _ = cl.Listen(listenCtx, func(ctx context.Context, log types.Log) error {
+ eventCount++
+
+ if eventCount == iterations*2 {
+ cancel()
+ }
+
+ return nil
+ })
+}
diff --git a/ethergo/listener/suite_test.go b/ethergo/listener/suite_test.go
new file mode 100644
index 0000000000..de38f6594e
--- /dev/null
+++ b/ethergo/listener/suite_test.go
@@ -0,0 +1,157 @@
+package listener_test
+
+import (
+ "context"
+ "fmt"
+ "github.com/brianvoe/gofakeit/v6"
+ "github.com/ipfs/go-log"
+ common_base "github.com/synapsecns/sanguine/core/dbcommon"
+ "github.com/synapsecns/sanguine/ethergo/example"
+ "github.com/synapsecns/sanguine/ethergo/example/counter"
+ "github.com/synapsecns/sanguine/ethergo/listener"
+ db2 "github.com/synapsecns/sanguine/ethergo/listener/db"
+ "gorm.io/gorm"
+ "gorm.io/gorm/schema"
+ "math/big"
+ "os"
+ "testing"
+ "time"
+
+ "github.com/Flaque/filet"
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/stretchr/testify/suite"
+ "github.com/synapsecns/sanguine/core/metrics"
+ "github.com/synapsecns/sanguine/core/testsuite"
+ "github.com/synapsecns/sanguine/ethergo/backends"
+ "github.com/synapsecns/sanguine/ethergo/backends/geth"
+ "gorm.io/driver/sqlite"
+)
+
+const chainID = 10
+
+type ListenerTestSuite struct {
+ *testsuite.TestSuite
+ manager *example.DeployManager
+ backend backends.SimulatedTestBackend
+ store db2.ChainListenerDB
+ metrics metrics.Handler
+ counter *counter.CounterRef
+}
+
+func NewListenerSuite(tb testing.TB) *ListenerTestSuite {
+ tb.Helper()
+
+ return &ListenerTestSuite{
+ TestSuite: testsuite.NewTestSuite(tb),
+ }
+}
+
+func TestListenerSuite(t *testing.T) {
+ suite.Run(t, NewListenerSuite(t))
+}
+
+func (l *ListenerTestSuite) SetupTest() {
+ l.TestSuite.SetupTest()
+
+ l.manager = example.NewDeployManager(l.T())
+ l.backend = geth.NewEmbeddedBackendForChainID(l.GetTestContext(), l.T(), big.NewInt(chainID))
+ var err error
+ l.metrics = metrics.NewNullHandler()
+ l.store, err = NewSqliteStore(l.GetTestContext(), filet.TmpDir(l.T(), ""), l.metrics)
+ l.Require().NoError(err)
+
+ _, l.counter = l.manager.GetCounter(l.GetTestContext(), l.backend)
+}
+
+func (l *ListenerTestSuite) TestGetMetadataNoStore() {
+ deployBlock, err := l.counter.DeployBlock(&bind.CallOpts{Context: l.GetTestContext()})
+ l.NoError(err)
+
+ // nothing stored, should use start block
+ cl := listener.NewTestChainListener(listener.TestChainListenerArgs{
+ Address: l.counter.Address(),
+ InitialBlock: deployBlock.Uint64(),
+ Client: l.backend,
+ Store: l.store,
+ Handler: l.metrics,
+ })
+
+ startBlock, myChainID, err := cl.GetMetadata(l.GetTestContext())
+ l.NoError(err)
+ l.Equal(myChainID, uint64(chainID))
+ l.Equal(startBlock, deployBlock.Uint64())
+}
+
+func (l *ListenerTestSuite) TestStartBlock() {
+ cl := listener.NewTestChainListener(listener.TestChainListenerArgs{
+ Address: l.counter.Address(),
+ Client: l.backend,
+ Store: l.store,
+ Handler: l.metrics,
+ })
+
+ deployBlock, err := l.counter.DeployBlock(&bind.CallOpts{Context: l.GetTestContext()})
+ l.NoError(err)
+
+ expectedLastIndexed := deployBlock.Uint64() + 10
+ err = l.store.PutLatestBlock(l.GetTestContext(), chainID, expectedLastIndexed)
+ l.NoError(err)
+
+ startBlock, cid, err := cl.GetMetadata(l.GetTestContext())
+ l.NoError(err)
+ l.Equal(cid, uint64(chainID))
+ l.Equal(startBlock, expectedLastIndexed)
+}
+
+func (l *ListenerTestSuite) TestListen() {
+
+}
+
+// NewSqliteStore creates a new sqlite data store.
+func NewSqliteStore(parentCtx context.Context, dbPath string, handler metrics.Handler) (_ *db2.Store, err error) {
+ logger := log.Logger("sqlite-store")
+
+ logger.Debugf("creating sqlite store at %s", dbPath)
+
+ ctx, span := handler.Tracer().Start(parentCtx, "start-sqlite")
+ defer func() {
+ metrics.EndSpanWithErr(span, err)
+ }()
+
+ // create the directory to the store if it doesn't exist
+ err = os.MkdirAll(dbPath, os.ModePerm)
+ if err != nil {
+ return nil, fmt.Errorf("could not create sqlite store")
+ }
+
+ logger.Warnf("submitter database is at %s/synapse.db", dbPath)
+
+ namingStrategy := schema.NamingStrategy{
+ TablePrefix: fmt.Sprintf("test%d_%d_", gofakeit.Int64(), time.Now().Unix()),
+ }
+
+ gdb, err := gorm.Open(sqlite.Open(fmt.Sprintf("%s/%s", dbPath, "synapse.db")), &gorm.Config{
+ DisableForeignKeyConstraintWhenMigrating: true,
+ Logger: common_base.GetGormLogger(logger),
+ FullSaveAssociations: true,
+ SkipDefaultTransaction: true,
+ NamingStrategy: namingStrategy,
+ })
+ if err != nil {
+ return nil, fmt.Errorf("could not connect to db %s: %w", dbPath, err)
+ }
+
+ err = gdb.AutoMigrate(&db2.LastIndexed{})
+ if err != nil {
+ return nil, fmt.Errorf("could not migrate models: %w", err)
+ }
+
+ handler.AddGormCallbacks(gdb)
+
+ err = gdb.WithContext(ctx).AutoMigrate(db2.GetAllModels()...)
+
+ if err != nil {
+ return nil, fmt.Errorf("could not migrate models: %w", err)
+ }
+ return db2.NewChainListenerStore(gdb, handler), nil
+}
diff --git a/services/rfq/e2e/rfq_test.go b/services/rfq/e2e/rfq_test.go
index 0a8e376271..e87cef2739 100644
--- a/services/rfq/e2e/rfq_test.go
+++ b/services/rfq/e2e/rfq_test.go
@@ -238,10 +238,7 @@ func (i *IntegrationSuite) TestUSDCtoUSDC() {
}
originPending, err := i.store.HasPendingRebalance(i.GetTestContext(), uint64(i.originBackend.GetChainID()))
i.NoError(err)
- if !originPending {
- return false
- }
- return true
+ return originPending
})
}
diff --git a/services/rfq/go.mod b/services/rfq/go.mod
index e10412187b..f6d0289d79 100644
--- a/services/rfq/go.mod
+++ b/services/rfq/go.mod
@@ -14,7 +14,6 @@ require (
github.com/ipfs/go-log v1.0.5
github.com/jellydator/ttlcache/v3 v3.1.1
github.com/jftuga/ellipsis v1.0.0
- github.com/jpillora/backoff v1.0.0
github.com/lmittmann/w3 v0.10.0
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5
github.com/puzpuzpuz/xsync/v2 v2.5.1
@@ -22,7 +21,8 @@ require (
github.com/stretchr/testify v1.8.4
github.com/synapsecns/sanguine/contrib/screener-api v0.0.0-00010101000000-000000000000
github.com/synapsecns/sanguine/core v0.0.0-00010101000000-000000000000
- github.com/synapsecns/sanguine/ethergo v0.0.2
+ github.com/synapsecns/sanguine/ethergo v0.1.0
+ github.com/synapsecns/sanguine/services/cctp-relayer v0.0.0-00010101000000-000000000000
github.com/synapsecns/sanguine/services/omnirpc v0.0.0-00010101000000-000000000000
github.com/urfave/cli/v2 v2.25.7
go.opentelemetry.io/otel v1.22.0
@@ -128,7 +128,6 @@ require (
github.com/go-stack/stack v1.8.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gofrs/flock v0.8.1 // indirect
- github.com/gofrs/uuid v4.2.0+incompatible // indirect
github.com/gogo/protobuf v1.3.3 // indirect
github.com/golang-jwt/jwt/v4 v4.4.3 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
@@ -167,6 +166,7 @@ require (
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
+ github.com/jpillora/backoff v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.17.3 // indirect
@@ -288,8 +288,8 @@ replace (
github.com/synapsecns/sanguine/contrib/screener-api => ../../contrib/screener-api
github.com/synapsecns/sanguine/core => ../../core
github.com/synapsecns/sanguine/ethergo => ../../ethergo
+ github.com/synapsecns/sanguine/services/cctp-relayer => ../cctp-relayer
github.com/synapsecns/sanguine/services/omnirpc => ../omnirpc
github.com/synapsecns/sanguine/services/scribe => ../scribe
github.com/synapsecns/sanguine/tools => ../../tools
- github.com/synapsecns/sanguine/services/cctp-relayer => ../cctp-relayer
)
diff --git a/services/rfq/relayer/chain/chain.go b/services/rfq/relayer/chain/chain.go
index 32cffe9287..1655c36cfa 100644
--- a/services/rfq/relayer/chain/chain.go
+++ b/services/rfq/relayer/chain/chain.go
@@ -10,8 +10,8 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/synapsecns/sanguine/core"
- "github.com/synapsecns/sanguine/ethergo/chain/listener"
"github.com/synapsecns/sanguine/ethergo/client"
+ "github.com/synapsecns/sanguine/ethergo/listener"
"github.com/synapsecns/sanguine/ethergo/submitter"
"github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge"
"github.com/synapsecns/sanguine/services/rfq/relayer/reldb"
diff --git a/services/rfq/relayer/inventory/rebalance.go b/services/rfq/relayer/inventory/rebalance.go
index 9cf37b82ac..d464c7947f 100644
--- a/services/rfq/relayer/inventory/rebalance.go
+++ b/services/rfq/relayer/inventory/rebalance.go
@@ -9,7 +9,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/synapsecns/sanguine/core/metrics"
- "github.com/synapsecns/sanguine/ethergo/chain/listener"
+ "github.com/synapsecns/sanguine/ethergo/listener"
"github.com/synapsecns/sanguine/ethergo/submitter"
"github.com/synapsecns/sanguine/services/cctp-relayer/contracts/cctp"
"github.com/synapsecns/sanguine/services/rfq/relayer/relconfig"
diff --git a/services/rfq/relayer/relapi/server.go b/services/rfq/relayer/relapi/server.go
index 73d215de31..faad66141d 100644
--- a/services/rfq/relayer/relapi/server.go
+++ b/services/rfq/relayer/relapi/server.go
@@ -14,7 +14,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/synapsecns/sanguine/core/metrics"
baseServer "github.com/synapsecns/sanguine/core/server"
- "github.com/synapsecns/sanguine/ethergo/chain/listener"
+ "github.com/synapsecns/sanguine/ethergo/listener"
omniClient "github.com/synapsecns/sanguine/services/omnirpc/client"
"github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge"
"github.com/synapsecns/sanguine/services/rfq/relayer/chain"
diff --git a/services/rfq/relayer/reldb/base/block.go b/services/rfq/relayer/reldb/base/block.go
deleted file mode 100644
index 75322aa224..0000000000
--- a/services/rfq/relayer/reldb/base/block.go
+++ /dev/null
@@ -1,40 +0,0 @@
-package base
-
-import (
- "context"
- "errors"
- "fmt"
- "github.com/synapsecns/sanguine/services/rfq/relayer/reldb"
- "gorm.io/gorm"
- "gorm.io/gorm/clause"
-)
-
-// PutLatestBlock upserts the latest block into the database.
-func (s Store) PutLatestBlock(ctx context.Context, chainID, height uint64) error {
- tx := s.DB().WithContext(ctx).Clauses(clause.OnConflict{
- Columns: []clause.Column{{Name: chainIDFieldName}},
- DoUpdates: clause.AssignmentColumns([]string{chainIDFieldName, blockNumberFieldName}),
- }).Create(&LastIndexed{
- ChainID: chainID,
- BlockNumber: int(height),
- })
-
- if tx.Error != nil {
- return fmt.Errorf("could not block updated: %w", tx.Error)
- }
- return nil
-}
-
-// LatestBlockForChain gets the latest block for a chain.
-func (s Store) LatestBlockForChain(ctx context.Context, chainID uint64) (uint64, error) {
- blockWatchModel := LastIndexed{ChainID: chainID}
- err := s.db.WithContext(ctx).First(&blockWatchModel).Error
- if err != nil {
- if errors.Is(err, gorm.ErrRecordNotFound) {
- return 0, reldb.ErrNoLatestBlockForChainID
- }
- return 0, fmt.Errorf("could not fetch latest block: %w", err)
- }
-
- return uint64(blockWatchModel.BlockNumber), nil
-}
diff --git a/services/rfq/relayer/reldb/base/model.go b/services/rfq/relayer/reldb/base/model.go
index 1321a1d833..f4226369f9 100644
--- a/services/rfq/relayer/reldb/base/model.go
+++ b/services/rfq/relayer/reldb/base/model.go
@@ -14,13 +14,10 @@ import (
"github.com/synapsecns/sanguine/core/dbcommon"
"github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge"
"github.com/synapsecns/sanguine/services/rfq/relayer/reldb"
- "gorm.io/gorm"
)
func init() {
namer := dbcommon.NewNamer(GetAllModels())
- chainIDFieldName = namer.GetConsistentName("ChainID")
- blockNumberFieldName = namer.GetConsistentName("BlockNumber")
statusFieldName = namer.GetConsistentName("Status")
transactionIDFieldName = namer.GetConsistentName("TransactionID")
originTxHashFieldName = namer.GetConsistentName("OriginTxHash")
@@ -29,11 +26,6 @@ func init() {
}
var (
- // chainIDFieldName gets the chain id field name.
- chainIDFieldName string
- // blockNumberFieldName is the name of the block number field.
- blockNumberFieldName string
-
statusFieldName string
// transactionIDFieldName is the transactions id field name.
transactionIDFieldName string
@@ -45,24 +37,6 @@ var (
rebalanceIDFieldName string
)
-// LastIndexed is used to make sure we haven't missed any events while offline.
-// since we event source - rather than use a state machine this is needed to make sure we haven't missed any events
-// by allowing us to go back and source any events we may have missed.
-//
-// this does not inherit from gorm.model to allow us to use ChainID as a primary key.
-type LastIndexed struct {
- // CreatedAt is the creation time
- CreatedAt time.Time
- // UpdatedAt is the update time
- UpdatedAt time.Time
- // DeletedAt time
- DeletedAt gorm.DeletedAt `gorm:"index"`
- // ChainID is the chain id of the chain we're watching blocks on. This is our primary index.
- ChainID uint64 `gorm:"column:chain_id;primaryKey;autoIncrement:false"`
- // BlockHeight is the highest height we've seen on the chain
- BlockNumber int `gorm:"block_number"`
-}
-
// RequestForQuote is the primary event model.
type RequestForQuote struct {
// CreatedAt is the creation time
diff --git a/services/rfq/relayer/reldb/base/store.go b/services/rfq/relayer/reldb/base/store.go
index 0d1a2bf30a..1a026933f6 100644
--- a/services/rfq/relayer/reldb/base/store.go
+++ b/services/rfq/relayer/reldb/base/store.go
@@ -2,6 +2,7 @@ package base
import (
"github.com/synapsecns/sanguine/core/metrics"
+ listenerDB "github.com/synapsecns/sanguine/ethergo/listener/db"
submitterDB "github.com/synapsecns/sanguine/ethergo/submitter/db"
"github.com/synapsecns/sanguine/ethergo/submitter/db/txdb"
"github.com/synapsecns/sanguine/services/rfq/relayer/reldb"
@@ -10,6 +11,7 @@ import (
// Store implements the service.
type Store struct {
+ listenerDB.ChainListenerDB
db *gorm.DB
submitterStore submitterDB.Service
}
@@ -17,7 +19,8 @@ type Store struct {
// NewStore creates a new store.
func NewStore(db *gorm.DB, metrics metrics.Handler) *Store {
txDB := txdb.NewTXStore(db, metrics)
- return &Store{db: db, submitterStore: txDB}
+
+ return &Store{ChainListenerDB: listenerDB.NewChainListenerStore(db, metrics), db: db, submitterStore: txDB}
}
// DB gets the database object for mutation outside of the lib.
@@ -33,7 +36,8 @@ func (s Store) SubmitterDB() submitterDB.Service {
// GetAllModels gets all models to migrate
// see: https://medium.com/@SaifAbid/slice-interfaces-8c78f8b6345d for an explanation of why we can't do this at initialization time
func GetAllModels() (allModels []interface{}) {
- allModels = append(txdb.GetAllModels(), &LastIndexed{}, &RequestForQuote{}, &Rebalance{})
+ allModels = append(txdb.GetAllModels(), &RequestForQuote{}, &Rebalance{})
+ allModels = append(allModels, listenerDB.GetAllModels()...)
return allModels
}
diff --git a/services/rfq/relayer/reldb/db.go b/services/rfq/relayer/reldb/db.go
index 9dbb1b4328..f3af3ed0b7 100644
--- a/services/rfq/relayer/reldb/db.go
+++ b/services/rfq/relayer/reldb/db.go
@@ -5,6 +5,7 @@ import (
"database/sql/driver"
"errors"
"fmt"
+ "github.com/synapsecns/sanguine/ethergo/listener/db"
"math/big"
"github.com/ethereum/go-ethereum/common"
@@ -15,8 +16,6 @@ import (
// Writer is the interface for writing to the database.
type Writer interface {
- // PutLatestBlock upsers the latest block on a given chain id to be new height.
- PutLatestBlock(ctx context.Context, chainID, height uint64) error
// StoreQuoteRequest stores a quote request. If one already exists, only the status will be updated
// TODO: find a better way to describe this in the name
StoreQuoteRequest(ctx context.Context, request QuoteRequest) error
@@ -33,8 +32,6 @@ type Writer interface {
// Reader is the interface for reading from the database.
type Reader interface {
- // LatestBlockForChain gets the latest block for a given chain id.
- LatestBlockForChain(ctx context.Context, chainID uint64) (uint64, error)
// GetQuoteRequestByID gets a quote request by id. Should return ErrNoQuoteForID if not found
GetQuoteRequestByID(ctx context.Context, id [32]byte) (*QuoteRequest, error)
// GetQuoteRequestByOriginTxHash gets a quote request by origin tx hash. Should return ErrNoQuoteForTxHash if not found
@@ -51,11 +48,10 @@ type Service interface {
// SubmitterDB returns the submitter database service.
SubmitterDB() submitterDB.Service
Writer
+ db.ChainListenerDB
}
var (
- // ErrNoLatestBlockForChainID is returned when no block exists for the chain.
- ErrNoLatestBlockForChainID = errors.New("no latest block for chainId")
// ErrNoQuoteForID means the quote was not found.
ErrNoQuoteForID = errors.New("no quote found for tx id")
// ErrNoQuoteForTxHash means the quote was not found.
diff --git a/services/rfq/relayer/reldb/db_test.go b/services/rfq/relayer/reldb/db_test.go
index ea6ce9a1ad..d36fc760c1 100644
--- a/services/rfq/relayer/reldb/db_test.go
+++ b/services/rfq/relayer/reldb/db_test.go
@@ -2,6 +2,7 @@ package reldb_test
import (
"errors"
+ "github.com/synapsecns/sanguine/ethergo/listener"
"github.com/synapsecns/sanguine/services/rfq/relayer/reldb"
)
@@ -9,7 +10,7 @@ func (d *DBSuite) TestBlock() {
d.RunOnAllDBs(func(testDB reldb.Service) {
const testChainID = 5
_, err := testDB.LatestBlockForChain(d.GetTestContext(), testChainID)
- d.True(errors.Is(err, reldb.ErrNoLatestBlockForChainID))
+ d.True(errors.Is(err, listener.ErrNoLatestBlockForChainID))
testHeight := 10
diff --git a/services/rfq/relayer/service/relayer.go b/services/rfq/relayer/service/relayer.go
index d4cb2fb40d..d1af5aa87a 100644
--- a/services/rfq/relayer/service/relayer.go
+++ b/services/rfq/relayer/service/relayer.go
@@ -12,7 +12,7 @@ import (
"github.com/jellydator/ttlcache/v3"
"github.com/synapsecns/sanguine/core/dbcommon"
"github.com/synapsecns/sanguine/core/metrics"
- "github.com/synapsecns/sanguine/ethergo/chain/listener"
+ "github.com/synapsecns/sanguine/ethergo/listener"
signerConfig "github.com/synapsecns/sanguine/ethergo/signer/config"
"github.com/synapsecns/sanguine/ethergo/signer/signer"
"github.com/synapsecns/sanguine/ethergo/submitter"
diff --git a/services/rfq/testutil/deployers.go b/services/rfq/testutil/deployers.go
index 4d0522d0e1..79ae1538e8 100644
--- a/services/rfq/testutil/deployers.go
+++ b/services/rfq/testutil/deployers.go
@@ -27,7 +27,6 @@ type DeployManager struct {
func NewDeployManager(t *testing.T) *DeployManager {
t.Helper()
- // TODO: add contracts here
parentManager := manager.NewDeployerManager(t, NewFastBridgeDeployer, NewMockERC20Deployer, NewMockFastBridgeDeployer, NewWETH9Deployer, NewUSDTDeployer, NewUSDCDeployer, NewDAIDeployer)
return &DeployManager{parentManager}
}