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} }