From 1d0643e9262266a67728990dacf80a18a04e8269 Mon Sep 17 00:00:00 2001 From: Stefan Date: Tue, 2 Apr 2024 16:27:21 +0300 Subject: [PATCH] tests(integration) add integration test to debug MTID transfer Updates #14071 --- test/status-go/integration/go.mod | 2 +- test/status-go/integration/go.sum | 4 +- test/status-go/integration/helpers/helpers.go | 37 ++-- .../wallet/activityfiltering_test.go | 15 +- test/status-go/integration/wallet/common.go | 13 +- .../wallet/pendingtransactions_test.go | 29 +-- .../wallet/sendtransactions_test.go | 174 +++++++++++++++++- 7 files changed, 225 insertions(+), 49 deletions(-) diff --git a/test/status-go/integration/go.mod b/test/status-go/integration/go.mod index f386e7605d..92ccffb834 100644 --- a/test/status-go/integration/go.mod +++ b/test/status-go/integration/go.mod @@ -225,7 +225,7 @@ require ( github.com/status-im/doubleratchet v3.0.0+incompatible // indirect github.com/status-im/go-multiaddr-ethv4 v1.2.5 // indirect github.com/status-im/keycard-go v0.0.0-20200402102358-957c09536969 // indirect - github.com/status-im/markdown v0.0.0-20231114210825-6c2d15b5dc57 // indirect + github.com/status-im/markdown v0.0.0-20240404192634-b7e33c6ac3d4 // indirect github.com/status-im/migrate/v4 v4.6.2-status.3 // indirect github.com/status-im/mvds v0.0.27-0.20240111144448-92d364e4be82 // indirect github.com/status-im/rendezvous v1.3.7 // indirect diff --git a/test/status-go/integration/go.sum b/test/status-go/integration/go.sum index d595f9c14e..5af2f7b480 100644 --- a/test/status-go/integration/go.sum +++ b/test/status-go/integration/go.sum @@ -1992,8 +1992,8 @@ github.com/status-im/gomoji v1.1.3-0.20220213022530-e5ac4a8732d4/go.mod h1:hmpnZ github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= github.com/status-im/keycard-go v0.0.0-20200402102358-957c09536969 h1:Oo2KZNP70KE0+IUJSidPj/BFS/RXNHmKIJOdckzml2E= github.com/status-im/keycard-go v0.0.0-20200402102358-957c09536969/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= -github.com/status-im/markdown v0.0.0-20231114210825-6c2d15b5dc57 h1:AuJFXERIFVzUjf9rrTb8vamFubB6Ks/e8aUasDr4pOM= -github.com/status-im/markdown v0.0.0-20231114210825-6c2d15b5dc57/go.mod h1:5rjPyv3KffPNVbFjnsVy0NGj9+JeW40WvXLdxH1VKuE= +github.com/status-im/markdown v0.0.0-20240404192634-b7e33c6ac3d4 h1:KBeXtOoisXjiSCkNVmZcq15kK4NmSi4p1hyZeUcT5dY= +github.com/status-im/markdown v0.0.0-20240404192634-b7e33c6ac3d4/go.mod h1:5rjPyv3KffPNVbFjnsVy0NGj9+JeW40WvXLdxH1VKuE= github.com/status-im/migrate/v4 v4.6.2-status.3 h1:Khwjb59NzniloUr5i9s9AtkEyqBbQFt1lkoAu66sAu0= github.com/status-im/migrate/v4 v4.6.2-status.3/go.mod h1:c/kc90n47GZu/58nnz1OMLTf7uE4Da4gZP5qmU+A/v8= github.com/status-im/mvds v0.0.27-0.20240111144448-92d364e4be82 h1:A7jtwOlDMUGUPx5tTwUPewMoIprvo9oN9kU0H1CP+FQ= diff --git a/test/status-go/integration/helpers/helpers.go b/test/status-go/integration/helpers/helpers.go index 8bb6a48658..1055927b35 100644 --- a/test/status-go/integration/helpers/helpers.go +++ b/test/status-go/integration/helpers/helpers.go @@ -230,21 +230,14 @@ func WaitForWalletEventGetPayload[T any](eventQueue chan GoEvent, eventName wall return newPayload, nil } -// WaitForTxDownloaderToFinishForAccountsCondition returns a state-full condition function that records every account that has been seen with the events until the entire list is seen +// WaitForTxDownloaderToFinishForAccountsCondition returns a stateful condition function that checks that at least on account that has been seen with the events until the entire list is seen. +// The loadBlocksAndTransfersCommand.fetchHistoryBlocksForAccount reports only for one account history ready, even though the downloaded history might contain other accounts. func WaitForTxDownloaderToFinishForAccountsCondition(t *testing.T, accounts []common.Address) func(walletEvent *walletevent.Event) bool { - accs := make([]common.Address, len(accounts)) - copy(accs, accounts) - return func(walletEvent *walletevent.Event) bool { - eventAccountsLoop: for _, acc := range walletEvent.Accounts { - for i, a := range accs { + for _, a := range accounts { if acc == a { - if len(accs) == 1 { - return true - } - accs = append(accs[:i], accs[i+1:]...) - continue eventAccountsLoop + return true } } } @@ -387,6 +380,28 @@ func CallPrivateMethodAndGetTWithTimeout[T any](method string, params []interfac return &res, nil } +func CallPrivateMethodAndGetSliceOfT[T any](method string, params []interface{}) ([]T, error) { + return CallPrivateMethodAndGetSliceOfTWithTimeout[T](method, params, 60*time.Second) +} + +func CallPrivateMethodAndGetSliceOfTWithTimeout[T any](method string, params []interface{}, timeout time.Duration) ([]T, error) { + resJson, err := CallPrivateMethodWithTimeout(method, params, timeout) + if err != nil { + return nil, err + } + + res := make([]T, 0) + rawJson, err := GetRPCAPIResponseRaw(resJson) + if err != nil { + return nil, err + } + + if err := json.Unmarshal(rawJson, &res); err != nil { + return nil, fmt.Errorf("failed to unmarshal data: %w", err) + } + + return res, nil +} type Config struct { HashedPassword string `json:"hashedPassword"` diff --git a/test/status-go/integration/wallet/activityfiltering_test.go b/test/status-go/integration/wallet/activityfiltering_test.go index 86cd1fa24d..7ef7f7077b 100644 --- a/test/status-go/integration/wallet/activityfiltering_test.go +++ b/test/status-go/integration/wallet/activityfiltering_test.go @@ -24,7 +24,8 @@ func TestActivityIncrementalUpdates_NoFilterNewPendingTransactions(t *testing.T) td, close := setupAccountsAndTransactions(t) defer close() - rawSessionID, err := helpers.CallPrivateMethodAndGetT[int32]("wallet_startActivityFilterSession", []interface{}{[]types.Address{td.operableAccounts[0].Address}, false, []common.ChainID{5}, activity.Filter{}, 3}) + chainID := common.OptimismSepolia + rawSessionID, err := helpers.CallPrivateMethodAndGetT[int32]("wallet_startActivityFilterSession", []interface{}{[]types.Address{td.operableAccounts[0].Address}, false, []common.ChainID{common.ChainID(chainID)}, activity.Filter{}, 3}) require.NoError(t, err) require.NotNil(t, rawSessionID) sessionID := activity.SessionID(*rawSessionID) @@ -36,7 +37,7 @@ func TestActivityIncrementalUpdates_NoFilterNewPendingTransactions(t *testing.T) require.Equal(t, 3, len(res.Activities)) // Trigger updating of activity results - sendTransaction(t, td) + sendTransaction(t, td, chainID) // Wait for EventActivitySessionUpdated signal triggered by the first EventPendingTransactionUpdate update, err := helpers.WaitForWalletEventGetPayload[activity.SessionUpdate](td.eventQueue, activity.EventActivitySessionUpdated, 60*time.Second) @@ -51,7 +52,7 @@ func TestActivityIncrementalUpdates_NoFilterNewPendingTransactions(t *testing.T) // require.True(t, *update.HasNewOnTop) // Start history download to cleanup pending transactions - _, err = helpers.CallPrivateMethod("wallet_checkRecentHistoryForChainIDs", []interface{}{[]uint64{5}, []types.Address{td.operableAccounts[0].Address, td.watchAccounts[0].Address}}) + _, err = helpers.CallPrivateMethod("wallet_checkRecentHistoryForChainIDs", []interface{}{[]uint64{chainID}, []types.Address{td.operableAccounts[0].Address, td.watchAccounts[0].Address}}) require.NoError(t, err) downloadDoneFn := helpers.WaitForTxDownloaderToFinishForAccountsCondition(t, []eth.Address{eth.Address(td.operableAccounts[0].Address), eth.Address(td.watchAccounts[0].Address)}) @@ -61,7 +62,7 @@ func TestActivityIncrementalUpdates_NoFilterNewPendingTransactions(t *testing.T) // It is expected that downloading will generate a EventPendingTransactionUpdate that in turn will generate a second EventActivitySessionUpdated signal marked by the update non nil value _, err = helpers.WaitForWalletEventsWithOptionals( td.eventQueue, - []walletevent.EventType{transfer.EventRecentHistoryReady}, + []walletevent.EventType{activity.EventActivitySessionUpdated}, 120*time.Second, func(e *walletevent.Event) bool { if e.Type == activity.EventActivitySessionUpdated { @@ -72,16 +73,16 @@ func TestActivityIncrementalUpdates_NoFilterNewPendingTransactions(t *testing.T) require.True(t, *update.HasNewOnTop) //require.NotNil(t, update.Removed) //require.True(t, *update.Removed) - return false + return true } else if e.Type == transfer.EventFetchingHistoryError { require.Fail(t, "History download failed") return false } else if downloadDoneFn(e) { - return true + return false } return false }, - []walletevent.EventType{activity.EventActivitySessionUpdated, transfer.EventFetchingHistoryError}, + []walletevent.EventType{transfer.EventFetchingHistoryError}, ) require.NoError(t, err) require.NotNil(t, update, "EventActivitySessionUpdated signal was triggered by the second EventPendingTransactionUpdate during history download") diff --git a/test/status-go/integration/wallet/common.go b/test/status-go/integration/wallet/common.go index 1c5bcd7c07..1251ccbafa 100644 --- a/test/status-go/integration/wallet/common.go +++ b/test/status-go/integration/wallet/common.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/ethereum/go-ethereum/common" + eth "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/status-im/status-desktop/test/status-go/integration/helpers" "github.com/status-im/status-go/multiaccounts/accounts" @@ -50,11 +50,12 @@ func setupAccountsAndTransactionsWithTimeout(t *testing.T, timeout time.Duration } } -// sendTransaction generates multi_transactions and pending entries then it creates and publishes a transaction -func sendTransaction(t *testing.T, td testUserData) { +// sendTransaction generates a Multi Transaction and Bridge entry then +// calls createMultiTransaction which creates a pending entry and publishes a transaction +func sendTransaction(t *testing.T, td testUserData, chainID uint64) { mTCommand := transfer.MultiTransactionCommand{ - FromAddress: common.Address(td.operableAccounts[0].Address), - ToAddress: common.Address(td.watchAccounts[0].Address), + FromAddress: eth.Address(td.operableAccounts[0].Address), + ToAddress: eth.Address(td.watchAccounts[0].Address), FromAsset: "ETH", ToAsset: "ETH", FromAmount: (*hexutil.Big)(new(big.Int).SetUint64(100000)), @@ -63,7 +64,7 @@ func sendTransaction(t *testing.T, td testUserData) { data := []*bridge.TransactionBridge{ { BridgeName: "Transfer", - ChainID: 5, + ChainID: chainID, TransferTx: &transactions.SendTxArgs{ From: td.operableAccounts[0].Address, To: &td.watchAccounts[0].Address, diff --git a/test/status-go/integration/wallet/pendingtransactions_test.go b/test/status-go/integration/wallet/pendingtransactions_test.go index a5b6da5f50..b04eb3c4df 100644 --- a/test/status-go/integration/wallet/pendingtransactions_test.go +++ b/test/status-go/integration/wallet/pendingtransactions_test.go @@ -7,13 +7,12 @@ import ( "testing" "time" - eth "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" "github.com/status-im/status-desktop/test/status-go/integration/helpers" "github.com/status-im/status-go/eth-node/types" - "github.com/status-im/status-go/services/wallet/transfer" + "github.com/status-im/status-go/services/wallet/common" "github.com/status-im/status-go/services/wallet/walletevent" "github.com/status-im/status-go/transactions" ) @@ -23,7 +22,8 @@ func TestPendingTx_NotificationStatus(t *testing.T) { td, close := setupAccountsAndTransactions(t) defer close() - sendTransaction(t, td) + chainID := common.OptimismSepolia + sendTransaction(t, td, chainID) // Wait for transaction to be included in block confirmationPayloads, err := helpers.WaitForWalletEventsGetMap( @@ -45,24 +45,13 @@ func TestPendingTx_NotificationStatus(t *testing.T) { } // Start history download ... - _, err = helpers.CallPrivateMethod("wallet_checkRecentHistoryForChainIDs", []interface{}{[]uint64{5}, []types.Address{td.operableAccounts[0].Address, td.watchAccounts[0].Address}}) + _, err = helpers.CallPrivateMethod("wallet_checkRecentHistoryForChainIDs", []interface{}{[]uint64{chainID}, []types.Address{td.operableAccounts[0].Address, td.watchAccounts[0].Address}}) require.NoError(t, err) - downloadDoneFn := helpers.WaitForTxDownloaderToFinishForAccountsCondition(t, []eth.Address{eth.Address(td.operableAccounts[0].Address), eth.Address(td.watchAccounts[0].Address)}) - - // ... and wait for the new transaction download to trigger deletion from pending_transactions - _, err = helpers.WaitForWalletEventsWithOptionals( - td.eventQueue, - []walletevent.EventType{transfer.EventRecentHistoryReady}, - 60*time.Second, - func(e *walletevent.Event) bool { - if e.Type == transfer.EventFetchingHistoryError { - require.Fail(t, "History download failed") - return false - } - return downloadDoneFn(e) - }, - []walletevent.EventType{transfer.EventFetchingHistoryError}, - ) + // Wait for transaction to be included in block + pendingUpdated, err := helpers.WaitForWalletEventGetPayload[transactions.PendingTxUpdatePayload]( + td.eventQueue, transactions.EventPendingTransactionUpdate, 60*time.Second) require.NoError(t, err) + + require.True(t, pendingUpdated.Deleted) } diff --git a/test/status-go/integration/wallet/sendtransactions_test.go b/test/status-go/integration/wallet/sendtransactions_test.go index fcd4bda346..090b1f12c5 100644 --- a/test/status-go/integration/wallet/sendtransactions_test.go +++ b/test/status-go/integration/wallet/sendtransactions_test.go @@ -5,16 +5,26 @@ package wallet import ( "math/big" + "strings" "testing" + "time" + "github.com/ethereum/go-ethereum/accounts/abi" eth "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/stretchr/testify/require" "github.com/status-im/status-desktop/test/status-go/integration/helpers" + "github.com/status-im/status-go/contracts/ierc20" + "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/services/wallet" + "github.com/status-im/status-go/services/wallet/activity" + "github.com/status-im/status-go/services/wallet/bridge" "github.com/status-im/status-go/services/wallet/common" + "github.com/status-im/status-go/services/wallet/transfer" + "github.com/status-im/status-go/services/wallet/walletevent" + "github.com/status-im/status-go/transactions" ) type dataPayload struct { @@ -22,7 +32,7 @@ type dataPayload struct { accountFrom eth.Address accountTo eth.Address amount *hexutil.Big - tokenIdentity string // Format: ":" + tokenIdentity string // Format: ":" or "" disabledFromChainIDs []uint64 disabledToChainIDs []uint64 preferredChainIDs []uint64 @@ -76,7 +86,7 @@ func erc1155Payload(tokenIdentity string) dataPayload { return customBasicPayload(wallet.ERC1155Transfer, tokenIdentity) } -func TestSendTransaction_Collectible(t *testing.T) { +func TestSendTransaction_Collectible_Routes(t *testing.T) { tests := []struct { name string @@ -98,3 +108,163 @@ func TestSendTransaction_Collectible(t *testing.T) { }) } } + +func gweiToWei(gwei *big.Float) *big.Int { + weiMultiplier := big.NewFloat(1e9 /*10^9*/) + weiValue := new(big.Float).Mul(gwei, weiMultiplier) + res, _ := weiValue.Int(nil) + return res +} + +// TestSendTransaction_Assets_KeepMTID is here to debug why the MTID is not kept +// when migrating a transaction from pending_transactions to transfers table +func TestSendTransaction_Assets_KeepMTID(t *testing.T) { + td, close := setupAccountsAndTransactions(t) + defer close() + + chainID := common.OptimismSepolia + amount, ok := new(big.Int).SetString("0x3e8", 0) + require.True(t, ok) + info := dataPayload{ + transferType: wallet.Transfer, + tokenIdentity: "USDC", + accountFrom: eth.HexToAddress("0xe2d622c817878da5143bbe06866ca8e35273ba8a"), + accountTo: eth.HexToAddress("0xbd54a96c0ae19a220c8e1234f54c940dfab34639"), + amount: (*hexutil.Big)(amount), + disabledFromChainIDs: []uint64{common.OptimismMainnet}, + disabledToChainIDs: []uint64{common.OptimismMainnet}, + preferredChainIDs: []uint64{chainID}, + gasFeeMode: wallet.GasFeeHigh, + fromLockedAmount: map[uint64]*hexutil.Big{}, + } + + payload := dataToCallingPayload(info) + res, err := helpers.CallPrivateMethodAndGetT[wallet.SuggestedRoutes]("wallet_getSuggestedRoutes", payload) + require.NoError(t, err) + require.Greater(t, len(res.Candidates), 0) + + suggested := res.Candidates[0] + + mtCmd := transfer.MultiTransactionCommand{ + FromAddress: info.accountFrom, + ToAddress: info.accountTo, + FromAsset: info.tokenIdentity, + ToAsset: info.tokenIdentity, + FromAmount: info.amount, + Type: transfer.MultiTransactionSend, + } + + abi, err := abi.JSON(strings.NewReader(ierc20.IERC20ABI)) + require.NoError(t, err) + input, err := abi.Pack("transfer", + info.accountTo, + info.amount.ToInt(), + ) + require.NoError(t, err) + + txArgs := transactions.SendTxArgs{ + From: types.Address(info.accountFrom), + To: common.NewAndSet(types.HexToAddress("0x5fd84259d66cd46123540766be93dfe6d43130d7")), + Gas: common.NewAndSet(hexutil.Uint64(suggested.GasAmount)), + GasPrice: (*hexutil.Big)(gweiToWei(suggested.GasFees.GasPrice)), + Value: (*hexutil.Big)(big.NewInt(0)), + MaxFeePerGas: (*hexutil.Big)(gweiToWei(suggested.GasFees.MaxFeePerGasHigh)), + MaxPriorityFeePerGas: (*hexutil.Big)(gweiToWei(suggested.GasFees.MaxPriorityFeePerGas)), + MultiTransactionID: common.NoMultiTransactionID, + Symbol: info.tokenIdentity, + Data: types.HexBytes(input), + } + + bridge := []*bridge.TransactionBridge{{ + BridgeName: suggested.BridgeName, + ChainID: chainID, + TransferTx: &txArgs, + }} + + sendPayload := []interface{}{mtCmd, bridge, td.hashedPassword} + + // Simulate the transaction creation as a result to SendModal user actions for an asset + sendRes, err := helpers.CallPrivateMethodAndGetT[transfer.MultiTransactionCommandResult]("wallet_createMultiTransaction", sendPayload) + require.NoError(t, err) + require.NotNil(t, sendRes) + require.Greater(t, len(sendRes.Hashes), 0) + + // Wait for transaction to be included in block + _, err = helpers.WaitForWalletEventsGetMap( + td.eventQueue, []walletevent.EventType{ + transactions.EventPendingTransactionUpdate, + transactions.EventPendingTransactionStatusChanged, + }, + 20*time.Second, + ) + require.NoError(t, err) + + // Start history download + _, err = helpers.CallPrivateMethod("wallet_checkRecentHistoryForChainIDs", []interface{}{[]uint64{chainID}, []types.Address{types.Address(info.accountFrom), types.Address(info.accountTo)}}) + require.NoError(t, err) + + newHash := eth.Hash(sendRes.Hashes[chainID][0]) + // Wait for transaction to be deleted by downloader and also ensure we receive notification that transaction entries + // stored in transfers for the sender account + _, err = helpers.WaitForWalletEventsWithOptionals( + td.eventQueue, + []walletevent.EventType{transactions.EventPendingTransactionUpdate, transfer.EventNewTransfers}, + 60*time.Second, + func(e *walletevent.Event) bool { + if e.Type == transactions.EventPendingTransactionUpdate { + update, err := walletevent.GetPayload[transactions.PendingTxUpdatePayload](*e) + require.NoError(t, err) + + return update.Deleted && update.TxIdentity.Hash == newHash + } else if e.Type == transfer.EventNewTransfers { + for _, acc := range e.Accounts { + if acc == info.accountFrom { + return true + } + } + } else if e.Type == transfer.EventFetchingHistoryError { + require.Fail(t, "History download failed") + return true + } + return false + }, + []walletevent.EventType{transfer.EventFetchingHistoryError}, + ) + require.NoError(t, err) + + // Ensure the transaction is not pending anymore + pendings, err := helpers.CallPrivateMethodAndGetSliceOfT[transactions.PendingTransaction]("wallet_getPendingTransactions", []interface{}{}) + require.NoError(t, err) + require.Len(t, pendings, 0) + + _, err = helpers.CallPrivateMethodAndGetT[activity.SessionID]("wallet_startActivityFilterSession", []interface{}{ + []eth.Address{info.accountFrom}, + false, /* allAddresses */ + []common.ChainID{common.ChainID(chainID)}, + activity.Filter{}, + 5, + }) + require.NoError(t, err) + + newMTID := sendRes.ID + // Confirm filtering results have a MT with expected MTID as last entry and the next one is not pending as reported + // by #14071 issue + resMap, err := helpers.WaitForWalletEventsGetMap(td.eventQueue, []walletevent.EventType{activity.EventActivityFilteringDone}, 5*time.Second) + require.NoError(t, err) + require.Len(t, resMap, 1) + require.Equal(t, resMap[0].EventName, activity.EventActivityFilteringDone) + activityRes := resMap[0].JsonData + require.Equal(t, activity.ErrorCodeSuccess, activity.ErrorCode(int(activityRes["errorCode"].(float64)))) + activities := activityRes["activities"].([](interface{})) + require.Greater(t, len(activities), 1) + lastEntry := activities[0].(map[string]interface{}) + require.Equal(t, activity.MultiTransactionPT, activity.PayloadType(int(lastEntry["payloadType"].(float64)))) + require.Equal(t, newMTID, int64(lastEntry["id"].(float64))) + if len(activities) > 1 { + secondEntry := activities[1].(map[string]interface{}) + require.NotEqual(t, activity.PendingTransactionPT, activity.PayloadType(int(secondEntry["payloadType"].(float64)))) + if tr, ok := secondEntry["transaction"]; ok { + require.NotEqual(t, newHash, tr.(map[string]interface{})["hash"].(eth.Hash)) + } + } +}