|
| 1 | +import { select } from '@redux-saga/core/effects' |
| 2 | +import { expectSaga } from 'redux-saga-test-plan' |
| 3 | +import * as matchers from 'redux-saga-test-plan/matchers' |
| 4 | +import type { StaticProvider } from 'redux-saga-test-plan/providers' |
| 5 | +import { saveDappConnection } from 'src/app/features/dapp/actions' |
| 6 | +import { type DappInfo, dappStore } from 'src/app/features/dapp/store' |
| 7 | +import { saveAccount } from 'src/app/features/dappRequests/accounts' |
| 8 | +import { type SenderTabInfo } from 'src/app/features/dappRequests/shared' |
| 9 | +import { UniverseChainId } from 'uniswap/src/features/chains/types' |
| 10 | +import { pushNotification } from 'uniswap/src/features/notifications/slice/slice' |
| 11 | +import { AppNotificationType } from 'uniswap/src/features/notifications/slice/types' |
| 12 | +import { Platform } from 'uniswap/src/features/platforms/types/Platform' |
| 13 | +import { getEnabledChainIdsSaga } from 'uniswap/src/features/settings/saga' |
| 14 | +import { extractBaseUrl } from 'utilities/src/format/urls' |
| 15 | +import { type Account } from 'wallet/src/features/wallet/accounts/types' |
| 16 | +import { getProvider } from 'wallet/src/features/wallet/context' |
| 17 | +import { selectActiveAccount } from 'wallet/src/features/wallet/selectors' |
| 18 | +import { ACCOUNT, ACCOUNT2 } from 'wallet/src/test/fixtures' |
| 19 | + |
| 20 | +const SENDER_TAB_INFO: SenderTabInfo = { |
| 21 | + id: 1, |
| 22 | + url: 'https://app.uniswap.org/swap', |
| 23 | + favIconUrl: 'https://app.uniswap.org/favicon.ico', |
| 24 | +} |
| 25 | +const DAPP_URL = extractBaseUrl(SENDER_TAB_INFO.url) |
| 26 | +const PROVIDER_URL = 'https://rpc.example/' |
| 27 | + |
| 28 | +function getProviders({ |
| 29 | + activeAccount, |
| 30 | + dappInfo, |
| 31 | + orderedAddresses, |
| 32 | +}: { |
| 33 | + activeAccount: Account |
| 34 | + dappInfo: DappInfo | undefined |
| 35 | + orderedAddresses: Address[] |
| 36 | +}): StaticProvider[] { |
| 37 | + return [ |
| 38 | + [select(selectActiveAccount), activeAccount], |
| 39 | + [matchers.call.fn(getEnabledChainIdsSaga), { defaultChainId: UniverseChainId.Mainnet }], |
| 40 | + [matchers.call.fn(dappStore.getDappInfo), dappInfo], |
| 41 | + [matchers.call.fn(getProvider), { connection: { url: PROVIDER_URL } }], |
| 42 | + [matchers.call.fn(saveDappConnection), undefined], |
| 43 | + [matchers.call.fn(dappStore.getDappOrderedConnectedAddresses), orderedAddresses], |
| 44 | + ] |
| 45 | +} |
| 46 | + |
| 47 | +describe('saveAccount', () => { |
| 48 | + // Security regression: a dapp connected only to Account A must not be able to silently absorb the |
| 49 | + // extension's active Account B via an auto-confirmed eth_requestAccounts / wallet_requestPermissions. |
| 50 | + it('does not expand an already-connected origin to the unapproved active account', async () => { |
| 51 | + const dappInfo: DappInfo = { |
| 52 | + lastChainId: UniverseChainId.Mainnet, |
| 53 | + connectedAccounts: [ACCOUNT], |
| 54 | + activeConnectedAddress: ACCOUNT.address, |
| 55 | + } |
| 56 | + |
| 57 | + const { returnValue } = await expectSaga(saveAccount, SENDER_TAB_INFO) |
| 58 | + .provide(getProviders({ activeAccount: ACCOUNT2, dappInfo, orderedAddresses: [ACCOUNT.address] })) |
| 59 | + .not.call.fn(saveDappConnection) |
| 60 | + .run() |
| 61 | + |
| 62 | + // Only the already-approved account is returned to the dapp; Account B was never added. |
| 63 | + expect(returnValue).toEqual({ |
| 64 | + dappUrl: DAPP_URL, |
| 65 | + connectedAddresses: [ACCOUNT.address], |
| 66 | + chainId: UniverseChainId.Mainnet, |
| 67 | + providerUrl: PROVIDER_URL, |
| 68 | + }) |
| 69 | + }) |
| 70 | + |
| 71 | + it('persists the connection when the active account is already approved for the origin', async () => { |
| 72 | + const dappInfo: DappInfo = { |
| 73 | + lastChainId: UniverseChainId.Mainnet, |
| 74 | + connectedAccounts: [ACCOUNT, ACCOUNT2], |
| 75 | + activeConnectedAddress: ACCOUNT.address, |
| 76 | + } |
| 77 | + |
| 78 | + await expectSaga(saveAccount, SENDER_TAB_INFO) |
| 79 | + .provide( |
| 80 | + getProviders({ activeAccount: ACCOUNT2, dappInfo, orderedAddresses: [ACCOUNT2.address, ACCOUNT.address] }), |
| 81 | + ) |
| 82 | + .call.fn(saveDappConnection) |
| 83 | + .run() |
| 84 | + }) |
| 85 | + |
| 86 | + it('saves the connection and notifies on a first-time connection', async () => { |
| 87 | + await expectSaga(saveAccount, SENDER_TAB_INFO) |
| 88 | + .provide(getProviders({ activeAccount: ACCOUNT, dappInfo: undefined, orderedAddresses: [ACCOUNT.address] })) |
| 89 | + .call.fn(saveDappConnection) |
| 90 | + .put(pushNotification({ type: AppNotificationType.DappConnected, dappIconUrl: SENDER_TAB_INFO.favIconUrl })) |
| 91 | + .run() |
| 92 | + }) |
| 93 | +}) |
0 commit comments