From a135b27980f6ca84eeaf6ac3c22ca72532796d63 Mon Sep 17 00:00:00 2001 From: Ivan Belyakov Date: Sun, 26 May 2024 10:31:13 +0200 Subject: [PATCH] test(wallet)_: created Transactor interface - Moved some methods from Transactor to users of it to clean interface. - Mocked Bridge interface and Transactor interface for tests - Wrote unit tests for SendTransaction --- Makefile | 2 + api/geth_backend.go | 8 +- services/wallet/api.go | 4 +- services/wallet/bridge/cbridge.go | 4 +- services/wallet/bridge/erc1155_transfer.go | 4 +- services/wallet/bridge/erc721_transfer.go | 4 +- services/wallet/bridge/hop.go | 4 +- services/wallet/bridge/mock_bridge/README.md | 2 + services/wallet/bridge/mock_bridge/bridge.go | 192 ++++++++++++++++++ services/wallet/bridge/swap_paraswap.go | 4 +- services/wallet/bridge/transfer.go | 4 +- services/wallet/transfer/helpers.go | 2 +- .../wallet/transfer/multi_transaction_db.go | 69 ++++--- .../transfer/multi_transaction_db_test.go | 19 +- services/wallet/transfer/testutils.go | 47 +++-- .../wallet/transfer/transaction_manager.go | 7 +- .../transaction_manager_multitransaction.go | 8 +- ...ansaction_manager_multitransaction_test.go | 166 +++++++++++++++ transactions/mock_transactor/README.md | 2 + transactions/mock_transactor/transactor.go | 177 ++++++++++++++++ transactions/transactor.go | 35 ++-- transactions/transactor_test.go | 11 +- 22 files changed, 674 insertions(+), 101 deletions(-) create mode 100644 services/wallet/bridge/mock_bridge/README.md create mode 100644 services/wallet/bridge/mock_bridge/bridge.go create mode 100644 services/wallet/transfer/transaction_manager_multitransaction_test.go create mode 100644 transactions/mock_transactor/README.md create mode 100644 transactions/mock_transactor/transactor.go diff --git a/Makefile b/Makefile index 733064d93..ea9860a65 100644 --- a/Makefile +++ b/Makefile @@ -342,6 +342,8 @@ mock: ##@other Regenerate mocks mockgen -package=fake -destination=transactions/fake/mock.go -source=transactions/fake/txservice.go mockgen -package=status -destination=services/status/account_mock.go -source=services/status/service.go mockgen -package=peer -destination=services/peer/discoverer_mock.go -source=services/peer/service.go + mockgen -package=mock_transactor -destination=transactions/mock_transactor/transactor.go -source=transactions/transactor.go + mockgen -package=mock_bridge -destination=services/wallet/bridge/mock_bridge/bridge.go -source=services/wallet/bridge/bridge.go docker-test: ##@tests Run tests in a docker container with golang. docker run --privileged --rm -it -v "$(PWD):$(DOCKER_TEST_WORKDIR)" -w "$(DOCKER_TEST_WORKDIR)" $(DOCKER_TEST_IMAGE) go test ${ARGS} diff --git a/api/geth_backend.go b/api/geth_backend.go index 4187e33c4..c198b01cb 100644 --- a/api/geth_backend.go +++ b/api/geth_backend.go @@ -18,6 +18,7 @@ import ( "github.com/imdario/mergo" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" ethcrypto "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" @@ -1950,7 +1951,12 @@ func (b *GethStatusBackend) SendTransactionWithChainID(chainID uint64, sendArgs } func (b *GethStatusBackend) SendTransactionWithSignature(sendArgs transactions.SendTxArgs, sig []byte) (hash types.Hash, err error) { - return b.transactor.BuildTransactionAndSendWithSignature(b.transactor.NetworkID(), sendArgs, sig) + txWithSignature, err := b.transactor.BuildTransactionWithSignature(b.transactor.NetworkID(), sendArgs, sig) + if err != nil { + return hash, err + } + + return b.transactor.SendTransactionWithSignature(common.Address(sendArgs.From), sendArgs.Symbol, sendArgs.MultiTransactionID, txWithSignature) } // HashTransaction validate the transaction and returns new sendArgs and the transaction hash. diff --git a/services/wallet/api.go b/services/wallet/api.go index 6b068a4b0..0bc9ff7ef 100644 --- a/services/wallet/api.go +++ b/services/wallet/api.go @@ -602,13 +602,13 @@ func (api *API) SendTransactionWithSignature(ctx context.Context, chainID uint64 if err != nil { return hash, err } - return api.s.transactionManager.SendTransactionWithSignature(chainID, txType, params, sig) + return api.s.transactionManager.SendTransactionWithSignature(chainID, params, sig) } func (api *API) CreateMultiTransaction(ctx context.Context, multiTransactionCommand *transfer.MultiTransactionCommand, data []*bridge.TransactionBridge, password string) (*transfer.MultiTransactionCommandResult, error) { log.Debug("[WalletAPI:: CreateMultiTransaction] create multi transaction") - cmd, err := api.s.transactionManager.CreateMultiTransactionFromCommand(ctx, multiTransactionCommand, data) + cmd, err := api.s.transactionManager.CreateMultiTransactionFromCommand(multiTransactionCommand, data) if err != nil { return nil, err } diff --git a/services/wallet/bridge/cbridge.go b/services/wallet/bridge/cbridge.go index 3ae3ad5d0..4216cd951 100644 --- a/services/wallet/bridge/cbridge.go +++ b/services/wallet/bridge/cbridge.go @@ -43,13 +43,13 @@ type CBridgeTxArgs struct { type CBridge struct { rpcClient *rpc.Client httpClient *thirdparty.HTTPClient - transactor *transactions.Transactor + transactor transactions.TransactorIface tokenManager *token.Manager prodTransferConfig *cbridge.GetTransferConfigsResponse testTransferConfig *cbridge.GetTransferConfigsResponse } -func NewCbridge(rpcClient *rpc.Client, transactor *transactions.Transactor, tokenManager *token.Manager) *CBridge { +func NewCbridge(rpcClient *rpc.Client, transactor transactions.TransactorIface, tokenManager *token.Manager) *CBridge { return &CBridge{ rpcClient: rpcClient, httpClient: thirdparty.NewHTTPClient(), diff --git a/services/wallet/bridge/erc1155_transfer.go b/services/wallet/bridge/erc1155_transfer.go index 1e784faf3..732cc365d 100644 --- a/services/wallet/bridge/erc1155_transfer.go +++ b/services/wallet/bridge/erc1155_transfer.go @@ -30,10 +30,10 @@ type ERC1155TransferTxArgs struct { type ERC1155TransferBridge struct { rpcClient *rpc.Client - transactor *transactions.Transactor + transactor transactions.TransactorIface } -func NewERC1155TransferBridge(rpcClient *rpc.Client, transactor *transactions.Transactor) *ERC1155TransferBridge { +func NewERC1155TransferBridge(rpcClient *rpc.Client, transactor transactions.TransactorIface) *ERC1155TransferBridge { return &ERC1155TransferBridge{rpcClient: rpcClient, transactor: transactor} } diff --git a/services/wallet/bridge/erc721_transfer.go b/services/wallet/bridge/erc721_transfer.go index 8fc76bdf9..33d11b21c 100644 --- a/services/wallet/bridge/erc721_transfer.go +++ b/services/wallet/bridge/erc721_transfer.go @@ -29,10 +29,10 @@ type ERC721TransferTxArgs struct { type ERC721TransferBridge struct { rpcClient *rpc.Client - transactor *transactions.Transactor + transactor transactions.TransactorIface } -func NewERC721TransferBridge(rpcClient *rpc.Client, transactor *transactions.Transactor) *ERC721TransferBridge { +func NewERC721TransferBridge(rpcClient *rpc.Client, transactor transactions.TransactorIface) *ERC721TransferBridge { return &ERC721TransferBridge{rpcClient: rpcClient, transactor: transactor} } diff --git a/services/wallet/bridge/hop.go b/services/wallet/bridge/hop.go index 5b1c42b6e..184e2a62d 100644 --- a/services/wallet/bridge/hop.go +++ b/services/wallet/bridge/hop.go @@ -107,14 +107,14 @@ func (bf *BonderFee) UnmarshalJSON(data []byte) error { } type HopBridge struct { - transactor *transactions.Transactor + transactor transactions.TransactorIface httpClient *thirdparty.HTTPClient tokenManager *token.Manager contractMaker *contracts.ContractMaker bonderFee *BonderFee } -func NewHopBridge(rpcClient *rpc.Client, transactor *transactions.Transactor, tokenManager *token.Manager) *HopBridge { +func NewHopBridge(rpcClient *rpc.Client, transactor transactions.TransactorIface, tokenManager *token.Manager) *HopBridge { return &HopBridge{ contractMaker: &contracts.ContractMaker{RPCClient: rpcClient}, httpClient: thirdparty.NewHTTPClient(), diff --git a/services/wallet/bridge/mock_bridge/README.md b/services/wallet/bridge/mock_bridge/README.md new file mode 100644 index 000000000..a80b77ac6 --- /dev/null +++ b/services/wallet/bridge/mock_bridge/README.md @@ -0,0 +1,2 @@ +# To generate mocks, from status-go root directory: +mockgen -source=services/wallet/bridge/bridge.go -destination=services/wallet/bridge/mock_bridge/bridge.go -package=mock_bridge diff --git a/services/wallet/bridge/mock_bridge/bridge.go b/services/wallet/bridge/mock_bridge/bridge.go new file mode 100644 index 000000000..833a91fcf --- /dev/null +++ b/services/wallet/bridge/mock_bridge/bridge.go @@ -0,0 +1,192 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: services/wallet/bridge/bridge.go + +// Package mock_bridge is a generated GoMock package. +package mock_bridge + +import ( + big "math/big" + reflect "reflect" + + common "github.com/ethereum/go-ethereum/common" + types "github.com/ethereum/go-ethereum/core/types" + gomock "github.com/golang/mock/gomock" + account "github.com/status-im/status-go/account" + types0 "github.com/status-im/status-go/eth-node/types" + params "github.com/status-im/status-go/params" + bridge "github.com/status-im/status-go/services/wallet/bridge" + token "github.com/status-im/status-go/services/wallet/token" +) + +// MockBridge is a mock of Bridge interface. +type MockBridge struct { + ctrl *gomock.Controller + recorder *MockBridgeMockRecorder +} + +// MockBridgeMockRecorder is the mock recorder for MockBridge. +type MockBridgeMockRecorder struct { + mock *MockBridge +} + +// NewMockBridge creates a new mock instance. +func NewMockBridge(ctrl *gomock.Controller) *MockBridge { + mock := &MockBridge{ctrl: ctrl} + mock.recorder = &MockBridgeMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockBridge) EXPECT() *MockBridgeMockRecorder { + return m.recorder +} + +// AvailableFor mocks base method. +func (m *MockBridge) AvailableFor(from, to *params.Network, token, toToken *token.Token) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AvailableFor", from, to, token, toToken) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AvailableFor indicates an expected call of AvailableFor. +func (mr *MockBridgeMockRecorder) AvailableFor(from, to, token, toToken interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AvailableFor", reflect.TypeOf((*MockBridge)(nil).AvailableFor), from, to, token, toToken) +} + +// BuildTransaction mocks base method. +func (m *MockBridge) BuildTransaction(sendArgs *bridge.TransactionBridge) (*types.Transaction, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BuildTransaction", sendArgs) + ret0, _ := ret[0].(*types.Transaction) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// BuildTransaction indicates an expected call of BuildTransaction. +func (mr *MockBridgeMockRecorder) BuildTransaction(sendArgs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildTransaction", reflect.TypeOf((*MockBridge)(nil).BuildTransaction), sendArgs) +} + +// BuildTx mocks base method. +func (m *MockBridge) BuildTx(fromNetwork, toNetwork *params.Network, fromAddress, toAddress common.Address, token *token.Token, amountIn, bonderFee *big.Int) (*types.Transaction, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BuildTx", fromNetwork, toNetwork, fromAddress, toAddress, token, amountIn, bonderFee) + ret0, _ := ret[0].(*types.Transaction) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// BuildTx indicates an expected call of BuildTx. +func (mr *MockBridgeMockRecorder) BuildTx(fromNetwork, toNetwork, fromAddress, toAddress, token, amountIn, bonderFee interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildTx", reflect.TypeOf((*MockBridge)(nil).BuildTx), fromNetwork, toNetwork, fromAddress, toAddress, token, amountIn, bonderFee) +} + +// CalculateAmountOut mocks base method. +func (m *MockBridge) CalculateAmountOut(from, to *params.Network, amountIn *big.Int, symbol string) (*big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CalculateAmountOut", from, to, amountIn, symbol) + ret0, _ := ret[0].(*big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CalculateAmountOut indicates an expected call of CalculateAmountOut. +func (mr *MockBridgeMockRecorder) CalculateAmountOut(from, to, amountIn, symbol interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CalculateAmountOut", reflect.TypeOf((*MockBridge)(nil).CalculateAmountOut), from, to, amountIn, symbol) +} + +// CalculateFees mocks base method. +func (m *MockBridge) CalculateFees(from, to *params.Network, token *token.Token, amountIn *big.Int) (*big.Int, *big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CalculateFees", from, to, token, amountIn) + ret0, _ := ret[0].(*big.Int) + ret1, _ := ret[1].(*big.Int) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// CalculateFees indicates an expected call of CalculateFees. +func (mr *MockBridgeMockRecorder) CalculateFees(from, to, token, amountIn interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CalculateFees", reflect.TypeOf((*MockBridge)(nil).CalculateFees), from, to, token, amountIn) +} + +// EstimateGas mocks base method. +func (m *MockBridge) EstimateGas(fromNetwork, toNetwork *params.Network, from, to common.Address, token, toToken *token.Token, amountIn *big.Int) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EstimateGas", fromNetwork, toNetwork, from, to, token, toToken, amountIn) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EstimateGas indicates an expected call of EstimateGas. +func (mr *MockBridgeMockRecorder) EstimateGas(fromNetwork, toNetwork, from, to, token, toToken, amountIn interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EstimateGas", reflect.TypeOf((*MockBridge)(nil).EstimateGas), fromNetwork, toNetwork, from, to, token, toToken, amountIn) +} + +// GetContractAddress mocks base method. +func (m *MockBridge) GetContractAddress(network *params.Network, token *token.Token) (common.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetContractAddress", network, token) + ret0, _ := ret[0].(common.Address) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetContractAddress indicates an expected call of GetContractAddress. +func (mr *MockBridgeMockRecorder) GetContractAddress(network, token interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContractAddress", reflect.TypeOf((*MockBridge)(nil).GetContractAddress), network, token) +} + +// Name mocks base method. +func (m *MockBridge) Name() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Name") + ret0, _ := ret[0].(string) + return ret0 +} + +// Name indicates an expected call of Name. +func (mr *MockBridgeMockRecorder) Name() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockBridge)(nil).Name)) +} + +// PackTxInputData mocks base method. +func (m *MockBridge) PackTxInputData(contractType string, fromNetwork, toNetwork *params.Network, from, to common.Address, token *token.Token, amountIn *big.Int) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PackTxInputData", contractType, fromNetwork, toNetwork, from, to, token, amountIn) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PackTxInputData indicates an expected call of PackTxInputData. +func (mr *MockBridgeMockRecorder) PackTxInputData(contractType, fromNetwork, toNetwork, from, to, token, amountIn interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PackTxInputData", reflect.TypeOf((*MockBridge)(nil).PackTxInputData), contractType, fromNetwork, toNetwork, from, to, token, amountIn) +} + +// Send mocks base method. +func (m *MockBridge) Send(sendArgs *bridge.TransactionBridge, verifiedAccount *account.SelectedExtKey) (types0.Hash, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Send", sendArgs, verifiedAccount) + ret0, _ := ret[0].(types0.Hash) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Send indicates an expected call of Send. +func (mr *MockBridgeMockRecorder) Send(sendArgs, verifiedAccount interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Send", reflect.TypeOf((*MockBridge)(nil).Send), sendArgs, verifiedAccount) +} diff --git a/services/wallet/bridge/swap_paraswap.go b/services/wallet/bridge/swap_paraswap.go index f6da5cdf0..ca5832247 100644 --- a/services/wallet/bridge/swap_paraswap.go +++ b/services/wallet/bridge/swap_paraswap.go @@ -28,10 +28,10 @@ type SwapTxArgs struct { type SwapParaswap struct { paraswapClient *paraswap.ClientV5 priceRoute paraswap.Route - transactor *transactions.Transactor + transactor transactions.TransactorIface } -func NewSwapParaswap(rpcClient *rpc.Client, transactor *transactions.Transactor, tokenManager *walletToken.Manager) *SwapParaswap { +func NewSwapParaswap(rpcClient *rpc.Client, transactor transactions.TransactorIface, tokenManager *walletToken.Manager) *SwapParaswap { return &SwapParaswap{ paraswapClient: paraswap.NewClientV5(walletCommon.EthereumMainnet), transactor: transactor, diff --git a/services/wallet/bridge/transfer.go b/services/wallet/bridge/transfer.go index 3c733e747..6b329aada 100644 --- a/services/wallet/bridge/transfer.go +++ b/services/wallet/bridge/transfer.go @@ -21,10 +21,10 @@ import ( type TransferBridge struct { rpcClient *rpc.Client - transactor *transactions.Transactor + transactor transactions.TransactorIface } -func NewTransferBridge(rpcClient *rpc.Client, transactor *transactions.Transactor) *TransferBridge { +func NewTransferBridge(rpcClient *rpc.Client, transactor transactions.TransactorIface) *TransferBridge { return &TransferBridge{rpcClient: rpcClient, transactor: transactor} } diff --git a/services/wallet/transfer/helpers.go b/services/wallet/transfer/helpers.go index 1591f58d1..3058feff0 100644 --- a/services/wallet/transfer/helpers.go +++ b/services/wallet/transfer/helpers.go @@ -178,7 +178,7 @@ func (tm *TransactionManager) removeMultiTransactionByAddress(address common.Add details := NewMultiTxDetails() details.FromAddress = address - mtxs, err := tm.storage.ReadMultiTransactionsByDetails(details) + mtxs, err := tm.storage.ReadMultiTransactions(details) ids := make([]wallet_common.MultiTransactionIDType, 0) for _, mtx := range mtxs { diff --git a/services/wallet/transfer/multi_transaction_db.go b/services/wallet/transfer/multi_transaction_db.go index 3e3fb32bd..f02af724a 100644 --- a/services/wallet/transfer/multi_transaction_db.go +++ b/services/wallet/transfer/multi_transaction_db.go @@ -9,20 +9,38 @@ import ( wallet_common "github.com/status-im/status-go/services/wallet/common" ) -// DO NOT CREATE IT MANUALLY! Use NewMultiTxDetails() instead +// Since we already use MultitransactionIDType in DB, and its default value is 0 (Send) +// this type is used to with default value 0 to represent invalid type to avoid bugs +// when devs forget to call NewMultiTxDetails() +type MultiTransactionDBType MultiTransactionType + +const ( + MultiTransactionDBTypeInvalid = 0 + MultiTransactionDBSend = iota + MultiTransactionDBSwap + MultiTransactionDBBridge +) + +func mtDBTypeToMTType(mtDBType MultiTransactionDBType) MultiTransactionType { + if mtDBType == MultiTransactionDBTypeInvalid { + return MultiTransactionTypeInvalid + } + + return MultiTransactionType(mtDBType - 1) +} + type MultiTxDetails struct { + IDs []wallet_common.MultiTransactionIDType AnyAddress common.Address FromAddress common.Address ToAddress common.Address ToChainID uint64 CrossTxID string - Type MultiTransactionType + Type MultiTransactionDBType } func NewMultiTxDetails() *MultiTxDetails { - details := &MultiTxDetails{} - details.Type = MultiTransactionTypeInvalid - return details + return &MultiTxDetails{} } type MultiTransactionDB struct { @@ -65,34 +83,7 @@ func (mtDB *MultiTransactionDB) CreateMultiTransaction(multiTransaction *MultiTr return err } -func (mtDB *MultiTransactionDB) ReadMultiTransactions(ids []wallet_common.MultiTransactionIDType) ([]*MultiTransaction, error) { - placeholders := make([]string, len(ids)) - args := make([]interface{}, len(ids)) - for i, v := range ids { - placeholders[i] = "?" - args[i] = v - } - - stmt, err := mtDB.db.Prepare(fmt.Sprintf(`SELECT %s - FROM multi_transactions - WHERE id in (%s)`, - selectMultiTransactionColumns, - strings.Join(placeholders, ","))) - if err != nil { - return nil, err - } - defer stmt.Close() - - rows, err := stmt.Query(args...) - if err != nil { - return nil, err - } - defer rows.Close() - - return rowsToMultiTransactions(rows) -} - -func (mtDB *MultiTransactionDB) ReadMultiTransactionsByDetails(details *MultiTxDetails) ([]*MultiTransaction, error) { +func (mtDB *MultiTransactionDB) ReadMultiTransactions(details *MultiTxDetails) ([]*MultiTransaction, error) { if details == nil { return nil, fmt.Errorf("details is nil") } @@ -101,6 +92,14 @@ func (mtDB *MultiTransactionDB) ReadMultiTransactionsByDetails(details *MultiTxD args := []interface{}{} + if len(details.IDs) > 0 { + placeholders := make([]string, len(details.IDs)) + for i, v := range details.IDs { + placeholders[i] = "?" + args = append(args, v) + } + whereClause += fmt.Sprintf("id in (%s) AND ", strings.Join(placeholders, ",")) + } if (details.AnyAddress != common.Address{}) { whereClause += "(from_address=? OR to_address=?) AND " args = append(args, details.AnyAddress, details.AnyAddress) @@ -121,9 +120,9 @@ func (mtDB *MultiTransactionDB) ReadMultiTransactionsByDetails(details *MultiTxD whereClause += "cross_tx_id=? AND " args = append(args, details.CrossTxID) } - if details.Type != MultiTransactionTypeInvalid { + if details.Type != MultiTransactionDBTypeInvalid { whereClause += "type=? AND " - args = append(args, details.Type) + args = append(args, mtDBTypeToMTType(details.Type)) } stmt, err := mtDB.db.Prepare(fmt.Sprintf(`SELECT %s diff --git a/services/wallet/transfer/multi_transaction_db_test.go b/services/wallet/transfer/multi_transaction_db_test.go index 090a39cd9..4cc1773af 100644 --- a/services/wallet/transfer/multi_transaction_db_test.go +++ b/services/wallet/transfer/multi_transaction_db_test.go @@ -32,7 +32,9 @@ func TestCreateMultiTransaction(t *testing.T) { require.NoError(t, err) // Add assertions here to verify the result of the CreateMultiTransaction method - mtx, err := mtDB.ReadMultiTransactions([]wallet_common.MultiTransactionIDType{multiTransaction.ID}) + details := NewMultiTxDetails() + details.IDs = []wallet_common.MultiTransactionIDType{multiTransaction.ID} + mtx, err := mtDB.ReadMultiTransactions(details) require.NoError(t, err) require.Len(t, mtx, 1) require.True(t, areMultiTransactionsEqual(&multiTransaction, mtx[0])) @@ -50,6 +52,9 @@ func TestReadMultiTransactions(t *testing.T) { tr3 := generateTestTransfer(3) mt3 := GenerateTestSwapMultiTransaction(tr3, "SNT", 100) + require.NotEqual(t, mt1.ID, mt2.ID) + require.NotEqual(t, mt1.ID, mt3.ID) + err := mtDB.CreateMultiTransaction(&mt1) require.NoError(t, err) err = mtDB.CreateMultiTransaction(&mt2) @@ -58,8 +63,9 @@ func TestReadMultiTransactions(t *testing.T) { require.NoError(t, err) // Read multi transactions - ids := []wallet_common.MultiTransactionIDType{mt1.ID, mt2.ID, mt3.ID} - mtx, err := mtDB.ReadMultiTransactions(ids) + details := NewMultiTxDetails() + details.IDs = []wallet_common.MultiTransactionIDType{mt1.ID, mt2.ID, mt3.ID} + mtx, err := mtDB.ReadMultiTransactions(details) require.NoError(t, err) require.Len(t, mtx, 3) require.True(t, areMultiTransactionsEqual(&mt1, mtx[0])) @@ -96,7 +102,9 @@ func TestUpdateMultiTransaction(t *testing.T) { require.NoError(t, err) // Read the updated multi transaction - mtx, err := mtDB.ReadMultiTransactions([]wallet_common.MultiTransactionIDType{multiTransaction.ID}) + details := NewMultiTxDetails() + details.IDs = []wallet_common.MultiTransactionIDType{multiTransaction.ID} + mtx, err := mtDB.ReadMultiTransactions(details) require.NoError(t, err) require.Len(t, mtx, 1) require.True(t, areMultiTransactionsEqual(&multiTransaction, mtx[0])) @@ -118,7 +126,8 @@ func TestDeleteMultiTransaction(t *testing.T) { require.NoError(t, err) // Read the deleted multi transaction - mtx, err := mtDB.ReadMultiTransactions([]wallet_common.MultiTransactionIDType{multiTransaction.ID}) + mtx, err := mtDB.ReadMultiTransactions(&MultiTxDetails{ + IDs: []wallet_common.MultiTransactionIDType{multiTransaction.ID}}) require.NoError(t, err) require.Len(t, mtx, 0) } diff --git a/services/wallet/transfer/testutils.go b/services/wallet/transfer/testutils.go index 834916d4a..d8349e0ae 100644 --- a/services/wallet/transfer/testutils.go +++ b/services/wallet/transfer/testutils.go @@ -37,6 +37,11 @@ type TestTransfer struct { Token *token.Token } +type TestCollectibleTransfer struct { + TestTransfer + TestCollectible +} + func SeedToToken(seed int) *token.Token { tokenIndex := seed % len(TestTokens) return TestTokens[tokenIndex] @@ -79,6 +84,28 @@ func generateTestTransfer(seed int) TestTransfer { } } +// Will be used in tests to generate a collectible transfer +// nolint:unused +func generateTestCollectibleTransfer(seed int) TestCollectibleTransfer { + collectibleIndex := seed % len(TestCollectibles) + collectible := TestCollectibles[collectibleIndex] + tr := TestCollectibleTransfer{ + TestTransfer: TestTransfer{ + TestTransaction: generateTestTransaction(seed), + To: eth_common.HexToAddress(fmt.Sprintf("0x3%d", seed)), + Value: int64(seed), + Token: &token.Token{ + Address: collectible.TokenAddress, + Name: "Collectible", + ChainID: uint64(collectible.ChainID), + }, + }, + TestCollectible: collectible, + } + tr.TestTransaction.ChainID = collectible.ChainID + return tr +} + func GenerateTestSendMultiTransaction(tr TestTransfer) MultiTransaction { return MultiTransaction{ ID: multiTransactionIDGenerator(), @@ -422,21 +449,13 @@ func (s *InMemMultiTransactionStorage) DeleteMultiTransaction(id common.MultiTra return nil } -func (s *InMemMultiTransactionStorage) ReadMultiTransactions(ids []common.MultiTransactionIDType) ([]*MultiTransaction, error) { - var multiTxs []*MultiTransaction - for _, id := range ids { - multiTx, ok := s.storage[id] - if !ok { - continue - } - multiTxs = append(multiTxs, multiTx) - } - return multiTxs, nil -} - -func (s *InMemMultiTransactionStorage) ReadMultiTransactionsByDetails(details *MultiTxDetails) ([]*MultiTransaction, error) { +func (s *InMemMultiTransactionStorage) ReadMultiTransactions(details *MultiTxDetails) ([]*MultiTransaction, error) { var multiTxs []*MultiTransaction for _, multiTx := range s.storage { + if len(details.IDs) > 0 && !testutils.SliceContains(details.IDs, multiTx.ID) { + continue + } + if (details.AnyAddress != eth_common.Address{}) && (multiTx.FromAddress != details.AnyAddress && multiTx.ToAddress != details.AnyAddress) { continue @@ -454,7 +473,7 @@ func (s *InMemMultiTransactionStorage) ReadMultiTransactionsByDetails(details *M continue } - if details.Type != MultiTransactionTypeInvalid && multiTx.Type != details.Type { + if details.Type != MultiTransactionDBTypeInvalid && multiTx.Type != mtDBTypeToMTType(details.Type) { continue } diff --git a/services/wallet/transfer/transaction_manager.go b/services/wallet/transfer/transaction_manager.go index a0c2a6881..1c21800ed 100644 --- a/services/wallet/transfer/transaction_manager.go +++ b/services/wallet/transfer/transaction_manager.go @@ -36,7 +36,7 @@ type TransactionDescription struct { type TransactionManager struct { storage MultiTransactionStorage gethManager *account.GethManager - transactor *transactions.Transactor + transactor transactions.TransactorIface config *params.NodeConfig accountsDB *accounts.Database pendingTracker *transactions.PendingTxTracker @@ -49,8 +49,7 @@ type TransactionManager struct { type MultiTransactionStorage interface { CreateMultiTransaction(tx *MultiTransaction) error - ReadMultiTransactions(ids []wallet_common.MultiTransactionIDType) ([]*MultiTransaction, error) - ReadMultiTransactionsByDetails(details *MultiTxDetails) ([]*MultiTransaction, error) + ReadMultiTransactions(details *MultiTxDetails) ([]*MultiTransaction, error) UpdateMultiTransaction(tx *MultiTransaction) error DeleteMultiTransaction(id wallet_common.MultiTransactionIDType) error } @@ -58,7 +57,7 @@ type MultiTransactionStorage interface { func NewTransactionManager( storage MultiTransactionStorage, gethManager *account.GethManager, - transactor *transactions.Transactor, + transactor transactions.TransactorIface, config *params.NodeConfig, accountsDB *accounts.Database, pendingTxManager *transactions.PendingTxTracker, diff --git a/services/wallet/transfer/transaction_manager_multitransaction.go b/services/wallet/transfer/transaction_manager_multitransaction.go index c888e22d1..a40b04c04 100644 --- a/services/wallet/transfer/transaction_manager_multitransaction.go +++ b/services/wallet/transfer/transaction_manager_multitransaction.go @@ -23,7 +23,7 @@ func (tm *TransactionManager) UpdateMultiTransaction(multiTransaction *MultiTran return tm.storage.UpdateMultiTransaction(multiTransaction) } -func (tm *TransactionManager) CreateMultiTransactionFromCommand(ctx context.Context, command *MultiTransactionCommand, +func (tm *TransactionManager) CreateMultiTransactionFromCommand(command *MultiTransactionCommand, data []*bridge.TransactionBridge) (*MultiTransaction, error) { multiTransaction := multiTransactionFromCommand(command) @@ -107,7 +107,7 @@ func (tm *TransactionManager) ProceedWithTransactionsSignatures(ctx context.Cont } func (tm *TransactionManager) GetMultiTransactions(ctx context.Context, ids []wallet_common.MultiTransactionIDType) ([]*MultiTransaction, error) { - return tm.storage.ReadMultiTransactions(ids) + return tm.storage.ReadMultiTransactions(&MultiTxDetails{IDs: ids}) } func (tm *TransactionManager) GetBridgeOriginMultiTransaction(ctx context.Context, toChainID uint64, crossTxID string) (*MultiTransaction, error) { @@ -115,7 +115,7 @@ func (tm *TransactionManager) GetBridgeOriginMultiTransaction(ctx context.Contex details.ToChainID = toChainID details.CrossTxID = crossTxID - multiTxs, err := tm.storage.ReadMultiTransactionsByDetails(details) + multiTxs, err := tm.storage.ReadMultiTransactions(details) if err != nil { return nil, err } @@ -135,7 +135,7 @@ func (tm *TransactionManager) GetBridgeDestinationMultiTransaction(ctx context.C details.ToChainID = toChainID details.CrossTxID = crossTxID - multiTxs, err := tm.storage.ReadMultiTransactionsByDetails(details) + multiTxs, err := tm.storage.ReadMultiTransactions(details) if err != nil { return nil, err } diff --git a/services/wallet/transfer/transaction_manager_multitransaction_test.go b/services/wallet/transfer/transaction_manager_multitransaction_test.go new file mode 100644 index 000000000..b542150bc --- /dev/null +++ b/services/wallet/transfer/transaction_manager_multitransaction_test.go @@ -0,0 +1,166 @@ +package transfer + +import ( + "context" + "math/big" + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/status-im/status-go/account" + "github.com/status-im/status-go/eth-node/types" + "github.com/status-im/status-go/rpc" + "github.com/status-im/status-go/services/wallet/bridge" + "github.com/status-im/status-go/services/wallet/bridge/mock_bridge" + "github.com/status-im/status-go/transactions" + "github.com/status-im/status-go/transactions/mock_transactor" +) + +func deepCopy(tx *transactions.SendTxArgs) *transactions.SendTxArgs { + return &transactions.SendTxArgs{ + From: tx.From, + To: tx.To, + Value: tx.Value, + Data: tx.Data, + } +} + +func deepCopyTransactionBridgeWithTransferTx(tx *bridge.TransactionBridge) *bridge.TransactionBridge { + return &bridge.TransactionBridge{ + BridgeName: tx.BridgeName, + ChainID: tx.ChainID, + TransferTx: deepCopy(tx.TransferTx), + HopTx: tx.HopTx, + CbridgeTx: tx.CbridgeTx, + ERC721TransferTx: tx.ERC721TransferTx, + ERC1155TransferTx: tx.ERC1155TransferTx, + SwapTx: tx.SwapTx, + } +} + +func setupTransactionManager(t *testing.T) (*TransactionManager, *mock_transactor.MockTransactorIface, *gomock.Controller) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // Create a mock transactor + transactor := mock_transactor.NewMockTransactorIface(ctrl) + // Create a new instance of the TransactionManager + tm := NewTransactionManager(NewInMemMultiTransactionStorage(), nil, transactor, nil, nil, nil, nil) + + return tm, transactor, ctrl +} + +func setupAccount(_ *testing.T, address common.Address) *account.SelectedExtKey { + // Dummy account + return &account.SelectedExtKey{ + Address: types.Address(address), + AccountKey: &types.Key{}, + } +} + +func setupTransactionData(_ *testing.T, transactor transactions.TransactorIface) (*MultiTransaction, []*bridge.TransactionBridge, map[string]bridge.Bridge, []*bridge.TransactionBridge) { + SetMultiTransactionIDGenerator(StaticIDCounter()) + + // Create mock data for the test + ethTransfer := generateTestTransfer(0) + multiTransaction := GenerateTestSendMultiTransaction(ethTransfer) + + // Initialize the bridges + var rpcClient *rpc.Client = nil + bridges := make(map[string]bridge.Bridge) + transferBridge := bridge.NewTransferBridge(rpcClient, transactor) + bridges[transferBridge.Name()] = transferBridge + + data := []*bridge.TransactionBridge{ + { + ChainID: 1, + BridgeName: transferBridge.Name(), + TransferTx: &transactions.SendTxArgs{ + From: types.Address(ethTransfer.From), + To: (*types.Address)(ðTransfer.To), + Value: (*hexutil.Big)(big.NewInt(ethTransfer.Value / 3)), + Data: types.HexBytes("0x0"), + // Symbol: multiTransaction.FromAsset, // This will be set by transaction manager + // MultiTransactionID: multiTransaction.ID, // This will be set by transaction manager + }, + }, + { + ChainID: 420, + BridgeName: transferBridge.Name(), + TransferTx: &transactions.SendTxArgs{ + From: types.Address(ethTransfer.From), + To: (*types.Address)(ðTransfer.To), + Value: (*hexutil.Big)(big.NewInt(ethTransfer.Value * 2 / 3)), + Data: types.HexBytes("0x0"), + // Symbol: multiTransaction.FromAsset, // This will be set by transaction manager + // MultiTransactionID: multiTransaction.ID, // This will be set by transaction manager + }, + }, + } + + expectedData := make([]*bridge.TransactionBridge, 0) + for _, tx := range data { + txCopy := deepCopyTransactionBridgeWithTransferTx(tx) + updateDataFromMultiTx([]*bridge.TransactionBridge{txCopy}, &multiTransaction) + expectedData = append(expectedData, txCopy) + } + + return &multiTransaction, data, bridges, expectedData +} + +func TestSendTransactionsETHSuccess(t *testing.T) { + tm, transactor, _ := setupTransactionManager(t) + account := setupAccount(t, common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678")) + multiTransaction, data, bridges, expectedData := setupTransactionData(t, transactor) + + // Verify that the SendTransactionWithChainID method is called for each transaction with proper arguments + // Return values are not checked, because they must be checked in Transactor tests + for _, tx := range expectedData { + transactor.EXPECT().SendTransactionWithChainID(tx.ChainID, *(tx.TransferTx), account).Return(types.Hash{}, nil) + } + + // Call the SendTransactions method + _, err := tm.SendTransactions(context.Background(), multiTransaction, data, bridges, account) + require.NoError(t, err) +} + +func TestSendTransactionsETHFailOnBridge(t *testing.T) { + tm, transactor, ctrl := setupTransactionManager(t) + account := setupAccount(t, common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678")) + multiTransaction, data, _, _ := setupTransactionData(t, transactor) + + // Initialize the bridges + bridges := make(map[string]bridge.Bridge) + transferBridge := mock_bridge.NewMockBridge(ctrl) + + // Set bridge name for the mock to the one used in data + transferBridge.EXPECT().Name().Return(data[0].BridgeName).AnyTimes() + bridges[transferBridge.Name()] = transferBridge + + expectedErr := transactions.ErrInvalidTxSender // Any error to verify + // In case of bridge error, verify that the error is returned + transferBridge.EXPECT().Send(gomock.Any(), gomock.Any()).Return(types.Hash{}, transactions.ErrInvalidTxSender) + + // Call the SendTransactions method + _, err := tm.SendTransactions(context.Background(), multiTransaction, data, bridges, account) + require.Error(t, expectedErr, err) +} + +func TestSendTransactionsETHFailOnTransactor(t *testing.T) { + tm, transactor, _ := setupTransactionManager(t) + account := setupAccount(t, common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678")) + multiTransaction, data, bridges, expectedData := setupTransactionData(t, transactor) + + // Verify that the SendTransactionWithChainID method is called for each transaction with proper arguments + // Return values are not checked, because they must be checked in Transactor tests. Only error propagation matters here + expectedErr := transactions.ErrInvalidTxSender // Any error to verify + transactor.EXPECT().SendTransactionWithChainID(expectedData[0].ChainID, *(expectedData[0].TransferTx), account).Return(types.Hash{}, nil) + transactor.EXPECT().SendTransactionWithChainID(expectedData[1].ChainID, *(expectedData[1].TransferTx), account).Return(types.Hash{}, expectedErr) + + // Call the SendTransactions method + _, err := tm.SendTransactions(context.Background(), multiTransaction, data, bridges, account) + require.Error(t, expectedErr, err) +} diff --git a/transactions/mock_transactor/README.md b/transactions/mock_transactor/README.md new file mode 100644 index 000000000..80e4bc2b6 --- /dev/null +++ b/transactions/mock_transactor/README.md @@ -0,0 +1,2 @@ +# To generate mocks, from status-go root directory: +mockgen -source=transactions/transactor.go -destination=transactions/mock_transactor/transactor.go -package=mock_transactor diff --git a/transactions/mock_transactor/transactor.go b/transactions/mock_transactor/transactor.go new file mode 100644 index 000000000..3ad68e6c6 --- /dev/null +++ b/transactions/mock_transactor/transactor.go @@ -0,0 +1,177 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: transactions/transactor.go + +// Package mock_transactor is a generated GoMock package. +package mock_transactor + +import ( + big "math/big" + reflect "reflect" + + common "github.com/ethereum/go-ethereum/common" + types "github.com/ethereum/go-ethereum/core/types" + gomock "github.com/golang/mock/gomock" + account "github.com/status-im/status-go/account" + types0 "github.com/status-im/status-go/eth-node/types" + params "github.com/status-im/status-go/params" + rpc "github.com/status-im/status-go/rpc" + common0 "github.com/status-im/status-go/services/wallet/common" + transactions "github.com/status-im/status-go/transactions" +) + +// MockTransactorIface is a mock of TransactorIface interface. +type MockTransactorIface struct { + ctrl *gomock.Controller + recorder *MockTransactorIfaceMockRecorder +} + +// MockTransactorIfaceMockRecorder is the mock recorder for MockTransactorIface. +type MockTransactorIfaceMockRecorder struct { + mock *MockTransactorIface +} + +// NewMockTransactorIface creates a new mock instance. +func NewMockTransactorIface(ctrl *gomock.Controller) *MockTransactorIface { + mock := &MockTransactorIface{ctrl: ctrl} + mock.recorder = &MockTransactorIfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockTransactorIface) EXPECT() *MockTransactorIfaceMockRecorder { + return m.recorder +} + +// AddSignatureToTransaction mocks base method. +func (m *MockTransactorIface) AddSignatureToTransaction(chainID uint64, tx *types.Transaction, sig []byte) (*types.Transaction, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddSignatureToTransaction", chainID, tx, sig) + ret0, _ := ret[0].(*types.Transaction) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AddSignatureToTransaction indicates an expected call of AddSignatureToTransaction. +func (mr *MockTransactorIfaceMockRecorder) AddSignatureToTransaction(chainID, tx, sig interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddSignatureToTransaction", reflect.TypeOf((*MockTransactorIface)(nil).AddSignatureToTransaction), chainID, tx, sig) +} + +// BuildTransactionWithSignature mocks base method. +func (m *MockTransactorIface) BuildTransactionWithSignature(chainID uint64, args transactions.SendTxArgs, sig []byte) (*types.Transaction, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BuildTransactionWithSignature", chainID, args, sig) + ret0, _ := ret[0].(*types.Transaction) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// BuildTransactionWithSignature indicates an expected call of BuildTransactionWithSignature. +func (mr *MockTransactorIfaceMockRecorder) BuildTransactionWithSignature(chainID, args, sig interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildTransactionWithSignature", reflect.TypeOf((*MockTransactorIface)(nil).BuildTransactionWithSignature), chainID, args, sig) +} + +// EstimateGas mocks base method. +func (m *MockTransactorIface) EstimateGas(network *params.Network, from, to common.Address, value *big.Int, input []byte) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EstimateGas", network, from, to, value, input) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EstimateGas indicates an expected call of EstimateGas. +func (mr *MockTransactorIfaceMockRecorder) EstimateGas(network, from, to, value, input interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EstimateGas", reflect.TypeOf((*MockTransactorIface)(nil).EstimateGas), network, from, to, value, input) +} + +// NextNonce mocks base method. +func (m *MockTransactorIface) NextNonce(rpcClient *rpc.Client, chainID uint64, from types0.Address) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NextNonce", rpcClient, chainID, from) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NextNonce indicates an expected call of NextNonce. +func (mr *MockTransactorIfaceMockRecorder) NextNonce(rpcClient, chainID, from interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NextNonce", reflect.TypeOf((*MockTransactorIface)(nil).NextNonce), rpcClient, chainID, from) +} + +// SendRawTransaction mocks base method. +func (m *MockTransactorIface) SendRawTransaction(chainID uint64, rawTx string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendRawTransaction", chainID, rawTx) + ret0, _ := ret[0].(error) + return ret0 +} + +// SendRawTransaction indicates an expected call of SendRawTransaction. +func (mr *MockTransactorIfaceMockRecorder) SendRawTransaction(chainID, rawTx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendRawTransaction", reflect.TypeOf((*MockTransactorIface)(nil).SendRawTransaction), chainID, rawTx) +} + +// SendTransaction mocks base method. +func (m *MockTransactorIface) SendTransaction(sendArgs transactions.SendTxArgs, verifiedAccount *account.SelectedExtKey) (types0.Hash, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendTransaction", sendArgs, verifiedAccount) + ret0, _ := ret[0].(types0.Hash) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SendTransaction indicates an expected call of SendTransaction. +func (mr *MockTransactorIfaceMockRecorder) SendTransaction(sendArgs, verifiedAccount interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendTransaction", reflect.TypeOf((*MockTransactorIface)(nil).SendTransaction), sendArgs, verifiedAccount) +} + +// SendTransactionWithChainID mocks base method. +func (m *MockTransactorIface) SendTransactionWithChainID(chainID uint64, sendArgs transactions.SendTxArgs, verifiedAccount *account.SelectedExtKey) (types0.Hash, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendTransactionWithChainID", chainID, sendArgs, verifiedAccount) + ret0, _ := ret[0].(types0.Hash) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SendTransactionWithChainID indicates an expected call of SendTransactionWithChainID. +func (mr *MockTransactorIfaceMockRecorder) SendTransactionWithChainID(chainID, sendArgs, verifiedAccount interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendTransactionWithChainID", reflect.TypeOf((*MockTransactorIface)(nil).SendTransactionWithChainID), chainID, sendArgs, verifiedAccount) +} + +// SendTransactionWithSignature mocks base method. +func (m *MockTransactorIface) SendTransactionWithSignature(from common.Address, symbol string, multiTransactionID common0.MultiTransactionIDType, tx *types.Transaction) (types0.Hash, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendTransactionWithSignature", from, symbol, multiTransactionID, tx) + ret0, _ := ret[0].(types0.Hash) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SendTransactionWithSignature indicates an expected call of SendTransactionWithSignature. +func (mr *MockTransactorIfaceMockRecorder) SendTransactionWithSignature(from, symbol, multiTransactionID, tx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendTransactionWithSignature", reflect.TypeOf((*MockTransactorIface)(nil).SendTransactionWithSignature), from, symbol, multiTransactionID, tx) +} + +// ValidateAndBuildTransaction mocks base method. +func (m *MockTransactorIface) ValidateAndBuildTransaction(chainID uint64, sendArgs transactions.SendTxArgs) (*types.Transaction, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ValidateAndBuildTransaction", chainID, sendArgs) + ret0, _ := ret[0].(*types.Transaction) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ValidateAndBuildTransaction indicates an expected call of ValidateAndBuildTransaction. +func (mr *MockTransactorIfaceMockRecorder) ValidateAndBuildTransaction(chainID, sendArgs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateAndBuildTransaction", reflect.TypeOf((*MockTransactorIface)(nil).ValidateAndBuildTransaction), chainID, sendArgs) +} diff --git a/transactions/transactor.go b/transactions/transactor.go index f60a5973a..1d6770c17 100644 --- a/transactions/transactor.go +++ b/transactions/transactor.go @@ -44,6 +44,19 @@ func (e *ErrBadNonce) Error() string { return fmt.Sprintf("bad nonce. expected %d, got %d", e.expectedNonce, e.nonce) } +// Transactor is an interface that defines the methods for validating and sending transactions. +type TransactorIface interface { + NextNonce(rpcClient *rpc.Client, chainID uint64, from types.Address) (uint64, error) + EstimateGas(network *params.Network, from common.Address, to common.Address, value *big.Int, input []byte) (uint64, error) + SendTransaction(sendArgs SendTxArgs, verifiedAccount *account.SelectedExtKey) (hash types.Hash, err error) + SendTransactionWithChainID(chainID uint64, sendArgs SendTxArgs, verifiedAccount *account.SelectedExtKey) (hash types.Hash, err error) + ValidateAndBuildTransaction(chainID uint64, sendArgs SendTxArgs) (tx *gethtypes.Transaction, err error) + AddSignatureToTransaction(chainID uint64, tx *gethtypes.Transaction, sig []byte) (*gethtypes.Transaction, error) + SendRawTransaction(chainID uint64, rawTx string) error + BuildTransactionWithSignature(chainID uint64, args SendTxArgs, sig []byte) (*gethtypes.Transaction, error) + SendTransactionWithSignature(from common.Address, symbol string, multiTransactionID wallet_common.MultiTransactionIDType, tx *gethtypes.Transaction) (hash types.Hash, err error) +} + // Transactor validates, signs transactions. // It uses upstream to propagate transactions to the Ethereum network. type Transactor struct { @@ -218,29 +231,9 @@ func (t *Transactor) SendTransactionWithSignature(from common.Address, symbol st return t.sendTransaction(rpcWrapper, from, symbol, multiTransactionID, tx) } -func (t *Transactor) AddSignatureToTransactionAndSend(chainID uint64, from common.Address, symbol string, - multiTransactionID wallet_common.MultiTransactionIDType, tx *gethtypes.Transaction, sig []byte) (hash types.Hash, err error) { - txWithSignature, err := t.AddSignatureToTransaction(chainID, tx, sig) - if err != nil { - return hash, err - } - - return t.SendTransactionWithSignature(from, symbol, multiTransactionID, txWithSignature) -} - -// BuildTransactionAndSendWithSignature receive a transaction and a signature, serialize them together and propage it to the network. +// BuildTransactionAndSendWithSignature receive a transaction and a signature, serialize them together // It's different from eth_sendRawTransaction because it receives a signature and not a serialized transaction with signature. // Since the transactions is already signed, we assume it was validated and used the right nonce. -func (t *Transactor) BuildTransactionAndSendWithSignature(chainID uint64, args SendTxArgs, sig []byte) (hash types.Hash, err error) { - txWithSignature, err := t.BuildTransactionWithSignature(chainID, args, sig) - if err != nil { - return hash, err - } - - hash, err = t.SendTransactionWithSignature(common.Address(args.From), args.Symbol, args.MultiTransactionID, txWithSignature) - return hash, err -} - func (t *Transactor) BuildTransactionWithSignature(chainID uint64, args SendTxArgs, sig []byte) (*gethtypes.Transaction, error) { if !args.Valid() { return nil, ErrInvalidSendTxArgs diff --git a/transactions/transactor_test.go b/transactions/transactor_test.go index bcd6ecee2..b587aae94 100644 --- a/transactions/transactor_test.go +++ b/transactions/transactor_test.go @@ -313,11 +313,18 @@ func (s *TransactorSuite) TestSendTransactionWithSignature() { Return(common.Hash{}, nil) } - _, err = s.manager.BuildTransactionAndSendWithSignature(s.nodeConfig.NetworkID, args, sig) + tx, err = s.manager.BuildTransactionWithSignature(s.nodeConfig.NetworkID, args, sig) if scenario.expectError { s.Error(err) } else { s.NoError(err) + + _, err = s.manager.SendTransactionWithSignature(common.Address(args.From), args.Symbol, args.MultiTransactionID, tx) + if scenario.expectError { + s.Error(err) + } else { + s.NoError(err) + } } }) } @@ -325,7 +332,7 @@ func (s *TransactorSuite) TestSendTransactionWithSignature() { func (s *TransactorSuite) TestSendTransactionWithSignature_InvalidSignature() { args := SendTxArgs{} - _, err := s.manager.BuildTransactionAndSendWithSignature(1, args, []byte{}) + _, err := s.manager.BuildTransactionWithSignature(1, args, []byte{}) s.Equal(ErrInvalidSignatureSize, err) }