From 7b3e25ee5c9c14ed9897ed96d57b88a0dbcfc1b6 Mon Sep 17 00:00:00 2001 From: Albina Nikiforova Date: Mon, 30 Sep 2024 21:25:36 +0100 Subject: [PATCH] test(suite): filterReceiveAccounts --- .../form/useCoinmarketVerifyAccount.tsx | 38 ++---- suite-common/wallet-config/src/utils.ts | 16 --- .../__tests__/filterReceiveAccounts.test.ts | 128 ++++++++++++++++++ .../wallet-utils/src/filterReceiveAccounts.ts | 51 +++++++ suite-common/wallet-utils/src/index.ts | 1 + 5 files changed, 190 insertions(+), 44 deletions(-) create mode 100644 suite-common/wallet-utils/src/__tests__/filterReceiveAccounts.test.ts create mode 100644 suite-common/wallet-utils/src/filterReceiveAccounts.ts diff --git a/packages/suite/src/hooks/wallet/coinmarket/form/useCoinmarketVerifyAccount.tsx b/packages/suite/src/hooks/wallet/coinmarket/form/useCoinmarketVerifyAccount.tsx index 133586265bc..92939c67b54 100644 --- a/packages/suite/src/hooks/wallet/coinmarket/form/useCoinmarketVerifyAccount.tsx +++ b/packages/suite/src/hooks/wallet/coinmarket/form/useCoinmarketVerifyAccount.tsx @@ -1,4 +1,4 @@ -import { isDebugOnlyAccountType, Network, networksCollection } from '@suite-common/wallet-config'; +import { Network, networksCollection } from '@suite-common/wallet-config'; import { selectDevice } from '@suite-common/wallet-core'; import { useEffect, useMemo, useState } from 'react'; import { useForm } from 'react-hook-form'; @@ -22,6 +22,7 @@ import { } from 'src/types/coinmarket/coinmarketVerify'; import { useAccountAddressDictionary } from 'src/hooks/wallet/useAccounts'; import { TrezorDevice } from '@suite-common/suite-types'; +import { filterReceiveAccounts } from '@suite-common/wallet-utils'; const getSelectAccountOptions = ( suiteReceiveAccounts: Account[] | undefined, @@ -79,33 +80,13 @@ const getSuiteReceiveAccounts = ({ ((n.isDebugOnlyNetwork && isDebug) || !n.isDebugOnlyNetwork), ); - const isSameDevice = (account: Account) => account.deviceState === device?.state; - const isSameNetwork = (account: Account) => account.symbol === receiveNetwork; - const isDebugAndIsAccountDebugOnly = (account: Account) => - isDebugOnlyAccountType(account.accountType, account.symbol) && isDebug; - const isNotDebugOnlyAccount = (account: Account) => - !isDebugOnlyAccountType(account.accountType, account.symbol); - // Check if the account is not empty - const isNotEmptyAccount = (account: Account) => !account.empty; - // Check if the account is marked as visible - const isVisibleAccount = (account: Account) => account.visible; - const isFirstNormalAccount = (account: Account) => - account.accountType === 'normal' && account.index === 0; - const isCoinjoinAccount = (account: Account) => account.accountType === 'coinjoin'; - - if (receiveNetworks.length > 0) { - // Get accounts of the current symbol belonging to the current device. - return accounts.filter( - account => - isSameDevice(account) && - isSameNetwork(account) && - !isCoinjoinAccount(account) && - (isDebugAndIsAccountDebugOnly(account) || isNotDebugOnlyAccount(account)) && - (isNotEmptyAccount(account) || - isVisibleAccount(account) || - isFirstNormalAccount(account)), - ); - } + return filterReceiveAccounts({ + accounts, + deviceState: device?.state, + receiveNetwork, + isDebug, + receiveNetworks, + }); } return undefined; @@ -142,6 +123,7 @@ const useCoinmarketVerifyAccount = ({ }), [accounts, currency, device, isDebug, receiveNetwork], ); + const selectAccountOptions = useMemo( () => getSelectAccountOptions(suiteReceiveAccounts, device), [device, suiteReceiveAccounts], diff --git a/suite-common/wallet-config/src/utils.ts b/suite-common/wallet-config/src/utils.ts index 6f0034916c6..9c65c18ed7a 100644 --- a/suite-common/wallet-config/src/utils.ts +++ b/suite-common/wallet-config/src/utils.ts @@ -3,7 +3,6 @@ import { AccountType, Network, NetworkFeature, - Networks, NetworkSymbol, NormalizedNetworkAccount, } from './types'; @@ -45,21 +44,6 @@ export const normalizeNetworkAccounts = (network: Network): NormalizedNetworkAcc export const isBlockbookBasedNetwork = (symbol: NetworkSymbol) => networks[symbol]?.customBackends.some(backend => backend === 'blockbook'); -export const isDebugOnlyAccountType = ( - accountType: AccountType, - symbol?: NetworkSymbol, -): boolean => { - if (!symbol) return false; - - const network = (networks as Networks)?.[symbol]; - - if (!network) return false; - - const accountTypeInfo = network.accountTypes[accountType]; - - return !!accountTypeInfo?.isDebugOnlyAccountType; -}; - export const getNetworkType = (symbol: NetworkSymbol) => networks[symbol]?.networkType; // Takes into account just network features, not features for specific accountTypes. diff --git a/suite-common/wallet-utils/src/__tests__/filterReceiveAccounts.test.ts b/suite-common/wallet-utils/src/__tests__/filterReceiveAccounts.test.ts new file mode 100644 index 00000000000..a2cdac305b7 --- /dev/null +++ b/suite-common/wallet-utils/src/__tests__/filterReceiveAccounts.test.ts @@ -0,0 +1,128 @@ +import { testMocks } from '@suite-common/test-utils'; +import { Network, networksCollection } from '@suite-common/wallet-config'; +import { Account } from '@suite-common/wallet-types'; + +import { isDebugOnlyAccountType, filterReceiveAccounts } from '../filterReceiveAccounts'; + +const { getSuiteDevice, getWalletAccount } = testMocks; + +const accountsList: Account[] = [ + getWalletAccount({ symbol: 'eth', accountType: 'legacy' }), + getWalletAccount({ symbol: 'eth', accountType: 'normal' }), + getWalletAccount({ symbol: 'eth', accountType: 'ledger' }), + getWalletAccount({ symbol: 'btc', accountType: 'coinjoin' }), + getWalletAccount({ symbol: 'btc', accountType: 'taproot' }), + getWalletAccount({ symbol: 'btc', accountType: 'legacy' }), + getWalletAccount({ symbol: 'btc', accountType: 'segwit' }), + getWalletAccount({ symbol: 'btc', accountType: 'ledger' }), + getWalletAccount({ symbol: 'pol', accountType: 'legacy' }), + getWalletAccount({ symbol: 'pol', accountType: 'normal' }), + getWalletAccount({ symbol: 'pol', accountType: 'ledger' }), + getWalletAccount({ symbol: 'sol', accountType: 'normal', empty: true, visible: false }), + getWalletAccount({ symbol: 'sol', accountType: 'ledger' }), + getWalletAccount({ + symbol: 'sol', + accountType: 'ledger', + empty: true, + visible: false, + }), +]; + +type RunFilterReceiveAccountsTestParams = { + isDebug?: boolean; + receiveNetwork?: string; + deviceState?: `${string}@${string}:${number}`; + accounts?: Account[]; +}; + +const runFilterReceiveAccouns = ({ + isDebug = true, + receiveNetwork = 'eth', + deviceState = '1stTestnetAddress@device_id:0', + accounts = accountsList, +}: RunFilterReceiveAccountsTestParams) => { + const device = getSuiteDevice({ + unavailableCapabilities: { + dash: 'no-support', + }, + state: deviceState, + }); + const unavailableCapabilities = device?.unavailableCapabilities ?? {}; + + const receiveNetworks = networksCollection.filter( + (n: Network) => + n.symbol === receiveNetwork && + !unavailableCapabilities[n.symbol] && + ((n.isDebugOnlyNetwork && isDebug) || !n.isDebugOnlyNetwork), + ); + + return filterReceiveAccounts({ + accounts, + deviceState: device.state, + receiveNetwork, + isDebug, + receiveNetworks, + }); +}; + +describe('filter receive accounts', () => { + it('checks if account is debug only type', () => { + expect(isDebugOnlyAccountType('legacy', 'btc')).toBe(false); + expect(isDebugOnlyAccountType('segwit', 'btc')).toBe(false); + expect(isDebugOnlyAccountType('coinjoin', 'btc')).toBe(false); + expect(isDebugOnlyAccountType('taproot', 'btc')).toBe(false); + expect(isDebugOnlyAccountType('ledger', 'btc')).toBe(false); + expect(isDebugOnlyAccountType('legacy', 'eth')).toBe(true); + expect(isDebugOnlyAccountType('ledger', 'eth')).toBe(true); + expect(isDebugOnlyAccountType('normal', 'regtest')).toBe(false); + }); + + it('returns no results when given an empty accounts array', () => { + expect(runFilterReceiveAccouns({ accounts: [] })).toEqual([]); + }); + + it('returns no results when given a non-existing network in acccounts list', () => { + expect(runFilterReceiveAccouns({ receiveNetwork: 'bnb' })).toEqual([]); + }); + + it('returns all accounts when debug mode is on', () => { + const filteredAccounts = [ + getWalletAccount({ symbol: 'eth', accountType: 'legacy' }), + getWalletAccount({ symbol: 'eth', accountType: 'normal' }), + getWalletAccount({ symbol: 'eth', accountType: 'ledger' }), + ]; + expect(runFilterReceiveAccouns({})).toEqual(filteredAccounts); + }); + + it('returns only non-debug accounts when debug mode is off', () => { + const filteredAccounts = [getWalletAccount({ symbol: 'eth', accountType: 'normal' })]; + + expect(runFilterReceiveAccouns({ isDebug: false })).toEqual(filteredAccounts); + }); + + it('returns no results when device is not the same', () => { + expect(runFilterReceiveAccouns({ deviceState: '2ndTestnetAddress@device_id:0' })).toEqual( + [], + ); + }); + + it('excludes coinjoin accounts for BTC network (also tests isAnotherNetwork and isCoinjoinAccount methods)', () => { + const filteredAccounts = [ + getWalletAccount({ symbol: 'btc', accountType: 'taproot' }), + getWalletAccount({ symbol: 'btc', accountType: 'legacy' }), + getWalletAccount({ symbol: 'btc', accountType: 'segwit' }), + getWalletAccount({ symbol: 'btc', accountType: 'ledger' }), + ]; + + expect(runFilterReceiveAccouns({ receiveNetwork: 'btc' })).toEqual(filteredAccounts); + }); + + it('returns account when when its either first normal account (no matter is empty or not visible) or it is not empty and visible', () => { + const filteredAccounts = [ + getWalletAccount({ symbol: 'sol', accountType: 'normal', empty: true, visible: false }), + getWalletAccount({ symbol: 'sol', accountType: 'ledger' }), + ]; + + expect(runFilterReceiveAccouns({ receiveNetwork: 'sol' })).toEqual(filteredAccounts); + }); +}); diff --git a/suite-common/wallet-utils/src/filterReceiveAccounts.ts b/suite-common/wallet-utils/src/filterReceiveAccounts.ts new file mode 100644 index 00000000000..2025144d829 --- /dev/null +++ b/suite-common/wallet-utils/src/filterReceiveAccounts.ts @@ -0,0 +1,51 @@ +import { AccountType, getNetwork, Network, NetworkSymbol } from '@suite-common/wallet-config'; +import { Account } from '@suite-common/wallet-types'; + +export const isDebugOnlyAccountType = ( + accountType: AccountType, + symbol?: NetworkSymbol, +): boolean => { + if (!symbol) return false; + + const network = getNetwork(symbol); + + const accountTypeInfo = network.accountTypes[accountType]; + + return !!accountTypeInfo?.isDebugOnlyAccountType; +}; + +type FilterReceiveAccountsProps = { + accounts: Account[]; + deviceState: string | undefined; + receiveNetwork?: string; + isDebug: boolean; + receiveNetworks: Network[]; +}; + +export const filterReceiveAccounts = ({ + accounts, + deviceState, + receiveNetwork, + isDebug, +}: FilterReceiveAccountsProps): Account[] => { + const isSameDevice = (account: Account) => account.deviceState === deviceState; + const isSameNetwork = (account: Account) => account.symbol === receiveNetwork; + const shouldDisplayDebugOnly = (account: Account) => + isDebug || !isDebugOnlyAccountType(account.accountType, account.symbol); + const isNotEmptyAccount = (account: Account) => !account.empty; + const isVisibleAccount = (account: Account) => account.visible; + const isFirstNormalAccount = (account: Account) => + account.accountType === 'normal' && account.index === 0; + const isCoinjoinAccount = (account: Account) => account.accountType === 'coinjoin'; + + return accounts.filter( + account => + isSameDevice(account) && + isSameNetwork(account) && + !isCoinjoinAccount(account) && + shouldDisplayDebugOnly(account) && + (isNotEmptyAccount(account) || + isVisibleAccount(account) || + isFirstNormalAccount(account)), + ); +}; diff --git a/suite-common/wallet-utils/src/index.ts b/suite-common/wallet-utils/src/index.ts index 221a1a2a325..699e388498b 100644 --- a/suite-common/wallet-utils/src/index.ts +++ b/suite-common/wallet-utils/src/index.ts @@ -19,5 +19,6 @@ export * from './validationUtils'; export * from './antiFraud'; export * from './stakingUtils'; export * from './reviewTransactionUtils'; +export * from './filterReceiveAccounts'; export { analyzeTransactions as analyzeTransactionsFixtures } from './__fixtures__/transactionUtils';