diff --git a/.eslintrc.js b/.eslintrc.js index 1236df167..4ef8de9bf 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -238,7 +238,6 @@ module.exports = { excludedFiles: [ 'app/scripts/controllers/app-state.test.js', 'app/scripts/controllers/mmi-controller.test.js', - 'app/scripts/controllers/network/**/*.test.js', 'app/scripts/controllers/permissions/**/*.test.js', 'app/scripts/lib/**/*.test.js', 'app/scripts/migrations/*.test.js', @@ -267,9 +266,6 @@ module.exports = { '**/__snapshots__/*.snap', 'app/scripts/controllers/app-state.test.js', 'app/scripts/controllers/mmi-controller.test.js', - 'app/scripts/controllers/network/**/*.test.js', - 'app/scripts/controllers/network/**/*.test.ts', - 'app/scripts/controllers/network/provider-api-tests/*.ts', 'app/scripts/controllers/permissions/**/*.test.js', 'app/scripts/lib/**/*.test.js', 'app/scripts/migrations/*.test.js', diff --git a/.mocharc.js b/.mocharc.js index a8843b0e1..2ed699e6b 100644 --- a/.mocharc.js +++ b/.mocharc.js @@ -6,7 +6,6 @@ module.exports = { './app/scripts/migrations/*.test.js', './app/scripts/platforms/*.test.js', './app/scripts/controllers/app-state.test.js', - './app/scripts/controllers/network/**/*.test.js', './app/scripts/controllers/permissions/**/*.test.js', './app/scripts/controllers/mmi-controller.test.js', './app/scripts/constants/error-utils.test.js', diff --git a/app/scripts/background.js b/app/scripts/background.js index bd80b71ac..e3416b796 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -462,7 +462,7 @@ export function setupController( setupEnsIpfsResolver({ getCurrentChainId: () => - controller.networkController.store.getState().providerConfig.chainId, + controller.networkController.state.providerConfig.chainId, getIpfsGateway: controller.preferencesController.getIpfsGateway.bind( controller.preferencesController, ), diff --git a/app/scripts/controllers/backup.js b/app/scripts/controllers/backup.js index 5873bc08c..159f02d2d 100644 --- a/app/scripts/controllers/backup.js +++ b/app/scripts/controllers/backup.js @@ -31,7 +31,7 @@ export default class BackupController { } if (network) { - this.networkController.store.updateState(network); + this.networkController.loadBackup(network); } if (preferences || addressBook || network) { @@ -48,7 +48,7 @@ export default class BackupController { addressBook: { ...this.addressBookController.state }, network: { networkConfigurations: - this.networkController.store.getState().networkConfigurations, + this.networkController.state.networkConfigurations, }, }; diff --git a/app/scripts/controllers/backup.test.js b/app/scripts/controllers/backup.test.js index fdeba7faf..25f153a37 100644 --- a/app/scripts/controllers/backup.test.js +++ b/app/scripts/controllers/backup.test.js @@ -57,18 +57,15 @@ function getMockAddressBookController() { } function getMockNetworkController() { - const mcState = { + const state = { networkConfigurations: {}, - - update: (store) => (mcState.store = store), }; - mcState.store = { - getState: sinon.stub().returns(mcState), - updateState: (store) => (mcState.store = store), + const loadBackup = ({ networkConfigurations }) => { + Object.assign(state, { networkConfigurations }); }; - return mcState; + return { state, loadBackup }; } const jsonData = JSON.stringify({ @@ -174,28 +171,28 @@ describe('BackupController', function () { it('should restore backup', async function () { const backupController = getBackupController(); - backupController.restoreUserData(jsonData); + await backupController.restoreUserData(jsonData); // check networks backup assert.equal( - backupController.networkController.store.networkConfigurations[ + backupController.networkController.state.networkConfigurations[ 'network-configuration-id-1' ].chainId, '0x539', ); assert.equal( - backupController.networkController.store.networkConfigurations[ + backupController.networkController.state.networkConfigurations[ 'network-configuration-id-2' ].chainId, '0x38', ); assert.equal( - backupController.networkController.store.networkConfigurations[ + backupController.networkController.state.networkConfigurations[ 'network-configuration-id-3' ].chainId, '0x61', ); assert.equal( - backupController.networkController.store.networkConfigurations[ + backupController.networkController.state.networkConfigurations[ 'network-configuration-id-4' ].chainId, '0x89', diff --git a/app/scripts/controllers/detect-tokens.js b/app/scripts/controllers/detect-tokens.js index 33e765f91..46719aebc 100644 --- a/app/scripts/controllers/detect-tokens.js +++ b/app/scripts/controllers/detect-tokens.js @@ -33,8 +33,10 @@ export default class DetectTokensController { * @param config.tokensController * @param config.assetsContractController * @param config.trackMetaMetricsEvent + * @param config.messenger */ constructor({ + messenger, interval = DEFAULT_INTERVAL, preferences, network, @@ -44,6 +46,7 @@ export default class DetectTokensController { assetsContractController = null, trackMetaMetricsEvent, } = {}) { + this.messenger = messenger; this.assetsContractController = assetsContractController; this.tokensController = tokensController; this.preferences = preferences; @@ -59,7 +62,7 @@ export default class DetectTokensController { }); this.hiddenTokens = this.tokensController?.state.ignoredTokens; this.detectedTokens = this.tokensController?.state.detectedTokens; - this.chainId = this.getChainIdFromNetworkStore(network); + this.chainId = this.getChainIdFromNetworkStore(); this._trackMetaMetricsEvent = trackMetaMetricsEvent; preferences?.store.subscribe(({ selectedAddress, useTokenDetection }) => { @@ -81,6 +84,13 @@ export default class DetectTokensController { this.detectedTokens = detectedTokens; }, ); + messenger.subscribe('NetworkController:stateChange', () => { + if (this.chainId !== this.getChainIdFromNetworkStore()) { + const chainId = this.getChainIdFromNetworkStore(); + this.chainId = chainId; + this.restartTokenDetection({ chainId: this.chainId }); + } + }); } /** @@ -93,7 +103,7 @@ export default class DetectTokensController { async detectNewTokens({ selectedAddress, chainId } = {}) { const addressAgainstWhichToDetect = selectedAddress ?? this.selectedAddress; const chainIdAgainstWhichToDetect = - chainId ?? this.getChainIdFromNetworkStore(this._network); + chainId ?? this.getChainIdFromNetworkStore(); if (!this.isActive) { return; } @@ -208,8 +218,8 @@ export default class DetectTokensController { this.interval = DEFAULT_INTERVAL; } - getChainIdFromNetworkStore(network) { - return network?.store.getState().providerConfig.chainId; + getChainIdFromNetworkStore() { + return this.network?.state.providerConfig.chainId; } /* eslint-disable accessor-pairs */ @@ -226,23 +236,6 @@ export default class DetectTokensController { }, interval); } - /** - * @type {object} - */ - set network(network) { - if (!network) { - return; - } - this._network = network; - this._network.store.subscribe(() => { - if (this.chainId !== this.getChainIdFromNetworkStore(network)) { - const chainId = this.getChainIdFromNetworkStore(network); - this.chainId = chainId; - this.restartTokenDetection({ chainId: this.chainId }); - } - }); - } - /** * In setter when isUnlocked is updated to true, detectNewTokens and restart polling * diff --git a/app/scripts/controllers/detect-tokens.test.js b/app/scripts/controllers/detect-tokens.test.js index a1fee90ce..beb0e9913 100644 --- a/app/scripts/controllers/detect-tokens.test.js +++ b/app/scripts/controllers/detect-tokens.test.js @@ -10,12 +10,19 @@ import { AssetsContractController, } from '@metamask/assets-controllers'; import { toHex } from '@metamask/controller-utils'; +import { NetworkController } from '@metamask/network-controller'; import { NETWORK_TYPES } from '../../../shared/constants/network'; import { toChecksumHexAddress } from '../../../shared/modules/hexstring-utils'; import DetectTokensController from './detect-tokens'; -import { NetworkController } from './network'; import PreferencesController from './preferences'; +function buildMessenger() { + return new ControllerMessenger().getRestricted({ + name: 'DetectTokensController', + allowedEvents: ['NetworkController:stateChange'], + }); +} + describe('DetectTokensController', function () { let sandbox, assetsContractController, @@ -230,23 +237,20 @@ describe('DetectTokensController', function () { onPreferencesStateChange: preferences.store.subscribe.bind( preferences.store, ), - onNetworkStateChange: (cb) => - network.store.subscribe((networkState) => { - const modifiedNetworkState = { - ...networkState, - providerConfig: { - ...networkState.providerConfig, - }, - }; - return cb(modifiedNetworkState); - }), + onNetworkStateChange: networkControllerMessenger.subscribe.bind( + networkControllerMessenger, + 'NetworkController:stateChange', + ), }); assetsContractController = new AssetsContractController({ onPreferencesStateChange: preferences.store.subscribe.bind( preferences.store, ), - onNetworkStateChange: network.store.subscribe.bind(network.store), + onNetworkStateChange: networkControllerMessenger.subscribe.bind( + networkControllerMessenger, + 'NetworkController:stateChange', + ), }); }); @@ -257,7 +261,7 @@ describe('DetectTokensController', function () { it('should poll on correct interval', async function () { const stub = sinon.stub(global, 'setInterval'); - new DetectTokensController({ interval: 1337 }); // eslint-disable-line no-new + new DetectTokensController({ messenger: buildMessenger(), interval: 1337 }); // eslint-disable-line no-new assert.strictEqual(stub.getCall(0).args[1], 1337); stub.restore(); }); @@ -266,6 +270,7 @@ describe('DetectTokensController', function () { const clock = sandbox.useFakeTimers(); await network.setProviderType(NETWORK_TYPES.MAINNET); const controller = new DetectTokensController({ + messenger: buildMessenger(), preferences, network, keyringMemStore, @@ -302,6 +307,7 @@ describe('DetectTokensController', function () { }); await tokenListController.start(); const controller = new DetectTokensController({ + messenger: buildMessenger(), preferences, network, keyringMemStore, @@ -325,6 +331,7 @@ describe('DetectTokensController', function () { sandbox.useFakeTimers(); await network.setProviderType(NETWORK_TYPES.MAINNET); const controller = new DetectTokensController({ + messenger: buildMessenger(), preferences, network, keyringMemStore, @@ -376,6 +383,7 @@ describe('DetectTokensController', function () { sandbox.useFakeTimers(); await network.setProviderType(NETWORK_TYPES.MAINNET); const controller = new DetectTokensController({ + messenger: buildMessenger(), preferences, network, keyringMemStore, @@ -434,6 +442,7 @@ describe('DetectTokensController', function () { it('should trigger detect new tokens when change address', async function () { sandbox.useFakeTimers(); const controller = new DetectTokensController({ + messenger: buildMessenger(), preferences, network, keyringMemStore, @@ -453,6 +462,7 @@ describe('DetectTokensController', function () { it('should trigger detect new tokens when submit password', async function () { sandbox.useFakeTimers(); const controller = new DetectTokensController({ + messenger: buildMessenger(), preferences, network, keyringMemStore, @@ -471,6 +481,7 @@ describe('DetectTokensController', function () { const clock = sandbox.useFakeTimers(); await network.setProviderType(NETWORK_TYPES.MAINNET); const controller = new DetectTokensController({ + messenger: buildMessenger(), preferences, network, keyringMemStore, @@ -492,6 +503,7 @@ describe('DetectTokensController', function () { const clock = sandbox.useFakeTimers(); await network.setProviderType(NETWORK_TYPES.MAINNET); const controller = new DetectTokensController({ + messenger: buildMessenger(), preferences, network, keyringMemStore, diff --git a/app/scripts/controllers/metametrics.test.js b/app/scripts/controllers/metametrics.test.js index 780da42b4..e881f4772 100644 --- a/app/scripts/controllers/metametrics.test.js +++ b/app/scripts/controllers/metametrics.test.js @@ -9,11 +9,7 @@ import { MetaMetricsUserTrait, } from '../../../shared/constants/metametrics'; import waitUntilCalled from '../../../test/lib/wait-until-called'; -import { - CHAIN_IDS, - CURRENCY_SYMBOLS, - NETWORK_TYPES, -} from '../../../shared/constants/network'; +import { CHAIN_IDS, CURRENCY_SYMBOLS } from '../../../shared/constants/network'; import * as Utils from '../lib/util'; import MetaMetricsController from './metametrics'; @@ -77,28 +73,6 @@ const DEFAULT_PAGE_PROPERTIES = { ...DEFAULT_SHARED_PROPERTIES, }; -function getMockNetworkController() { - let state = { - providerConfig: { - type: NETWORK_TYPES.GOERLI, - chainId: FAKE_CHAIN_ID, - }, - network: 'loading', - }; - const onNetworkDidChange = sinon.stub(); - const updateState = (newState) => { - state = { ...state, ...newState }; - onNetworkDidChange.getCall(0).args[0](); - }; - return { - store: { - getState: () => state, - updateState, - }, - onNetworkDidChange, - }; -} - function getMockPreferencesStore({ currentLocale = LOCALE } = {}) { let preferencesStore = { currentLocale, @@ -142,15 +116,16 @@ function getMetaMetricsController({ participateInMetaMetrics = true, metaMetricsId = TEST_META_METRICS_ID, preferencesStore = getMockPreferencesStore(), - networkController = getMockNetworkController(), + getCurrentChainId = () => FAKE_CHAIN_ID, + onNetworkDidChange = () => { + // do nothing + }, segmentInstance, } = {}) { return new MetaMetricsController({ segment: segmentInstance || segment, - getCurrentChainId: () => - networkController.store.getState().providerConfig.chainId, - onNetworkDidChange: - networkController.onNetworkDidChange.bind(networkController), + getCurrentChainId, + onNetworkDidChange, preferencesStore, version: '0.0.1', environment: 'test', @@ -166,6 +141,7 @@ function getMetaMetricsController({ extension: MOCK_EXTENSION, }); } + describe('MetaMetricsController', function () { const now = new Date(); let clock; @@ -213,17 +189,20 @@ describe('MetaMetricsController', function () { }); it('should update when network changes', function () { - const networkController = getMockNetworkController(); + let chainId = '0x111'; + let networkDidChangeListener; + const onNetworkDidChange = (listener) => { + networkDidChangeListener = listener; + }; const metaMetricsController = getMetaMetricsController({ - networkController, + getCurrentChainId: () => chainId, + onNetworkDidChange, }); - networkController.store.updateState({ - providerConfig: { - type: 'NEW_NETWORK', - chainId: '0xaab', - }, - }); - assert.strictEqual(metaMetricsController.chainId, '0xaab'); + + chainId = '0x222'; + networkDidChangeListener(); + + assert.strictEqual(metaMetricsController.chainId, '0x222'); }); it('should update when preferences changes', function () { diff --git a/app/scripts/controllers/mmi-controller.js b/app/scripts/controllers/mmi-controller.js index ca9933cfb..689472829 100644 --- a/app/scripts/controllers/mmi-controller.js +++ b/app/scripts/controllers/mmi-controller.js @@ -549,22 +549,25 @@ export default class MMIController extends EventEmitter { this.preferencesController.setSelectedAddress(address); } const selectedChainId = parseInt( - this.networkController.getCurrentChainId(), + this.networkController.state.providerConfig.chainId, 16, ); if (selectedChainId !== chainId && chainId === 1) { - this.networkController.setProviderType('mainnet'); + await this.networkController.setProviderType('mainnet'); } else if (selectedChainId !== chainId) { - const network = this.preferencesController - .getFrequentRpcListDetail() - .find((item) => parseInt(item.chainId, 16) === chainId); - this.networkController.setRpcTarget( - network.rpcUrl, - network.chainId, - network.ticker, - network.nickname, - ); + const foundNetworkConfiguration = Object.values( + this.networkController.state.networkConfigurations, + ).find((networkConfiguration) => { + return parseInt(networkConfiguration.chainId, 16) === chainId; + }); + + if (foundNetworkConfiguration !== undefined) { + await this.networkConfiguration.setActiveNetwork( + foundNetworkConfiguration.id, + ); + } } + getPermissionBackgroundApiMethods( this.permissionController, ).addPermittedAccount(origin, address); diff --git a/app/scripts/controllers/network/create-network-client.test.ts b/app/scripts/controllers/network/create-network-client.test.ts deleted file mode 100644 index 7e674392b..000000000 --- a/app/scripts/controllers/network/create-network-client.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { NetworkClientType } from './create-network-client'; -import { testsForProviderType } from './provider-api-tests/shared-tests'; - -describe('createNetworkClient', () => { - testsForProviderType(NetworkClientType.Infura); - testsForProviderType(NetworkClientType.Custom); -}); diff --git a/app/scripts/controllers/network/create-network-client.ts b/app/scripts/controllers/network/create-network-client.ts deleted file mode 100644 index 6e96b71f5..000000000 --- a/app/scripts/controllers/network/create-network-client.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { - createAsyncMiddleware, - createScaffoldMiddleware, - JsonRpcEngine, - mergeMiddleware, - JsonRpcMiddleware, -} from 'json-rpc-engine'; -import { - createBlockCacheMiddleware, - createBlockRefMiddleware, - createBlockRefRewriteMiddleware, - createBlockTrackerInspectorMiddleware, - createInflightCacheMiddleware, - createFetchMiddleware, - createRetryOnEmptyMiddleware, -} from '@metamask/eth-json-rpc-middleware'; -import { - providerFromEngine, - providerFromMiddleware, - SafeEventEmitterProvider, -} from '@metamask/eth-json-rpc-provider'; -import { createInfuraMiddleware } from '@metamask/eth-json-rpc-infura'; -import type { Hex } from '@metamask/utils/dist'; -import { PollingBlockTracker } from 'eth-block-tracker/dist'; -import { SECOND } from '../../../../shared/constants/time'; -import { - BUILT_IN_INFURA_NETWORKS, - BuiltInInfuraNetwork, -} from '../../../../shared/constants/network'; - -export enum NetworkClientType { - Custom = 'custom', - Infura = 'infura', -} - -type CustomNetworkConfiguration = { - chainId: Hex; - rpcUrl: string; - type: NetworkClientType.Custom; -}; - -type InfuraNetworkConfiguration = { - network: BuiltInInfuraNetwork; - infuraProjectId: string; - type: NetworkClientType.Infura; -}; - -/** - * Create a JSON RPC network client for a specific network. - * - * @param networkConfig - The network configuration. - * @returns - */ -export function createNetworkClient( - networkConfig: CustomNetworkConfiguration | InfuraNetworkConfiguration, -): { provider: SafeEventEmitterProvider; blockTracker: PollingBlockTracker } { - const rpcApiMiddleware = - networkConfig.type === NetworkClientType.Infura - ? createInfuraMiddleware({ - network: networkConfig.network, - projectId: networkConfig.infuraProjectId, - maxAttempts: 5, - source: 'metamask', - }) - : createFetchMiddleware({ - btoa: global.btoa, - fetch: global.fetch, - rpcUrl: networkConfig.rpcUrl, - }); - - const rpcProvider = providerFromMiddleware(rpcApiMiddleware); - - const blockTrackerOpts = - process.env.IN_TEST && networkConfig.type === 'custom' - ? { pollingInterval: SECOND } - : {}; - const blockTracker = new PollingBlockTracker({ - ...blockTrackerOpts, - provider: rpcProvider, - }); - - const networkMiddleware = - networkConfig.type === NetworkClientType.Infura - ? createInfuraNetworkMiddleware({ - blockTracker, - network: networkConfig.network, - rpcProvider, - rpcApiMiddleware, - }) - : createCustomNetworkMiddleware({ - blockTracker, - chainId: networkConfig.chainId, - rpcApiMiddleware, - }); - - const engine = new JsonRpcEngine(); - - engine.push(networkMiddleware); - - const provider = providerFromEngine(engine); - - return { provider, blockTracker }; -} - -function createInfuraNetworkMiddleware({ - blockTracker, - network, - rpcProvider, - rpcApiMiddleware, -}: { - blockTracker: PollingBlockTracker; - network: BuiltInInfuraNetwork; - rpcProvider: SafeEventEmitterProvider; - rpcApiMiddleware: JsonRpcMiddleware; -}) { - return mergeMiddleware([ - createNetworkAndChainIdMiddleware({ network }), - createBlockCacheMiddleware({ blockTracker }), - createInflightCacheMiddleware(), - createBlockRefMiddleware({ blockTracker, provider: rpcProvider }), - createRetryOnEmptyMiddleware({ blockTracker, provider: rpcProvider }), - createBlockTrackerInspectorMiddleware({ blockTracker }), - rpcApiMiddleware, - ]); -} - -function createNetworkAndChainIdMiddleware({ - network, -}: { - network: BuiltInInfuraNetwork; -}) { - if (!BUILT_IN_INFURA_NETWORKS[network]) { - throw new Error(`createInfuraClient - unknown network "${network}"`); - } - - const { chainId, networkId } = BUILT_IN_INFURA_NETWORKS[network]; - - return createScaffoldMiddleware({ - eth_chainId: chainId, - net_version: networkId, - }); -} - -const createChainIdMiddleware = ( - chainId: string, -): JsonRpcMiddleware => { - return (req, res, next, end) => { - if (req.method === 'eth_chainId') { - res.result = chainId; - return end(); - } - return next(); - }; -}; - -function createCustomNetworkMiddleware({ - blockTracker, - chainId, - rpcApiMiddleware, -}: { - blockTracker: PollingBlockTracker; - chainId: string; - rpcApiMiddleware: any; -}) { - const testMiddlewares = process.env.IN_TEST - ? [createEstimateGasDelayTestMiddleware()] - : []; - - return mergeMiddleware([ - ...testMiddlewares, - createChainIdMiddleware(chainId), - createBlockRefRewriteMiddleware({ blockTracker }), - createBlockCacheMiddleware({ blockTracker }), - createInflightCacheMiddleware(), - createBlockTrackerInspectorMiddleware({ blockTracker }), - rpcApiMiddleware, - ]); -} - -/** - * For use in tests only. - * Adds a delay to `eth_estimateGas` calls. - */ -function createEstimateGasDelayTestMiddleware() { - return createAsyncMiddleware(async (req, _, next) => { - if (req.method === 'eth_estimateGas') { - await new Promise((resolve) => setTimeout(resolve, SECOND * 2)); - } - return next(); - }); -} diff --git a/app/scripts/controllers/network/index.ts b/app/scripts/controllers/network/index.ts deleted file mode 100644 index de3e59ea1..000000000 --- a/app/scripts/controllers/network/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './network-controller'; diff --git a/app/scripts/controllers/network/network-controller.test.ts b/app/scripts/controllers/network/network-controller.test.ts deleted file mode 100644 index cd693f4e8..000000000 --- a/app/scripts/controllers/network/network-controller.test.ts +++ /dev/null @@ -1,7676 +0,0 @@ -// The `refreshNetworkTests` helper defines tests outside of a `describe` block. -/* eslint-disable jest/require-top-level-describe */ - -import { inspect, isDeepStrictEqual, promisify } from 'util'; -import assert from 'assert'; -import { get } from 'lodash'; -import { v4 } from 'uuid'; -import nock from 'nock'; -import { ControllerMessenger } from '@metamask/base-controller'; -import { SafeEventEmitterProvider } from '@metamask/eth-json-rpc-provider'; -import { toHex } from '@metamask/controller-utils'; -import { when, resetAllWhenMocks } from 'jest-when'; -import { ethErrors } from 'eth-rpc-errors'; -import { - NetworkStatus, - NETWORK_TYPES, -} from '../../../../shared/constants/network'; -import { - NetworkController, - NetworkControllerAction, - NetworkControllerEvent, - NetworkControllerOptions, - NetworkControllerState, - ProviderConfiguration, -} from './network-controller'; -import { - createNetworkClient, - NetworkClientType, -} from './create-network-client'; -import { FakeBlockTracker } from './test/fake-block-tracker'; -import { FakeProvider, FakeProviderStub } from './test/fake-provider'; - -jest.mock('./create-network-client'); - -jest.mock('uuid', () => { - const actual = jest.requireActual('uuid'); - - return { - ...actual, - v4: jest.fn(), - }; -}); - -/** - * A block header object that `eth_getBlockByNumber` can be mocked to return. - * Note that this type does not specify all of the properties present within the - * block header; within these tests, we are only interested in `number` and - * `baseFeePerGas`. - */ -type Block = { - number: string; - baseFeePerGas?: string; -}; - -const createNetworkClientMock = jest.mocked(createNetworkClient); -const uuidV4Mock = jest.mocked(v4); - -const NetworkType = { - mainnet: NETWORK_TYPES.MAINNET, - goerli: NETWORK_TYPES.GOERLI, - sepolia: NETWORK_TYPES.SEPOLIA, - rpc: NETWORK_TYPES.RPC, -} as const; - -/** - * A dummy block that matches the pre-EIP-1559 format (i.e. it doesn't have the - * `baseFeePerGas` property). - */ -const PRE_1559_BLOCK: Block = { - number: '0x42', -}; - -/** - * A dummy block that matches the pre-EIP-1559 format (i.e. it has the - * `baseFeePerGas` property). - */ -const POST_1559_BLOCK: Block = { - ...PRE_1559_BLOCK, - baseFeePerGas: '0x63c498a46', -}; - -/** - * An alias for `POST_1559_BLOCK`, for tests that don't care about which kind of - * block they're looking for. - */ -const BLOCK: Block = POST_1559_BLOCK; - -/** - * The networks that NetworkController recognizes as built-in Infura networks, - * along with information we expect to be true for those networks. - */ -const INFURA_NETWORKS = [ - { - networkType: NetworkType.mainnet, - chainId: toHex(1), - ticker: 'ETH', - blockExplorerUrl: 'https://etherscan.io', - }, - { - networkType: NetworkType.goerli, - chainId: toHex(5), - ticker: 'GoerliETH', - blockExplorerUrl: 'https://goerli.etherscan.io', - }, - { - networkType: NetworkType.sepolia, - chainId: toHex(11155111), - ticker: 'SepoliaETH', - blockExplorerUrl: 'https://sepolia.etherscan.io', - }, -]; - -/** - * A response object for a successful request to `eth_getBlockByNumber`. It is - * assumed that the block number here is insignificant to the test. - */ -const SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE = { - result: BLOCK, -}; - -/** - * A response object for a successful request to `net_version`. It is assumed - * that the network ID here is insignificant to the test. - */ -const SUCCESSFUL_NET_VERSION_RESPONSE = { - result: '42', -}; - -/** - * A response object for a request that has been geoblocked by Infura. - */ -const BLOCKED_INFURA_JSON_RPC_ERROR = ethErrors.rpc.internal( - JSON.stringify({ error: 'countryBlocked' }), -); - -/** - * A response object for a unsuccessful request to any RPC method. It is assumed - * that the error here is insignificant to the test. - */ -const GENERIC_JSON_RPC_ERROR = ethErrors.rpc.internal( - JSON.stringify({ error: 'oops' }), -); - -describe('NetworkController', () => { - beforeEach(() => { - // Disable all requests, even those to localhost - nock.disableNetConnect(); - }); - - afterEach(() => { - nock.enableNetConnect('localhost'); - nock.cleanAll(); - resetAllWhenMocks(); - jest.resetAllMocks(); - }); - - describe('constructor', () => { - const invalidInfuraProjectIds = [undefined, null, {}, 1]; - invalidInfuraProjectIds.forEach((invalidProjectId) => { - it(`throws given an invalid Infura ID of "${inspect( - invalidProjectId, - )}"`, () => { - const messenger = buildMessenger(); - const restrictedMessenger = buildNetworkControllerMessenger(messenger); - expect( - () => - new NetworkController({ - messenger: restrictedMessenger, - // @ts-expect-error We are intentionally passing bad input. - infuraProjectId: invalidProjectId, - }), - ).toThrow('Invalid Infura project ID'); - }); - }); - - it('initializes the state with some defaults', async () => { - await withController(({ controller }) => { - expect(controller.store.getState()).toMatchInlineSnapshot(` - { - "networkConfigurations": {}, - "networkDetails": { - "EIPS": {}, - }, - "networkId": null, - "networkStatus": "unknown", - "providerConfig": { - "chainId": "0x539", - "nickname": "Localhost 8545", - "rpcUrl": "http://localhost:8545", - "ticker": "ETH", - "type": "rpc", - }, - } - `); - }); - }); - - it('merges the given state into the default state', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'http://example-custom-rpc.metamask.io', - chainId: '0x9999' as const, - nickname: 'Test initial state', - }, - networkDetails: { - EIPS: { - 1559: true, - }, - }, - }, - }, - ({ controller }) => { - expect(controller.store.getState()).toMatchInlineSnapshot(` - { - "networkConfigurations": {}, - "networkDetails": { - "EIPS": { - "1559": true, - }, - }, - "networkId": null, - "networkStatus": "unknown", - "providerConfig": { - "chainId": "0x9999", - "nickname": "Test initial state", - "rpcUrl": "http://example-custom-rpc.metamask.io", - "type": "rpc", - }, - } - `); - }, - ); - }); - }); - - describe('destroy', () => { - it('does not throw if called before the provider is initialized', async () => { - await withController(async ({ controller }) => { - expect(await controller.destroy()).toBeUndefined(); - }); - }); - - it('stops the block tracker for the currently selected network as long as the provider has been initialized', async () => { - await withController(async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); - const { blockTracker } = controller.getProviderAndBlockTracker(); - assert(blockTracker, 'Block tracker is somehow unset'); - // The block tracker starts running after a listener is attached - blockTracker.addListener('latest', () => { - // do nothing - }); - expect(blockTracker.isRunning()).toBe(true); - - await controller.destroy(); - - expect(blockTracker.isRunning()).toBe(false); - }); - }); - }); - - describe('initializeProvider', () => { - describe('when the type in the provider config is invalid', () => { - it('throws', async () => { - const invalidProviderConfig = {}; - await withController( - /* @ts-expect-error We're intentionally passing bad input. */ - { - state: { - providerConfig: invalidProviderConfig, - }, - }, - async ({ controller }) => { - await expect(async () => { - await controller.initializeProvider(); - }).rejects.toThrow("Unrecognized network type: 'undefined'"); - }, - ); - }); - }); - - for (const { networkType } of INFURA_NETWORKS) { - describe(`when the type in the provider config is "${networkType}"`, () => { - it(`creates a network client for the ${networkType} Infura network, capturing the resulting provider`, async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'test_method', - params: [], - }, - response: { - result: 'test response', - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - - await controller.initializeProvider(); - - expect(createNetworkClientMock).toHaveBeenCalledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }); - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider, 'Provider is not set'); - const promisifiedSendAsync = promisify(provider.sendAsync).bind( - provider, - ); - const { result } = await promisifiedSendAsync({ - id: 1, - jsonrpc: '2.0', - method: 'test_method', - params: [], - }); - expect(result).toBe('test response'); - }, - ); - }); - - lookupNetworkTests({ - expectedProviderConfig: buildProviderConfig({ type: networkType }), - initialState: { - providerConfig: buildProviderConfig({ type: networkType }), - }, - operation: async (controller: NetworkController) => { - await controller.initializeProvider(); - }, - }); - }); - } - - describe(`when the type in the provider configuration is "rpc"`, () => { - describe('if chainId and rpcUrl are present in the provider config', () => { - it('creates a network client for a custom RPC endpoint using the provider config, capturing the resulting provider', async () => { - await withController( - { - state: { - providerConfig: { - type: NetworkType.rpc, - chainId: toHex(1337), - rpcUrl: 'http://example.com', - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'test_method', - params: [], - }, - response: { - result: 'test response', - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - - await controller.initializeProvider(); - - expect(createNetworkClientMock).toHaveBeenCalledWith({ - chainId: toHex(1337), - rpcUrl: 'http://example.com', - type: NetworkClientType.Custom, - }); - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider, 'Provider is not set'); - const promisifiedSendAsync = promisify(provider.sendAsync).bind( - provider, - ); - const { result } = await promisifiedSendAsync({ - id: 1, - jsonrpc: '2.0', - method: 'test_method', - params: [], - }); - expect(result).toBe('test response'); - }, - ); - }); - - lookupNetworkTests({ - expectedProviderConfig: buildProviderConfig({ - type: NetworkType.rpc, - }), - initialState: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - }), - }, - operation: async (controller: NetworkController) => { - await controller.initializeProvider(); - }, - }); - }); - - describe('if chainId is missing from the provider config', () => { - it('throws', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - chainId: undefined, - }), - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - - await expect(() => - controller.initializeProvider(), - ).rejects.toThrow( - 'chainId must be provided for custom RPC endpoints', - ); - }, - ); - }); - - it('does not create a network client or capture a provider', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - chainId: undefined, - }), - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - - try { - await controller.initializeProvider(); - } catch { - // ignore the error - } - - expect(createNetworkClientMock).not.toHaveBeenCalled(); - const { provider, blockTracker } = - controller.getProviderAndBlockTracker(); - expect(provider).toBeNull(); - expect(blockTracker).toBeNull(); - }, - ); - }); - }); - - describe('if rpcUrl is missing from the provider config', () => { - it('throws', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - rpcUrl: undefined, - }), - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - - await expect(() => - controller.initializeProvider(), - ).rejects.toThrow( - 'rpcUrl must be provided for custom RPC endpoints', - ); - }, - ); - }); - - it('does not create a network client or capture a provider', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - rpcUrl: undefined, - }), - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - - try { - await controller.initializeProvider(); - } catch { - // ignore the error - } - - expect(createNetworkClientMock).not.toHaveBeenCalled(); - const { provider, blockTracker } = - controller.getProviderAndBlockTracker(); - expect(provider).toBeNull(); - expect(blockTracker).toBeNull(); - }, - ); - }); - }); - }); - }); - - describe('getProviderAndBlockTracker', () => { - it('returns objects that proxy to the provider and block tracker as long as the provider has been initialized', async () => { - await withController(async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); - - const { provider, blockTracker } = - controller.getProviderAndBlockTracker(); - - expect(provider).toHaveProperty('sendAsync'); - expect(blockTracker).toHaveProperty('checkForLatestBlock'); - }); - }); - - it("returns null for both the provider and block tracker if the provider hasn't been initialized yet", async () => { - await withController(async ({ controller }) => { - const { provider, blockTracker } = - controller.getProviderAndBlockTracker(); - - expect(provider).toBeNull(); - expect(blockTracker).toBeNull(); - }); - }); - - for (const { networkType } of INFURA_NETWORKS) { - describe(`when the type in the provider configuration is changed to "${networkType}"`, () => { - it(`returns a provider object that was pointed to another network before the switch and is pointed to "${networkType}" afterward`, async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'test', - }, - response: { - result: 'test response 1', - }, - }, - ]), - buildFakeProvider([ - { - request: { - method: 'test', - }, - response: { - result: 'test response 2', - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - chainId: '0x1337', - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider, 'Provider is somehow unset'); - - const promisifiedSendAsync1 = promisify(provider.sendAsync).bind( - provider, - ); - const response1 = await promisifiedSendAsync1({ - id: '1', - jsonrpc: '2.0', - method: 'test', - }); - expect(response1.result).toBe('test response 1'); - - await controller.setProviderType(networkType); - const promisifiedSendAsync2 = promisify(provider.sendAsync).bind( - provider, - ); - const response2 = await promisifiedSendAsync2({ - id: '2', - jsonrpc: '2.0', - method: 'test', - }); - expect(response2.result).toBe('test response 2'); - }, - ); - }); - }); - } - - describe('when the type in the provider configuration is changed to "rpc"', () => { - it('returns a provider object that was pointed to another network before the switch and is pointed to the new network', async () => { - await withController( - { - state: { - providerConfig: { - type: 'goerli', - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', - }, - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'ABC', - id: 'testNetworkConfigurationId', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'test', - }, - response: { - result: 'test response 1', - }, - }, - ]), - buildFakeProvider([ - { - request: { - method: 'test', - }, - response: { - result: 'test response 2', - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - chainId: '0x1337', - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider, 'Provider is somehow unset'); - - const promisifiedSendAsync1 = promisify(provider.sendAsync).bind( - provider, - ); - const response1 = await promisifiedSendAsync1({ - id: '1', - jsonrpc: '2.0', - method: 'test', - }); - expect(response1.result).toBe('test response 1'); - - await controller.setActiveNetwork('testNetworkConfigurationId'); - const promisifiedSendAsync2 = promisify(provider.sendAsync).bind( - provider, - ); - const response2 = await promisifiedSendAsync2({ - id: '2', - jsonrpc: '2.0', - method: 'test', - }); - expect(response2.result).toBe('test response 2'); - }, - ); - }); - }); - }); - - describe('lookupNetwork', () => { - describe('if a provider has not been set', () => { - it('does not change network in state', async () => { - await withController(async ({ controller }) => { - const promiseForNetworkChanges = waitForStateChanges({ - controller, - propertyPath: ['networkId'], - }); - - await controller.lookupNetwork(); - - await expect(promiseForNetworkChanges).toNeverResolve(); - }); - }); - }); - - [NetworkType.mainnet, NetworkType.goerli, NetworkType.sepolia].forEach( - (networkType) => { - describe(`when the provider config in state contains a network type of "${networkType}"`, () => { - describe('if the network was switched after the net_version request started but before it completed', () => { - it('stores the network status of the second network, not the first', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ type: networkType }), - networkConfigurations: { - testNetworkConfigurationId: { - id: 'testNetworkConfigurationId', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'ABC', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - beforeCompleting: () => { - // Intentionally not awaited because don't want this to - // block the `net_version` request - controller.setActiveNetwork( - 'testNetworkConfigurationId', - ); - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'net_version', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - expect(controller.store.getState().networkStatus).toBe( - 'available', - ); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkStatus).toBe( - 'unknown', - ); - }, - ); - }); - - it('stores the ID of the second network, not the first', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ type: networkType }), - networkConfigurations: { - testNetworkConfigurationId: { - id: 'testNetworkConfigurationId', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'ABC', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - beforeCompleting: async () => { - // Intentionally not awaited because don't want this to - // block the `net_version` request - controller.setActiveNetwork( - 'testNetworkConfigurationId', - ); - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'net_version', - }, - response: { - result: '2', - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkId).toBe('1'); - - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkId).toBe('2'); - }, - ); - }); - - it('stores the EIP-1559 support of the second network, not the first', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ type: networkType }), - networkConfigurations: { - testNetworkConfigurationId: { - id: 'testNetworkConfigurationId', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'ABC', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - beforeCompleting: () => { - // Intentionally not awaited because don't want this to - // block the `net_version` request - controller.setActiveNetwork( - 'testNetworkConfigurationId', - ); - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'net_version', - }, - response: { - result: '2', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - expect( - controller.store.getState().networkDetails, - ).toStrictEqual({ - EIPS: { - 1559: true, - }, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect( - controller.store.getState().networkDetails, - ).toStrictEqual({ - EIPS: { - 1559: false, - }, - }); - }, - ); - }); - - it('emits infuraIsUnblocked, not infuraIsBlocked, assuming that the first network was blocked', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ type: networkType }), - networkConfigurations: { - testNetworkConfigurationId: { - id: 'testNetworkConfigurationId', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'ABC', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller, messenger }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - beforeCompleting: () => { - // Intentionally not awaited because don't want this to - // block the `net_version` request - controller.setActiveNetwork( - 'testNetworkConfigurationId', - ); - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - const promiseForInfuraIsUnblockedEvents = - waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - }); - const promiseForNoInfuraIsBlockedEvents = - waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - count: 0, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - await expect( - promiseForInfuraIsUnblockedEvents, - ).toBeFulfilled(); - await expect( - promiseForNoInfuraIsBlockedEvents, - ).toBeFulfilled(); - }, - ); - }); - }); - - describe('if the network was switched after the eth_getBlockByNumber request started but before it completed', () => { - it('stores the network status of the second network, not the first', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ type: networkType }), - networkConfigurations: { - testNetworkConfigurationId: { - id: 'testNetworkConfigurationId', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'ABC', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - beforeCompleting: () => { - // Intentionally not awaited because don't want this to - // block the `net_version` request - controller.setActiveNetwork( - 'testNetworkConfigurationId', - ); - }, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'net_version', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - expect(controller.store.getState().networkStatus).toBe( - 'available', - ); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkStatus).toBe( - 'unknown', - ); - }, - ); - }); - - it('stores the ID of the second network, not the first', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ type: networkType }), - networkConfigurations: { - testNetworkConfigurationId: { - id: 'testNetworkConfigurationId', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'ABC', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - beforeCompleting: async () => { - // Intentionally not awaited because don't want this to - // block the `net_version` request - controller.setActiveNetwork( - 'testNetworkConfigurationId', - ); - }, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'net_version', - }, - response: { - result: '2', - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkId).toBe('1'); - - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkId).toBe('2'); - }, - ); - }); - - it('stores the EIP-1559 support of the second network, not the first', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ type: networkType }), - networkConfigurations: { - testNetworkConfigurationId: { - id: 'testNetworkConfigurationId', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'ABC', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - beforeCompleting: () => { - // Intentionally not awaited because don't want this to - // block the `net_version` request - controller.setActiveNetwork( - 'testNetworkConfigurationId', - ); - }, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'net_version', - }, - response: { - result: '2', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - expect( - controller.store.getState().networkDetails, - ).toStrictEqual({ - EIPS: { - 1559: true, - }, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect( - controller.store.getState().networkDetails, - ).toStrictEqual({ - EIPS: { - 1559: false, - }, - }); - }, - ); - }); - - it('emits infuraIsUnblocked, not infuraIsBlocked, assuming that the first network was blocked', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ type: networkType }), - networkConfigurations: { - testNetworkConfigurationId: { - id: 'testNetworkConfigurationId', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'ABC', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller, messenger }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - beforeCompleting: () => { - // Intentionally not awaited because don't want this to - // block the `net_version` request - controller.setActiveNetwork( - 'testNetworkConfigurationId', - ); - }, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - const promiseForInfuraIsUnblockedEvents = - waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - }); - const promiseForNoInfuraIsBlockedEvents = - waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - count: 0, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - await expect( - promiseForInfuraIsUnblockedEvents, - ).toBeFulfilled(); - await expect( - promiseForNoInfuraIsBlockedEvents, - ).toBeFulfilled(); - }, - ); - }); - }); - - lookupNetworkTests({ - expectedProviderConfig: buildProviderConfig({ type: networkType }), - initialState: { - providerConfig: buildProviderConfig({ type: networkType }), - }, - operation: async (controller) => { - await controller.lookupNetwork(); - }, - }); - }); - }, - ); - - describe(`when the provider config in state contains a network type of "rpc"`, () => { - describe('if the network was switched after the net_version request started but before it completed', () => { - it('stores the network status of the second network, not the first', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - beforeCompleting: () => { - // Intentionally not awaited because don't want this to - // block the `net_version` request - controller.setProviderType(NetworkType.goerli); - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'net_version', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - expect(controller.store.getState().networkStatus).toBe( - 'available', - ); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkStatus).toBe('unknown'); - }, - ); - }); - - it('stores the ID of the second network, not the first', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - beforeCompleting: async () => { - // Intentionally not awaited because don't want this to - // block the `net_version` request - controller.setProviderType(NetworkType.goerli); - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'net_version', - }, - response: { - result: '2', - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkId).toBe('1'); - - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkId).toBe('2'); - }, - ); - }); - - it('stores the EIP-1559 support of the second network, not the first', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - beforeCompleting: () => { - // Intentionally not awaited because don't want this to - // block the `net_version` request - controller.setProviderType(NetworkType.goerli); - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'net_version', - }, - response: { - result: '2', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: true, - }, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: false, - }, - }); - }, - ); - }); - - it('emits infuraIsBlocked, not infuraIsUnblocked, if the second network was blocked and the first network was not', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller, messenger }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - beforeCompleting: () => { - // Intentionally not awaited because don't want this to - // block the `net_version` request - controller.setProviderType(NetworkType.goerli); - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - const promiseForNoInfuraIsUnblockedEvents = - waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - count: 0, - }); - const promiseForInfuraIsBlockedEvents = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - await expect(promiseForNoInfuraIsUnblockedEvents).toBeFulfilled(); - await expect(promiseForInfuraIsBlockedEvents).toBeFulfilled(); - }, - ); - }); - }); - - describe('if the network was switched after the eth_getBlockByNumber request started but before it completed', () => { - it('stores the network status of the second network, not the first', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - beforeCompleting: () => { - // Intentionally not awaited because don't want this to - // block the `net_version` request - controller.setProviderType(NetworkType.goerli); - }, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'net_version', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - expect(controller.store.getState().networkStatus).toBe( - 'available', - ); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkStatus).toBe('unknown'); - }, - ); - }); - - it('stores the ID of the second network, not the first', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - beforeCompleting: async () => { - // Intentionally not awaited because don't want this to - // block the `net_version` request - controller.setProviderType(NetworkType.goerli); - }, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'net_version', - }, - response: { - result: '2', - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkId).toBe('1'); - - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkId).toBe('2'); - }, - ); - }); - - it('stores the EIP-1559 support of the second network, not the first', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - beforeCompleting: () => { - // Intentionally not awaited because don't want this to - // block the `net_version` request - controller.setProviderType(NetworkType.goerli); - }, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'net_version', - }, - response: { - result: '2', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: true, - }, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: false, - }, - }); - }, - ); - }); - - it('emits infuraIsBlocked, not infuraIsUnblocked, if the second network was blocked and the first network was not', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller, messenger }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - beforeCompleting: () => { - // Intentionally not awaited because don't want this to - // block the `net_version` request - controller.setProviderType(NetworkType.goerli); - }, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - const promiseForNoInfuraIsUnblockedEvents = - waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - count: 0, - }); - const promiseForInfuraIsBlockedEvents = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - await expect(promiseForNoInfuraIsUnblockedEvents).toBeFulfilled(); - await expect(promiseForInfuraIsBlockedEvents).toBeFulfilled(); - }, - ); - }); - }); - - lookupNetworkTests({ - expectedProviderConfig: buildProviderConfig({ - type: NetworkType.rpc, - }), - initialState: { - providerConfig: buildProviderConfig({ type: NetworkType.rpc }), - }, - operation: async (controller) => { - await controller.lookupNetwork(); - }, - }); - }); - }); - - describe('setProviderType', () => { - for (const { - networkType, - chainId, - ticker, - blockExplorerUrl, - } of INFURA_NETWORKS) { - describe(`given a network type of "${networkType}"`, () => { - refreshNetworkTests({ - expectedProviderConfig: buildProviderConfig({ - type: networkType, - }), - operation: async (controller) => { - await controller.setProviderType(networkType); - }, - }); - }); - - it(`overwrites the provider configuration using a predetermined chainId, ticker, and blockExplorerUrl for "${networkType}", clearing id, rpcUrl, and nickname`, async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - nickname: 'test-chain', - ticker: 'TEST', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer.com', - }, - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - await controller.setProviderType(networkType); - - expect(controller.store.getState().providerConfig).toStrictEqual({ - type: networkType, - rpcUrl: undefined, - chainId, - ticker, - nickname: undefined, - rpcPrefs: { blockExplorerUrl }, - id: undefined, - }); - }, - ); - }); - } - - describe('given a network type of "rpc"', () => { - it('throws because there is no way to switch to a custom RPC endpoint using this method', async () => { - await withController( - { - state: { - providerConfig: { - type: NetworkType.rpc, - rpcUrl: 'http://somethingexisting.com', - chainId: toHex(99999), - ticker: 'something existing', - nickname: 'something existing', - }, - }, - }, - async ({ controller }) => { - await expect(() => - controller.setProviderType(NetworkType.rpc), - ).rejects.toThrow( - 'NetworkController - cannot call "setProviderType" with type "rpc". Use "setActiveNetwork"', - ); - }, - ); - }); - - it("doesn't set a provider", async () => { - await withController(async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - - try { - await controller.setProviderType(NetworkType.rpc); - } catch { - // catch the rejection (it is tested above) - } - - expect(createNetworkClientMock).not.toHaveBeenCalled(); - expect(controller.getProviderAndBlockTracker().provider).toBeNull(); - }); - }); - - it('does not update networkDetails.EIPS in state', async () => { - await withController(async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: { - baseFeePerGas: '0x1', - }, - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - - try { - await controller.setProviderType(NetworkType.rpc); - } catch { - // catch the rejection (it is tested above) - } - - expect( - controller.store.getState().networkDetails.EIPS[1559], - ).toBeUndefined(); - }); - }); - }); - - describe('given an invalid Infura network name', () => { - it('throws', async () => { - await withController(async ({ controller }) => { - await expect(() => - controller.setProviderType('invalid-infura-network'), - ).rejects.toThrow( - new Error('Unknown Infura provider type "invalid-infura-network".'), - ); - }); - }); - }); - }); - - describe('setActiveNetwork', () => { - refreshNetworkTests({ - expectedProviderConfig: { - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - nickname: 'something existing', - id: 'testNetworkConfigurationId', - rpcPrefs: undefined, - type: NetworkType.rpc, - }, - initialState: { - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - nickname: 'something existing', - id: 'testNetworkConfigurationId', - rpcPrefs: undefined, - }, - }, - }, - operation: async (controller) => { - await controller.setActiveNetwork('testNetworkConfigurationId'); - }, - }); - - describe('if the given ID does not match a network configuration in networkConfigurations', () => { - it('throws', async () => { - await withController( - { - state: { - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - id: 'testNetworkConfigurationId', - }, - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - await expect(() => - controller.setActiveNetwork('invalidNetworkConfigurationId'), - ).rejects.toThrow( - new Error( - 'networkConfigurationId invalidNetworkConfigurationId does not match a configured networkConfiguration', - ), - ); - }, - ); - }); - }); - - describe('if the network config does not contain an RPC URL', () => { - it('throws', async () => { - await withController( - // @ts-expect-error RPC URL intentionally omitted - { - state: { - providerConfig: { - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - nickname: 'something existing', - rpcPrefs: undefined, - }, - networkConfigurations: { - testNetworkConfigurationId1: { - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - nickname: 'something existing', - id: 'testNetworkConfigurationId1', - rpcPrefs: undefined, - }, - testNetworkConfigurationId2: { - rpcUrl: undefined, - chainId: toHex(222), - ticker: 'something existing', - nickname: 'something existing', - id: 'testNetworkConfigurationId2', - rpcPrefs: undefined, - }, - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - - await expect(() => - controller.setActiveNetwork('testNetworkConfigurationId2'), - ).rejects.toThrow( - 'rpcUrl must be provided for custom RPC endpoints', - ); - - expect(createNetworkClientMock).not.toHaveBeenCalled(); - const { provider, blockTracker } = - controller.getProviderAndBlockTracker(); - expect(provider).toBeNull(); - expect(blockTracker).toBeNull(); - }, - ); - }); - }); - - describe('if the network config does not contain a chain ID', () => { - it('throws', async () => { - await withController( - // @ts-expect-error chain ID intentionally omitted - { - state: { - providerConfig: { - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - nickname: 'something existing', - rpcPrefs: undefined, - }, - networkConfigurations: { - testNetworkConfigurationId1: { - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - nickname: 'something existing', - id: 'testNetworkConfigurationId1', - rpcPrefs: undefined, - }, - testNetworkConfigurationId2: { - rpcUrl: 'http://somethingexisting.com', - chainId: undefined, - ticker: 'something existing', - nickname: 'something existing', - id: 'testNetworkConfigurationId2', - rpcPrefs: undefined, - }, - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - - await expect(() => - controller.setActiveNetwork('testNetworkConfigurationId2'), - ).rejects.toThrow( - 'chainId must be provided for custom RPC endpoints', - ); - - expect(createNetworkClientMock).not.toHaveBeenCalled(); - const { provider, blockTracker } = - controller.getProviderAndBlockTracker(); - expect(provider).toBeNull(); - expect(blockTracker).toBeNull(); - }, - ); - }); - }); - - it('overwrites the provider configuration given a networkConfigurationId that matches a configured networkConfiguration', async () => { - await withController( - { - state: { - networkConfigurations: { - testNetworkConfiguration: { - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - nickname: 'something existing', - id: 'testNetworkConfigurationId', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer-2.com', - }, - }, - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClient); - - await controller.setActiveNetwork('testNetworkConfiguration'); - - expect(controller.store.getState().providerConfig).toStrictEqual({ - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - nickname: 'something existing', - id: 'testNetworkConfigurationId', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer-2.com', - }, - }); - }, - ); - }); - }); - - describe('getEIP1559Compatibility', () => { - describe('if no provider has been set yet', () => { - it('does not make any state changes', async () => { - await withController(async ({ controller }) => { - const promiseForNoStateChanges = waitForStateChanges({ - controller, - count: 0, - operation: async () => { - await controller.getEIP1559Compatibility(); - }, - }); - - expect(Boolean(promiseForNoStateChanges)).toBe(true); - }); - }); - - it('returns false', async () => { - await withController(async ({ controller }) => { - const isEIP1559Compatible = - await controller.getEIP1559Compatibility(); - - expect(isEIP1559Compatible).toBe(false); - }); - }); - }); - - describe('if a provider has been set but networkDetails.EIPS in state already has a "1559" property', () => { - it('does not make any state changes', async () => { - await withController( - { - state: { - networkDetails: { - EIPS: { - 1559: true, - }, - }, - }, - }, - async ({ controller }) => { - setFakeProvider(controller, { - stubLookupNetworkWhileSetting: true, - }); - const promiseForNoStateChanges = waitForStateChanges({ - controller, - count: 0, - operation: async () => { - await controller.getEIP1559Compatibility(); - }, - }); - - expect(Boolean(promiseForNoStateChanges)).toBe(true); - }, - ); - }); - - it('returns the value of the "1559" property', async () => { - await withController( - { - state: { - networkDetails: { - EIPS: { - 1559: true, - }, - }, - }, - }, - async ({ controller }) => { - setFakeProvider(controller, { - stubLookupNetworkWhileSetting: true, - }); - const isEIP1559Compatible = - await controller.getEIP1559Compatibility(); - - expect(isEIP1559Compatible).toBe(true); - }, - ); - }); - }); - - describe('if a provider has been set and networkDetails.EIPS in state does not already have a "1559" property', () => { - describe('if the request for the latest block is successful', () => { - describe('if the latest block has a "baseFeePerGas" property', () => { - it('sets the "1559" property to true', async () => { - await withController(async ({ controller }) => { - setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - await controller.getEIP1559Compatibility(); - - expect( - controller.store.getState().networkDetails.EIPS[1559], - ).toBe(true); - }); - }); - - it('returns true', async () => { - await withController(async ({ controller }) => { - setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const isEIP1559Compatible = - await controller.getEIP1559Compatibility(); - - expect(isEIP1559Compatible).toBe(true); - }); - }); - }); - - describe('if the latest block does not have a "baseFeePerGas" property', () => { - it('sets the "1559" property to false', async () => { - await withController(async ({ controller }) => { - setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - await controller.getEIP1559Compatibility(); - - expect( - controller.store.getState().networkDetails.EIPS[1559], - ).toBe(false); - }); - }); - - it('returns false', async () => { - await withController(async ({ controller }) => { - setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const isEIP1559Compatible = - await controller.getEIP1559Compatibility(); - - expect(isEIP1559Compatible).toBe(false); - }); - }); - }); - - describe('if the request for the latest block responds with null', () => { - it('sets the "1559" property to false', async () => { - await withController(async ({ controller }) => { - setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: null, - }, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - await controller.getEIP1559Compatibility(); - - expect( - controller.store.getState().networkDetails.EIPS[1559], - ).toBe(false); - }); - }); - - it('returns false', async () => { - await withController(async ({ controller }) => { - setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: null, - }, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const isEIP1559Compatible = - await controller.getEIP1559Compatibility(); - - expect(isEIP1559Compatible).toBe(false); - }); - }); - }); - }); - - describe('if the request for the latest block is unsuccessful', () => { - it('does not make any state changes', async () => { - await withController(async ({ controller }) => { - setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: GENERIC_JSON_RPC_ERROR, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const promiseForNoStateChanges = waitForStateChanges({ - controller, - count: 0, - operation: async () => { - try { - await controller.getEIP1559Compatibility(); - } catch (error) { - // ignore error - } - }, - }); - - expect(Boolean(promiseForNoStateChanges)).toBe(true); - }); - }); - }); - }); - }); - - describe('resetConnection', () => { - [NetworkType.mainnet, NetworkType.goerli, NetworkType.sepolia].forEach( - (networkType) => { - describe(`when the type in the provider configuration is "${networkType}"`, () => { - refreshNetworkTests({ - expectedProviderConfig: buildProviderConfig({ type: networkType }), - initialState: { - providerConfig: buildProviderConfig({ type: networkType }), - }, - operation: async (controller) => { - await controller.resetConnection(); - }, - }); - }); - }, - ); - - describe(`when the type in the provider configuration is "rpc"`, () => { - refreshNetworkTests({ - expectedProviderConfig: buildProviderConfig({ - type: NetworkType.rpc, - }), - initialState: { - providerConfig: buildProviderConfig({ type: NetworkType.rpc }), - }, - operation: async (controller) => { - await controller.resetConnection(); - }, - }); - }); - }); - - describe('NetworkController:getProviderConfig action', () => { - it('returns the provider config in state', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.mainnet, - }), - }, - }, - async ({ messenger }) => { - const providerConfig = await messenger.call( - 'NetworkController:getProviderConfig', - ); - - expect(providerConfig).toStrictEqual( - buildProviderConfig({ - type: NetworkType.mainnet, - }), - ); - }, - ); - }); - }); - - describe('NetworkController:getEthQuery action', () => { - it('returns a EthQuery object that can be used to make requests to the currently selected network', async () => { - await withController(async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'test_method', - params: [], - }, - response: { - result: 'test response', - }, - }, - ], - }); - - const ethQuery = messenger.call('NetworkController:getEthQuery'); - assert(ethQuery, 'ethQuery is not set'); - - const promisifiedSendAsync = promisify(ethQuery.sendAsync).bind( - ethQuery, - ); - const result = await promisifiedSendAsync({ - id: 1, - jsonrpc: '2.0', - method: 'test_method', - params: [], - }); - expect(result).toBe('test response'); - }); - }); - - it('returns undefined if the provider has not been set yet', async () => { - await withController(({ messenger }) => { - const ethQuery = messenger.call('NetworkController:getEthQuery'); - - expect(ethQuery).toBeUndefined(); - }); - }); - }); - - describe('upsertNetworkConfiguration', () => { - it('adds the given network configuration when its rpcURL does not match an existing configuration', async () => { - uuidV4Mock.mockImplementationOnce(() => 'network-configuration-id-1'); - - await withController(async ({ controller }) => { - const rpcUrlNetwork = { - chainId: toHex(9999), - rpcUrl: 'https://test-rpc.com', - ticker: 'RPC', - }; - - expect(controller.store.getState().networkConfigurations).toStrictEqual( - {}, - ); - - await controller.upsertNetworkConfiguration(rpcUrlNetwork, { - referrer: 'https://test-dapp.com', - source: 'dapp', - }); - - expect( - Object.values(controller.store.getState().networkConfigurations), - ).toStrictEqual( - expect.arrayContaining([ - { - ...rpcUrlNetwork, - nickname: undefined, - rpcPrefs: undefined, - id: 'network-configuration-id-1', - }, - ]), - ); - }); - }); - - it('update a network configuration when the configuration being added has an rpcURL that matches an existing configuration', async () => { - await withController( - { - state: { - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://rpc-url.com', - ticker: 'old_rpc_ticker', - nickname: 'old_rpc_nickname', - rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: toHex(1), - id: 'testNetworkConfigurationId', - }, - }, - }, - }, - async ({ controller }) => { - await controller.upsertNetworkConfiguration( - { - rpcUrl: 'https://rpc-url.com', - ticker: 'new_rpc_ticker', - nickname: 'new_rpc_nickname', - rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, - chainId: toHex(1), - }, - { referrer: 'https://test-dapp.com', source: 'dapp' }, - ); - expect( - Object.values(controller.store.getState().networkConfigurations), - ).toStrictEqual( - expect.arrayContaining([ - { - rpcUrl: 'https://rpc-url.com', - nickname: 'new_rpc_nickname', - ticker: 'new_rpc_ticker', - rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, - chainId: toHex(1), - id: 'testNetworkConfigurationId', - }, - ]), - ); - }, - ); - }); - - it('throws if the given chain ID is not a 0x-prefixed hex number', async () => { - const invalidChainId = '1'; - await withController(async ({ controller }) => { - await expect(async () => - controller.upsertNetworkConfiguration( - { - // @ts-expect-error Intentionally invalid - chainId: invalidChainId, - nickname: 'RPC', - rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, - rpcUrl: 'rpc_url', - ticker: 'RPC', - }, - { - referrer: 'https://test-dapp.com', - source: 'dapp', - }, - ), - ).rejects.toThrow( - new Error( - `Invalid chain ID "${invalidChainId}": invalid hex string.`, - ), - ); - }); - }); - - it('throws if the given chain ID is greater than the maximum allowed ID', async () => { - await withController(async ({ controller }) => { - await expect(async () => - controller.upsertNetworkConfiguration( - { - chainId: '0xFFFFFFFFFFFFFFFF', - nickname: 'RPC', - rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, - rpcUrl: 'rpc_url', - ticker: 'RPC', - }, - { - referrer: 'https://test-dapp.com', - source: 'dapp', - }, - ), - ).rejects.toThrow( - new Error( - 'Invalid chain ID "0xFFFFFFFFFFFFFFFF": numerical value greater than max safe value.', - ), - ); - }); - }); - - it('throws if the no (or a falsy) rpcUrl is passed', async () => { - await withController(async ({ controller }) => { - await expect(() => - controller.upsertNetworkConfiguration( - /* @ts-expect-error We are intentionally passing bad input. */ - { - chainId: toHex(9999), - nickname: 'RPC', - rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, - ticker: 'RPC', - }, - { - referrer: 'https://test-dapp.com', - source: 'dapp', - }, - ), - ).rejects.toThrow( - new Error( - 'An rpcUrl is required to add or update network configuration', - ), - ); - }); - }); - - it('throws if rpcUrl passed is not a valid Url', async () => { - await withController(async ({ controller }) => { - await expect(async () => - controller.upsertNetworkConfiguration( - { - chainId: toHex(9999), - nickname: 'RPC', - rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, - ticker: 'RPC', - rpcUrl: 'test', - }, - { - referrer: 'https://test-dapp.com', - source: 'dapp', - }, - ), - ).rejects.toThrow(new Error('rpcUrl must be a valid URL')); - }); - }); - - it('throws if the no (or a falsy) ticker is passed', async () => { - await withController(async ({ controller }) => { - await expect(async () => - controller.upsertNetworkConfiguration( - // @ts-expect-error - we want to test the case where no ticker is present. - { - chainId: toHex(5), - nickname: 'RPC', - rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, - rpcUrl: 'https://mock-rpc-url', - }, - { - referrer: 'https://test-dapp.com', - source: 'dapp', - }, - ), - ).rejects.toThrow( - new Error( - 'A ticker is required to add or update networkConfiguration', - ), - ); - }); - }); - - it('throws if an options object is not passed as a second argument', async () => { - await withController(async ({ controller }) => { - await expect(async () => - // @ts-expect-error - we want to test the case where no second arg is passed. - controller.upsertNetworkConfiguration({ - chainId: toHex(5), - nickname: 'RPC', - rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, - rpcUrl: 'https://mock-rpc-url', - }), - ).rejects.toThrow('Cannot read properties of undefined'); - }); - }); - - it('throws if referrer and source arguments are not passed', async () => { - uuidV4Mock.mockImplementationOnce(() => 'networkConfigurationId'); - const trackEventSpy = jest.fn(); - await withController( - { - state: { - providerConfig: { - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - id: 'testNetworkConfigurationId', - }, - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - id: 'testNetworkConfigurationId', - nickname: undefined, - rpcPrefs: undefined, - }, - }, - }, - trackMetaMetricsEvent: trackEventSpy, - }, - async ({ controller }) => { - const newNetworkConfiguration = { - rpcUrl: 'https://new-chain-rpc-url', - chainId: toHex(222), - ticker: 'NEW', - nickname: 'new-chain', - rpcPrefs: { blockExplorerUrl: 'https://block-explorer' }, - }; - - await expect(async () => - // @ts-expect-error - we want to test the case where the options object is empty. - controller.upsertNetworkConfiguration(newNetworkConfiguration, {}), - ).rejects.toThrow( - 'referrer and source are required arguments for adding or updating a network configuration', - ); - }, - ); - }); - - it('should add the given network if all required properties are present but nither rpcPrefs nor nickname properties are passed', async () => { - uuidV4Mock.mockImplementationOnce(() => 'networkConfigurationId'); - await withController( - { - state: { - networkConfigurations: {}, - }, - }, - async ({ controller }) => { - const rpcUrlNetwork = { - chainId: toHex(1), - rpcUrl: 'https://test-rpc-url', - ticker: 'test_ticker', - }; - - await controller.upsertNetworkConfiguration(rpcUrlNetwork, { - referrer: 'https://test-dapp.com', - source: 'dapp', - }); - - expect( - Object.values(controller.store.getState().networkConfigurations), - ).toStrictEqual( - expect.arrayContaining([ - { - ...rpcUrlNetwork, - nickname: undefined, - rpcPrefs: undefined, - id: 'networkConfigurationId', - }, - ]), - ); - }, - ); - }); - - it('adds new networkConfiguration to networkController store, but only adds valid properties (rpcUrl, chainId, ticker, nickname, rpcPrefs) and fills any missing properties from this list as undefined', async function () { - uuidV4Mock.mockImplementationOnce(() => 'networkConfigurationId'); - await withController( - { - state: { - networkConfigurations: {}, - }, - }, - async ({ controller }) => { - const rpcUrlNetwork = { - chainId: toHex(1), - rpcUrl: 'https://test-rpc-url', - ticker: 'test_ticker', - invalidKey: 'new-chain', - invalidKey2: {}, - }; - - await controller.upsertNetworkConfiguration(rpcUrlNetwork, { - referrer: 'https://test-dapp.com', - source: 'dapp', - }); - - expect( - Object.values(controller.store.getState().networkConfigurations), - ).toStrictEqual( - expect.arrayContaining([ - { - chainId: toHex(1), - rpcUrl: 'https://test-rpc-url', - ticker: 'test_ticker', - nickname: undefined, - rpcPrefs: undefined, - id: 'networkConfigurationId', - }, - ]), - ); - }, - ); - }); - - it('should add the given network configuration if its rpcURL does not match an existing configuration without changing or overwriting other configurations', async () => { - uuidV4Mock.mockImplementationOnce(() => 'networkConfigurationId2'); - await withController( - { - state: { - networkConfigurations: { - networkConfigurationId: { - rpcUrl: 'https://test-rpc-url', - ticker: 'ticker', - nickname: 'nickname', - rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: toHex(1), - id: 'networkConfigurationId', - }, - }, - }, - }, - async ({ controller }) => { - const rpcUrlNetwork = { - chainId: toHex(1), - nickname: 'RPC', - rpcPrefs: undefined, - rpcUrl: 'https://test-rpc-url-2', - ticker: 'RPC', - }; - - await controller.upsertNetworkConfiguration(rpcUrlNetwork, { - referrer: 'https://test-dapp.com', - source: 'dapp', - }); - - expect( - Object.values(controller.store.getState().networkConfigurations), - ).toStrictEqual( - expect.arrayContaining([ - { - rpcUrl: 'https://test-rpc-url', - ticker: 'ticker', - nickname: 'nickname', - rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: toHex(1), - id: 'networkConfigurationId', - }, - { ...rpcUrlNetwork, id: 'networkConfigurationId2' }, - ]), - ); - }, - ); - }); - - it('should use the given configuration to update an existing network configuration that has a matching rpcUrl', async () => { - await withController( - { - state: { - networkConfigurations: { - networkConfigurationId: { - rpcUrl: 'https://test-rpc-url', - ticker: 'old_rpc_ticker', - nickname: 'old_rpc_chainName', - rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: toHex(1), - id: 'networkConfigurationId', - }, - }, - }, - }, - - async ({ controller }) => { - const updatedConfiguration = { - rpcUrl: 'https://test-rpc-url', - ticker: 'new_rpc_ticker', - nickname: 'new_rpc_chainName', - rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, - chainId: toHex(1), - }; - await controller.upsertNetworkConfiguration(updatedConfiguration, { - referrer: 'https://test-dapp.com', - source: 'dapp', - }); - expect( - Object.values(controller.store.getState().networkConfigurations), - ).toStrictEqual([ - { - rpcUrl: 'https://test-rpc-url', - nickname: 'new_rpc_chainName', - ticker: 'new_rpc_ticker', - rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, - chainId: toHex(1), - id: 'networkConfigurationId', - }, - ]); - }, - ); - }); - - it('should use the given configuration to update an existing network configuration that has a matching rpcUrl without changing or overwriting other networkConfigurations', async () => { - await withController( - { - state: { - networkConfigurations: { - networkConfigurationId: { - rpcUrl: 'https://test-rpc-url', - ticker: 'ticker', - nickname: 'nickname', - rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: toHex(1), - id: 'networkConfigurationId', - }, - networkConfigurationId2: { - rpcUrl: 'https://test-rpc-url-2', - ticker: 'ticker-2', - nickname: 'nickname-2', - rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: toHex(9999), - id: 'networkConfigurationId2', - }, - }, - }, - }, - async ({ controller }) => { - await controller.upsertNetworkConfiguration( - { - rpcUrl: 'https://test-rpc-url', - ticker: 'new-ticker', - nickname: 'new-nickname', - rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, - chainId: toHex(1), - }, - { - referrer: 'https://test-dapp.com', - source: 'dapp', - }, - ); - - expect( - Object.values(controller.store.getState().networkConfigurations), - ).toStrictEqual([ - { - rpcUrl: 'https://test-rpc-url', - ticker: 'new-ticker', - nickname: 'new-nickname', - rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, - chainId: toHex(1), - id: 'networkConfigurationId', - }, - { - rpcUrl: 'https://test-rpc-url-2', - ticker: 'ticker-2', - nickname: 'nickname-2', - rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: toHex(9999), - id: 'networkConfigurationId2', - }, - ]); - }, - ); - }); - - it('should add the given network and not set it to active if the setActive option is not passed (or a falsy value is passed)', async () => { - uuidV4Mock.mockImplementationOnce(() => 'networkConfigurationId'); - const originalProvider = { - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - id: 'testNetworkConfigurationId', - }; - await withController( - { - state: { - providerConfig: originalProvider, - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - id: 'testNetworkConfigurationId', - nickname: undefined, - rpcPrefs: undefined, - }, - }, - }, - }, - async ({ controller }) => { - const rpcUrlNetwork = { - chainId: toHex(222), - rpcUrl: 'https://test-rpc-url', - ticker: 'test_ticker', - }; - - await controller.upsertNetworkConfiguration(rpcUrlNetwork, { - referrer: 'https://test-dapp.com', - source: 'dapp', - }); - - expect(controller.store.getState().providerConfig).toStrictEqual( - originalProvider, - ); - }, - ); - }); - - it('should add the given network and set it to active if the setActive option is passed as true', async () => { - uuidV4Mock.mockImplementationOnce(() => 'networkConfigurationId'); - await withController( - { - state: { - providerConfig: { - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - id: 'testNetworkConfigurationId', - nickname: undefined, - rpcPrefs: undefined, - }, - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - id: 'testNetworkConfigurationId', - nickname: undefined, - rpcPrefs: undefined, - }, - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - const rpcUrlNetwork = { - rpcUrl: 'https://test-rpc-url', - chainId: toHex(222), - ticker: 'test_ticker', - }; - - await controller.upsertNetworkConfiguration(rpcUrlNetwork, { - setActive: true, - referrer: 'https://test-dapp.com', - source: 'dapp', - }); - - expect(controller.store.getState().providerConfig).toStrictEqual({ - type: 'rpc', - rpcUrl: 'https://test-rpc-url', - chainId: toHex(222), - ticker: 'test_ticker', - id: 'networkConfigurationId', - nickname: undefined, - rpcPrefs: undefined, - }); - }, - ); - }); - - it('adds new networkConfiguration to networkController store and calls to the metametrics event tracking with the correct values', async () => { - uuidV4Mock.mockImplementationOnce(() => 'networkConfigurationId'); - const trackEventSpy = jest.fn(); - await withController( - { - state: { - providerConfig: { - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - id: 'testNetworkConfigurationId', - nickname: undefined, - rpcPrefs: undefined, - }, - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - id: 'testNetworkConfigurationId', - nickname: undefined, - rpcPrefs: undefined, - }, - }, - }, - trackMetaMetricsEvent: trackEventSpy, - }, - async ({ controller }) => { - const newNetworkConfiguration = { - rpcUrl: 'https://new-chain-rpc-url', - chainId: toHex(222), - ticker: 'NEW', - nickname: 'new-chain', - rpcPrefs: { blockExplorerUrl: 'https://block-explorer' }, - }; - - await controller.upsertNetworkConfiguration(newNetworkConfiguration, { - referrer: 'https://test-dapp.com', - source: 'dapp', - }); - - expect( - Object.values(controller.store.getState().networkConfigurations), - ).toStrictEqual([ - { - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - id: 'testNetworkConfigurationId', - nickname: undefined, - rpcPrefs: undefined, - }, - { - ...newNetworkConfiguration, - id: 'networkConfigurationId', - }, - ]); - expect(trackEventSpy).toHaveBeenCalledWith({ - event: 'Custom Network Added', - category: 'Network', - referrer: { - url: 'https://test-dapp.com', - }, - properties: { - chain_id: toHex(222), - symbol: 'NEW', - source: 'dapp', - }, - }); - }, - ); - }); - }); - - describe('removeNetworkConfigurations', () => { - it('remove a network configuration', async () => { - const testNetworkConfigurationId = 'testNetworkConfigurationId'; - await withController( - { - state: { - networkConfigurations: { - [testNetworkConfigurationId]: { - rpcUrl: 'https://rpc-url.com', - ticker: 'old_rpc_ticker', - nickname: 'old_rpc_nickname', - rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: toHex(1337), - id: testNetworkConfigurationId, - }, - }, - }, - }, - async ({ controller }) => { - controller.removeNetworkConfiguration(testNetworkConfigurationId); - expect( - controller.store.getState().networkConfigurations, - ).toStrictEqual({}); - }, - ); - }); - - it('throws if the networkConfigurationId it is passed does not correspond to a network configuration in state', async () => { - const testNetworkConfigurationId = 'testNetworkConfigurationId'; - const invalidNetworkConfigurationId = 'invalidNetworkConfigurationId'; - await withController( - { - state: { - networkConfigurations: { - [testNetworkConfigurationId]: { - rpcUrl: 'https://rpc-url.com', - ticker: 'old_rpc_ticker', - nickname: 'old_rpc_nickname', - rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: toHex(1337), - id: testNetworkConfigurationId, - }, - }, - }, - }, - async ({ controller }) => { - expect(() => - controller.removeNetworkConfiguration( - invalidNetworkConfigurationId, - ), - ).toThrow( - `networkConfigurationId ${invalidNetworkConfigurationId} does not match a configured networkConfiguration`, - ); - }, - ); - }); - }); - - describe('rollbackToPreviousProvider', () => { - for (const { networkType } of INFURA_NETWORKS) { - describe(`if the previous provider configuration had a type of "${networkType}"`, () => { - it('emits networkWillChange', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - nickname: 'test network', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer.com', - }, - }, - }, - }, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.setActiveNetwork('testNetworkConfiguration'); - - const networkWillChange = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:networkWillChange', - operation: () => { - // Intentionally not awaited because we're capturing an event - // emitted partway through the operation - controller.rollbackToPreviousProvider(); - }, - }); - - await expect(networkWillChange).toBeFulfilled(); - }, - ); - }); - - it('emits networkDidChange', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - nickname: 'test network', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer.com', - }, - }, - }, - }, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.setActiveNetwork('testNetworkConfiguration'); - - const networkDidChange = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:networkDidChange', - operation: () => { - // Intentionally not awaited because we're capturing an event - // emitted partway through the operation - controller.rollbackToPreviousProvider(); - }, - }); - - await expect(networkDidChange).toBeFulfilled(); - }, - ); - }); - - it('overwrites the the current provider configuration with the previous provider configuration', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - nickname: 'test network', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer.com', - }, - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - expect(controller.store.getState().providerConfig).toStrictEqual({ - type: 'rpc', - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - nickname: 'test network', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer.com', - }, - }); - - await controller.rollbackToPreviousProvider(); - - expect(controller.store.getState().providerConfig).toStrictEqual( - buildProviderConfig({ - type: networkType, - }), - ); - }, - ); - }); - - it('resets the network status to "unknown" before updating the provider', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]), - buildFakeProvider(), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - expect(controller.store.getState().networkStatus).toBe( - 'available', - ); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - // We only care about the first state change, because it - // happens before networkDidChange - count: 1, - operation: () => { - // Intentionally not awaited because we want to check state - // while this operation is in-progress - controller.rollbackToPreviousProvider(); - }, - beforeResolving: () => { - expect(controller.store.getState().networkStatus).toBe( - 'unknown', - ); - }, - }); - }, - ); - }); - - it('clears EIP-1559 support for the network from state before updating the provider', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ]), - buildFakeProvider(), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: true, - }, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - // We only care about the first state change, because it - // happens before networkDidChange - count: 1, - operation: () => { - // Intentionally not awaited because we want to check state - // while this operation is in-progress - controller.rollbackToPreviousProvider(); - }, - beforeResolving: () => { - expect( - controller.store.getState().networkDetails, - ).toStrictEqual({ - EIPS: {}, - }); - }, - }); - }, - ); - }); - - it(`initializes a provider pointed to the "${networkType}" Infura network`, async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider(), - buildFakeProvider([ - { - request: { - method: 'test', - }, - response: { - result: 'test response', - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - - await controller.rollbackToPreviousProvider(); - - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider, 'Provider is somehow unset'); - const promisifiedSendAsync = promisify(provider.sendAsync).bind( - provider, - ); - const response = await promisifiedSendAsync({ - id: '1', - jsonrpc: '2.0', - method: 'test', - }); - expect(response.result).toBe('test response'); - }, - ); - }); - - it('replaces the provider object underlying the provider proxy without creating a new instance of the proxy itself', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - const { provider: providerBefore } = - controller.getProviderAndBlockTracker(); - - await controller.rollbackToPreviousProvider(); - - const { provider: providerAfter } = - controller.getProviderAndBlockTracker(); - expect(providerBefore).toBe(providerAfter); - }, - ); - }); - - it('emits infuraIsBlocked or infuraIsUnblocked, depending on whether Infura is blocking requests for the previous network', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller, messenger }) => { - const fakeProviders = [ - buildFakeProvider(), - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - const promiseForNoInfuraIsUnblockedEvents = - waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - count: 0, - }); - const promiseForInfuraIsBlocked = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - }); - - await controller.rollbackToPreviousProvider(); - - await expect(promiseForNoInfuraIsUnblockedEvents).toBeFulfilled(); - await expect(promiseForInfuraIsBlocked).toBeFulfilled(); - }, - ); - }); - - it('checks the status of the previous network again and updates state accordingly', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - error: ethErrors.rpc.methodNotFound(), - }, - ]), - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - expect(controller.store.getState().networkStatus).toBe( - 'unavailable', - ); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); - expect(controller.store.getState().networkStatus).toBe( - 'available', - ); - }, - ); - }); - - it('checks whether the previous network supports EIP-1559 again and updates state accordingly', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - ]), - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: false, - }, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - // rollbackToPreviousProvider clears networkDetails first, and - // then updates it to what we expect it to be - count: 2, - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: true, - }, - }); - }, - ); - }); - }); - } - - describe(`if the previous provider configuration had a type of "rpc"`, () => { - it('emits networkWillChange', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - }), - }, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.setProviderType(NetworkType.goerli); - - const networkWillChange = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:networkWillChange', - operation: () => { - // Intentionally not awaited because we're capturing an event - // emitted partway through the operation - controller.rollbackToPreviousProvider(); - }, - }); - - await expect(networkWillChange).toBeFulfilled(); - }, - ); - }); - - it('emits networkDidChange', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - }), - }, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.setProviderType(NetworkType.goerli); - - const networkDidChange = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:networkDidChange', - operation: () => { - // Intentionally not awaited because we're capturing an event - // emitted partway through the operation - controller.rollbackToPreviousProvider(); - }, - }); - - await expect(networkDidChange).toBeFulfilled(); - }, - ); - }); - - it('overwrites the the current provider configuration with the previous provider configuration', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - nickname: 'network', - ticker: 'TEST', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer.com', - }, - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); - expect(controller.store.getState().providerConfig).toStrictEqual({ - type: 'goerli', - rpcUrl: undefined, - chainId: toHex(5), - ticker: 'GoerliETH', - nickname: undefined, - rpcPrefs: { - blockExplorerUrl: 'https://goerli.etherscan.io', - }, - id: undefined, - }); - - await controller.rollbackToPreviousProvider(); - expect(controller.store.getState().providerConfig).toStrictEqual( - buildProviderConfig({ - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - nickname: 'network', - ticker: 'TEST', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer.com', - }, - }), - ); - }, - ); - }); - - it('resets the network state to "unknown" before updating the provider', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]), - buildFakeProvider(), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); - expect(controller.store.getState().networkStatus).toBe('available'); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - // We only care about the first state change, because it - // happens before networkDidChange - count: 1, - operation: () => { - // Intentionally not awaited because we want to check state - // while this operation is in-progress - controller.rollbackToPreviousProvider(); - }, - beforeResolving: () => { - expect(controller.store.getState().networkStatus).toBe( - 'unknown', - ); - }, - }); - }, - ); - }); - - it('clears EIP-1559 support for the network from state before updating the provider', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ]), - buildFakeProvider(), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: true, - }, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - // We only care about the first state change, because it - // happens before networkDidChange - count: 1, - operation: () => { - // Intentionally not awaited because we want to check state - // while this operation is in-progress - controller.rollbackToPreviousProvider(); - }, - beforeResolving: () => { - expect( - controller.store.getState().networkDetails, - ).toStrictEqual({ - EIPS: {}, - }); - }, - }); - }, - ); - }); - - it('initializes a provider pointed to the given RPC URL', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider(), - buildFakeProvider([ - { - request: { - method: 'test', - }, - response: { - result: 'test response', - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); - - await controller.rollbackToPreviousProvider(); - - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider, 'Provider is somehow unset'); - const promisifiedSendAsync = promisify(provider.sendAsync).bind( - provider, - ); - const response = await promisifiedSendAsync({ - id: '1', - jsonrpc: '2.0', - method: 'test', - }); - expect(response.result).toBe('test response'); - }, - ); - }); - - it('replaces the provider object underlying the provider proxy without creating a new instance of the proxy itself', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); - const { provider: providerBefore } = - controller.getProviderAndBlockTracker(); - - await controller.rollbackToPreviousProvider(); - - const { provider: providerAfter } = - controller.getProviderAndBlockTracker(); - expect(providerBefore).toBe(providerAfter); - }, - ); - }); - - it('emits infuraIsUnblocked', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller, messenger }) => { - const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); - - const promiseForInfuraIsUnblocked = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); - - await expect(promiseForInfuraIsUnblocked).toBeFulfilled(); - }, - ); - }); - - it('checks the status of the previous network again and updates state accordingly', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - error: ethErrors.rpc.methodNotFound(), - }, - ]), - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); - expect(controller.store.getState().networkStatus).toBe( - 'unavailable', - ); - - await controller.rollbackToPreviousProvider(); - expect(controller.store.getState().networkStatus).toBe('available'); - }, - ); - }); - - it('checks whether the previous network supports EIP-1559 again and updates state accordingly', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - ]), - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: false, - }, - }); - - await controller.rollbackToPreviousProvider(); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: true, - }, - }); - }, - ); - }); - }); - }); -}); - -/** - * Creates a mocked version of `createNetworkClient` where multiple mock - * invocations can be specified. A default implementation is provided so that if - * none of the actual invocations of the function match the mock invocations - * then an error will be thrown. - */ -function mockCreateNetworkClient() { - return when(createNetworkClientMock).mockImplementation((options) => { - const inspectedOptions = inspect(options, { depth: null, compact: true }); - const lines = [ - `No fake network client was specified for ${inspectedOptions}.`, - 'Make sure to mock this invocation of `createNetworkClient`.', - ]; - if ('infuraProjectId' in options) { - lines.push( - '(You might have forgotten to pass an `infuraProjectId` to `withController`.)', - ); - } - throw new Error(lines.join('\n')); - }); -} - -/** - * Test an operation that performs a `#switchNetwork` call with the given - * provider configuration. All effects of the `#switchNetwork` call should be - * covered by these tests. - * - * @param args - Arguments. - * @param args.expectedProviderConfig - The provider configuration that the - * operation is expected to set. - * @param args.initialState - The initial state of the network controller. - * @param args.operation - The operation to test. - */ -function refreshNetworkTests({ - expectedProviderConfig, - initialState, - operation, -}: { - expectedProviderConfig: ProviderConfiguration; - initialState?: Partial; - operation: (controller: NetworkController) => Promise; -}) { - it('emits networkWillChange', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - const networkWillChange = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:networkWillChange', - operation: () => { - // Intentionally not awaited because we're capturing an event - // emitted partway through the operation - operation(controller); - }, - }); - - await expect(networkWillChange).toBeTruthy(); - }, - ); - }); - - it('emits networkDidChange', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - const networkDidChange = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:networkDidChange', - operation: () => { - // Intentionally not awaited because we're capturing an event - // emitted partway through the operation - operation(controller); - }, - }); - - await expect(networkDidChange).toBeTruthy(); - }, - ); - }); - - it('clears network id from state', async () => { - await withController( - { - infuraProjectId: 'infura-project-id', - state: initialState, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - // Called during network lookup after resetting connection. - // Delayed to ensure that we can check the network id - // before this resolves. - { - delay: 1, - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: '0x1', - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); - expect(controller.store.getState().networkId).toBe('1'); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - // We only care about the first state change, because it - // happens before the network lookup - count: 1, - operation: () => { - // Intentionally not awaited because we want to check state - // partway through the operation - operation(controller); - }, - }); - - expect(controller.store.getState().networkId).toBeNull(); - }, - ); - }); - - it('clears network status from state', async () => { - await withController( - { - infuraProjectId: 'infura-project-id', - state: initialState, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called during network lookup after resetting connection. - // Delayed to ensure that we can check the network status - // before this resolves. - { - delay: 1, - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - delay: 1, - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); - expect(controller.store.getState().networkStatus).toBe( - NetworkStatus.Available, - ); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - // We only care about the first state change, because it - // happens before the network lookup - count: 1, - operation: () => { - // Intentionally not awaited because we want to check state - // partway through the operation - operation(controller); - }, - }); - - expect(controller.store.getState().networkStatus).toBe( - NetworkStatus.Unknown, - ); - }, - ); - }); - - it('clears network details from state', async () => { - await withController( - { - infuraProjectId: 'infura-project-id', - state: initialState, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: '0x1', - }, - }, - // Called during network lookup after resetting connection. - // Delayed to ensure that we can check the network details - // before this resolves. - { - delay: 1, - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: '0x1', - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: false, - }, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - // We only care about the first state change, because it - // happens before the network lookup - count: 1, - operation: () => { - // Intentionally not awaited because we want to check state - // partway through the operation - operation(controller); - }, - }); - - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: {}, - }); - }, - ); - }); - - if (expectedProviderConfig.type === NetworkType.rpc) { - it('sets the provider to a custom RPC provider initialized with the RPC target and chain ID', async () => { - await withController( - { - infuraProjectId: 'infura-project-id', - state: initialState, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_chainId', - }, - response: { - result: toHex(111), - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - - await operation(controller); - - expect(createNetworkClientMock).toHaveBeenCalledWith({ - chainId: expectedProviderConfig.chainId, - rpcUrl: expectedProviderConfig.rpcUrl, - type: NetworkClientType.Custom, - }); - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider); - const promisifiedSendAsync = promisify(provider.sendAsync).bind( - provider, - ); - const chainIdResult = await promisifiedSendAsync({ - id: 1, - jsonrpc: '2.0', - method: 'eth_chainId', - params: [], - }); - expect(chainIdResult.result).toBe(toHex(111)); - }, - ); - }); - } else { - it(`sets the provider to an Infura provider pointed to ${expectedProviderConfig.type}`, async () => { - await withController( - { - infuraProjectId: 'infura-project-id', - state: initialState, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_chainId', - }, - response: { - result: toHex(1337), - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - - await operation(controller); - - expect(createNetworkClientMock).toHaveBeenCalledWith({ - network: expectedProviderConfig.type, - infuraProjectId: 'infura-project-id', - type: NetworkClientType.Infura, - }); - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider); - const promisifiedSendAsync = promisify(provider.sendAsync).bind( - provider, - ); - const chainIdResult = await promisifiedSendAsync({ - id: 1, - jsonrpc: '2.0', - method: 'eth_chainId', - params: [], - }); - expect(chainIdResult.result).toBe(toHex(1337)); - }, - ); - }); - } - - it('replaces the provider object underlying the provider proxy without creating a new instance of the proxy itself', async () => { - await withController( - { - infuraProjectId: 'infura-project-id', - state: initialState, - }, - async ({ controller }) => { - const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - const providerType = controller.store.getState().providerConfig.type; - const initializationNetworkClientOptions: Parameters< - typeof createNetworkClient - >[0] = - providerType === NetworkType.rpc - ? { - chainId: controller.store.getState().providerConfig.chainId, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - rpcUrl: controller.store.getState().providerConfig.rpcUrl!, - type: NetworkClientType.Custom, - } - : { - network: providerType, - infuraProjectId: 'infura-project-id', - type: NetworkClientType.Infura, - }; - const operationNetworkClientOptions: Parameters< - typeof createNetworkClient - >[0] = - expectedProviderConfig.type === NetworkType.rpc - ? { - chainId: toHex(expectedProviderConfig.chainId), - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - rpcUrl: expectedProviderConfig.rpcUrl!, - type: NetworkClientType.Custom, - } - : { - network: expectedProviderConfig.type, - infuraProjectId: 'infura-project-id', - type: NetworkClientType.Infura, - }; - mockCreateNetworkClient() - .calledWith(initializationNetworkClientOptions) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith(operationNetworkClientOptions) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - const { provider: providerBefore } = - controller.getProviderAndBlockTracker(); - - await operation(controller); - - const { provider: providerAfter } = - controller.getProviderAndBlockTracker(); - expect(providerBefore).toBe(providerAfter); - }, - ); - }); - - lookupNetworkTests({ expectedProviderConfig, initialState, operation }); -} - -/** - * Test an operation that performs a `lookupNetwork` call with the given - * provider configuration. All effects of the `lookupNetwork` call should be - * covered by these tests. - * - * @param args - Arguments. - * @param args.expectedProviderConfig - The provider configuration that the - * operation is expected to set. - * @param args.initialState - The initial state of the network controller. - * @param args.operation - The operation to test. - */ -function lookupNetworkTests({ - expectedProviderConfig, - initialState, - operation, -}: { - expectedProviderConfig: ProviderConfiguration; - initialState?: Partial; - operation: (controller: NetworkController) => Promise; -}) { - describe('if the network ID and network details requests resolve successfully', () => { - const validNetworkIds = [12345, '12345', toHex(12345)]; - for (const networkId of validNetworkIds) { - describe(`with a network id of '${networkId}'`, () => { - describe('if the current network is different from the network in state', () => { - it('updates the network in state to match', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - response: { result: networkId }, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - await operation(controller); - - expect(controller.store.getState().networkId).toBe('12345'); - }, - ); - }); - }); - - describe('if the version of the current network is the same as that in state', () => { - it('does not change network ID in state', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - response: { result: networkId }, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - await operation(controller); - - await expect(controller.store.getState().networkId).toBe( - '12345', - ); - }, - ); - }); - - it('updates the network details', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: { result: networkId }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: { result: networkId }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ], - }); - - await operation(controller); - - await expect( - controller.store.getState().networkDetails, - ).toStrictEqual({ - EIPS: { - 1559: true, - }, - }); - }, - ); - }); - }); - }); - } - - describe('if the network details of the current network are different from the network details in state', () => { - it('updates the network in state to match', async () => { - await withController( - { - state: { - ...initialState, - networkDetails: { - EIPS: { - 1559: false, - }, - }, - }, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: { - baseFeePerGas: '0x1', - }, - }, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - await operation(controller); - - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: true, - }, - }); - }, - ); - }); - }); - - describe('if the network details of the current network are the same as the network details in state', () => { - it('does not change network details in state', async () => { - await withController( - { - state: { - ...initialState, - networkDetails: { - EIPS: { - 1559: true, - }, - }, - }, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: { - baseFeePerGas: '0x1', - }, - }, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - await operation(controller); - - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: true, - }, - }); - }, - ); - }); - }); - - it('emits infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - - it('does not emit infuraIsBlocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - }); - - describe('if an RPC error is encountered while retrieving the version of the current network', () => { - it('updates the network in state to "unavailable"', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - error: ethErrors.rpc.limitExceeded('some error'), - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - await operation(controller); - - expect(controller.store.getState().networkStatus).toBe('unavailable'); - }, - ); - }); - - it('resets the network details in state', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - // Called during provider initialization - { - request: { method: 'net_version' }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - // Called when calling the operation directly - { - request: { method: 'net_version' }, - error: ethErrors.rpc.limitExceeded('some error'), - }, - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ], - }); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: false, - }, - }); - - await operation(controller); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: {}, - }); - }, - ); - }); - - if (expectedProviderConfig.type === NetworkType.rpc) { - it('emits infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - error: ethErrors.rpc.limitExceeded('some error'), - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - } else { - it('does not emit infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - error: ethErrors.rpc.limitExceeded('some error'), - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - } - - it('does not emit infuraIsBlocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - error: ethErrors.rpc.limitExceeded('some error'), - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - }); - - describe('if a country blocked error is encountered while retrieving the version of the current network', () => { - if (expectedProviderConfig.type === NetworkType.rpc) { - it('updates the network in state to "unknown"', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - await operation(controller); - - expect(controller.store.getState().networkStatus).toBe('unknown'); - }, - ); - }); - - it('emits infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - - it('does not emit infuraIsBlocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - } else { - it('updates the network in state to "blocked"', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - await operation(controller); - - expect(controller.store.getState().networkStatus).toBe('blocked'); - }, - ); - }); - - it('does not emit infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - - it('emits infuraIsBlocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - } - - it('resets the network details in state', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - // Called during provider initialization - { - request: { method: 'net_version' }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - // Called when calling the operation directly - { - request: { method: 'net_version' }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ], - }); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: false, - }, - }); - - await operation(controller); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: {}, - }); - }, - ); - }); - }); - - describe('if an internal error is encountered while retrieving the version of the current network', () => { - it('updates the network in state to "unknown"', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - error: ethErrors.rpc.internal('some error'), - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - await operation(controller); - - expect(controller.store.getState().networkStatus).toBe('unknown'); - }, - ); - }); - - it('resets the network details in state', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - // Called during provider initialization - { - request: { method: 'net_version' }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - // Called when calling the operation directly - { - request: { method: 'net_version' }, - error: GENERIC_JSON_RPC_ERROR, - }, - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ], - }); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: false, - }, - }); - - await operation(controller); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: {}, - }); - }, - ); - }); - - if (expectedProviderConfig.type === NetworkType.rpc) { - it('emits infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - error: ethErrors.rpc.internal('some error'), - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - } else { - it('does not emit infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - error: ethErrors.rpc.internal('some error'), - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - } - - it('does not emit infuraIsBlocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - error: ethErrors.rpc.internal('some error'), - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - }); - - describe('if an invalid network ID is returned', () => { - it('updates the network in state to "unknown"', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - response: { result: 'invalid' }, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - await operation(controller); - - expect(controller.store.getState().networkStatus).toBe('unknown'); - }, - ); - }); - - it('resets the network details in state', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - // Called during provider initialization - { - request: { method: 'net_version' }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - // Called when calling the operation directly - { - request: { method: 'net_version' }, - response: { result: 'invalid' }, - }, - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ], - }); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: false, - }, - }); - - await operation(controller); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: {}, - }); - }, - ); - }); - - if (expectedProviderConfig.type === NetworkType.rpc) { - it('emits infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - response: { result: 'invalid' }, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - } else { - it('does not emit infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - response: { result: 'invalid' }, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - } - - it('does not emit infuraIsBlocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - response: { result: 'invalid' }, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - }); - - describe('if an RPC error is encountered while retrieving the network details of the current network', () => { - it('updates the network in state to "unavailable"', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: ethErrors.rpc.limitExceeded('some error'), - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - await operation(controller); - - expect(controller.store.getState().networkStatus).toBe('unavailable'); - }, - ); - }); - - it('resets the network details in state', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - // Called during provider initialization - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - // Called when calling the operation directly - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: ethErrors.rpc.limitExceeded('some error'), - }, - ], - }); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: false, - }, - }); - - await operation(controller); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: {}, - }); - }, - ); - }); - - if (expectedProviderConfig.type === NetworkType.rpc) { - it('emits infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: ethErrors.rpc.limitExceeded('some error'), - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - } else { - it('does not emit infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: ethErrors.rpc.limitExceeded('some error'), - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - } - - it('does not emit infuraIsBlocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: ethErrors.rpc.limitExceeded('some error'), - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - }); - - describe('if a country blocked error is encountered while retrieving the network details of the current network', () => { - if (expectedProviderConfig.type === NetworkType.rpc) { - it('updates the network in state to "unknown"', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - await operation(controller); - - expect(controller.store.getState().networkStatus).toBe('unknown'); - }, - ); - }); - - it('emits infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - - it('does not emit infuraIsBlocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - } else { - it('updates the network in state to "blocked"', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - await operation(controller); - - expect(controller.store.getState().networkStatus).toBe('blocked'); - }, - ); - }); - - it('does not emit infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - - it('emits infuraIsBlocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - } - - it('resets the network details in state', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - // Called during provider initialization - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - // Called when calling the operation directly - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ], - }); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: false, - }, - }); - - await operation(controller); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: {}, - }); - }, - ); - }); - }); - - describe('if an internal error is encountered while retrieving the network details of the current network', () => { - it('updates the network in state to "unknown"', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: ethErrors.rpc.internal('some error'), - }, - ], - }); - - await operation(controller); - - expect(controller.store.getState().networkStatus).toBe('unknown'); - }, - ); - }); - - it('resets the network details in state', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - // Called during provider initialization - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - // Called when calling the operation directly - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: GENERIC_JSON_RPC_ERROR, - }, - ], - }); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: false, - }, - }); - - await operation(controller); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: {}, - }); - }, - ); - }); - - if (expectedProviderConfig.type === NetworkType.rpc) { - it('emits infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: ethErrors.rpc.internal('some error'), - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - } else { - it('does not emit infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: ethErrors.rpc.internal('some error'), - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - } - - it('does not emit infuraIsBlocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: ethErrors.rpc.internal('some error'), - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - }); -} - -/** - * Builds a controller messenger. - * - * @returns The controller messenger. - */ -function buildMessenger() { - return new ControllerMessenger< - NetworkControllerAction, - NetworkControllerEvent - >(); -} - -/** - * Build a restricted controller messenger for the network controller. - * - * @param messenger - A controller messenger. - * @returns The network controller restricted messenger. - */ -function buildNetworkControllerMessenger(messenger = buildMessenger()) { - return messenger.getRestricted({ - name: 'NetworkController', - allowedActions: [ - 'NetworkController:getProviderConfig', - 'NetworkController:getEthQuery', - ], - allowedEvents: [ - 'NetworkController:networkDidChange', - 'NetworkController:networkWillChange', - 'NetworkController:infuraIsBlocked', - 'NetworkController:infuraIsUnblocked', - ], - }); -} - -/** - * The callback that `withController` takes. - */ -type WithControllerCallback = ({ - controller, -}: { - controller: NetworkController; - messenger: ControllerMessenger< - NetworkControllerAction, - NetworkControllerEvent - >; -}) => Promise | ReturnValue; - -/** - * A partial form of the options that `NetworkController` takes. - */ -type WithControllerOptions = Partial; - -/** - * The arguments that `withController` takes. - */ -type WithControllerArgs = - | [WithControllerCallback] - | [ - Omit, - WithControllerCallback, - ]; - -/** - * Builds a controller based on the given options, and calls the given function - * with that controller. - * - * @param args - Either a function, or an options bag + a function. The options - * bag is equivalent to the options that NetworkController takes (although - * `messenger` and `infuraProjectId` are filled in if not given); the function - * will be called with the built controller. - * @returns Whatever the callback returns. - */ -async function withController( - ...args: WithControllerArgs -): Promise { - const [{ ...rest }, fn] = args.length === 2 ? args : [{}, args[0]]; - const messenger = buildMessenger(); - const restrictedMessenger = buildNetworkControllerMessenger(messenger); - const controller = new NetworkController({ - messenger: restrictedMessenger, - trackMetaMetricsEvent: jest.fn(), - infuraProjectId: 'infura-project-id', - ...rest, - }); - try { - return await fn({ controller, messenger }); - } finally { - await controller.destroy(); - } -} - -/** - * Builds a complete ProviderConfig object, filling in values that are not - * provided with defaults. - * - * @param config - An incomplete ProviderConfig object. - * @returns The complete ProviderConfig object. - */ -function buildProviderConfig( - config: Partial = {}, -): ProviderConfiguration { - if (config.type && config.type !== NetworkType.rpc) { - const networkConfig = INFURA_NETWORKS.find( - ({ networkType }) => networkType === config.type, - ); - if (!networkConfig) { - throw new Error(`Invalid type: ${config.type}`); - } - return { - ...networkConfig, - // This is redundant with the spread operation below, but this was - // required for TypeScript to understand that this property was set to an - // Infura type. - type: config.type, - ...config, - }; - } - return { - type: NetworkType.rpc, - chainId: '0x42', - nickname: undefined, - rpcUrl: 'http://doesntmatter.com', - ...config, - }; -} - -/** - * Builds an object that `createInfuraProvider` or `createJsonRpcClient` returns. - * - * @param provider - provider to use if you dont want the defaults - * @returns The object. - */ -function buildFakeClient( - provider: SafeEventEmitterProvider = buildFakeProvider(), -) { - return { - provider, - blockTracker: new FakeBlockTracker({ provider }), - }; -} - -/** - * Builds fake provider engine object that `createMetamaskProvider` returns, - * with canned responses optionally provided for certain RPC methods. - * - * @param stubs - The list of RPC methods you want to stub along with their - * responses. - * @returns The object. - */ -function buildFakeProvider(stubs: FakeProviderStub[] = []) { - const completeStubs = stubs.slice(); - if (!stubs.some((stub) => stub.request.method === 'eth_getBlockByNumber')) { - completeStubs.unshift({ - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - discardAfterMatching: false, - }); - } - if (!stubs.some((stub) => stub.request.method === 'net_version')) { - completeStubs.unshift({ - request: { method: 'net_version' }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - discardAfterMatching: false, - }); - } - return new FakeProvider({ stubs: completeStubs }); -} - -/** - * Asks the controller to set the provider in the simplest way, stubbing the - * provider appropriately so as not to cause any errors to be thrown. This is - * useful in tests where it doesn't matter how the provider gets set, just that - * it does. Canned responses may be optionally provided for certain RPC methods - * on the provider. - * - * @param controller - The network controller. - * @param options - Additional options. - * @param options.stubs - The set of RPC methods you want to stub on the - * provider along with their responses. - * @param options.stubLookupNetworkWhileSetting - Whether to stub the call to - * `lookupNetwork` that happens when the provider is set. This option is useful - * in tests that need a provider to get set but also call `lookupNetwork` on - * their own. In this case, since the `providerConfig` setter already calls - * `lookupNetwork` once, and since `lookupNetwork` is called out of band, the - * test may run with unexpected results. By stubbing `lookupNetwork` before - * setting the provider, the test is free to explicitly call it. - * @returns The set provider. - */ -async function setFakeProvider( - controller: NetworkController, - { - stubs = [], - stubLookupNetworkWhileSetting = false, - }: { - stubs?: FakeProviderStub[]; - stubLookupNetworkWhileSetting?: boolean; - } = {}, -): Promise { - const fakeProvider = buildFakeProvider(stubs); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - const lookupNetworkMock = jest.spyOn(controller, 'lookupNetwork'); - - if (stubLookupNetworkWhileSetting) { - lookupNetworkMock.mockResolvedValue(undefined); - } - - await controller.initializeProvider(); - assert(controller.getProviderAndBlockTracker().provider); - - if (stubLookupNetworkWhileSetting) { - lookupNetworkMock.mockRestore(); - } -} - -/** - * Waits for changes to the primary observable store of a controller to occur - * before proceeding. May be called with a function, in which case waiting will - * occur after the function is called; or may be called standalone if you want - * to assert that no state changes occurred. - * - * @param args - The arguments. - * @param args.controller - The network controller. - * @param args.propertyPath - The path of the property you expect the state - * changes to concern. - * @param args.count - The number of events you expect to occur. If null, this - * function will wait until no events have occurred in `wait` number of - * milliseconds. Default: 1. - * @param args.duration - The amount of time in milliseconds to wait for the - * expected number of filtered state changes to occur before resolving the - * promise that this function returns (default: 150). - * @param args.operation - A function to run that will presumably produce the - * state changes in question. - * @param args.beforeResolving - In some tests, state updates happen so fast, we - * need to make an assertion immediately after the event in question occurs. - * However, if we wait until the promise this function returns resolves to do - * so, some other state update to the same - * property may have happened. This option allows you to make an assertion - * _before_ the promise resolves. This has the added benefit of allowing you to - * maintain the "arrange, act, assert" ordering in your test, meaning that you - * can still call the method that kicks off the event and then make the - * assertion afterward instead of the other way around. - * @returns A promise that resolves to an array of state objects (that is, the - * contents of the store) when the specified number of filtered state changes - * have occurred, or all of them if no number has been specified. - */ -async function waitForStateChanges({ - controller, - propertyPath, - count: expectedInterestingStateCount = 1, - duration: timeBeforeAssumingNoMoreStateChanges = 150, - operation = () => { - // do nothing - }, - beforeResolving = async () => { - // do nothing - }, -}: { - controller: NetworkController; - propertyPath?: string[]; - count?: number | null; - duration?: number; - operation?: () => void | Promise; - beforeResolving?: () => void | Promise; -}) { - const initialState = { ...controller.store.getState() }; - let isTimerRunning = false; - - const promiseForStateChanges = new Promise((resolve, reject) => { - // We need to declare this variable first, then assign it later, so that - // ESLint won't complain that resetTimer is referring to this variable - // before it's declared. And we need to use let so that we can assign it - // below. - /* eslint-disable-next-line prefer-const */ - let eventListener: (...args: any[]) => void; - let timer: NodeJS.Timeout | undefined; - const allStates: NetworkControllerState[] = []; - const interestingStates: NetworkControllerState[] = []; - - const stopTimer = () => { - if (timer) { - clearTimeout(timer); - } - isTimerRunning = false; - }; - - async function end() { - stopTimer(); - - controller.store.unsubscribe(eventListener); - - await beforeResolving(); - - const shouldEnd = - expectedInterestingStateCount === null - ? interestingStates.length > 0 - : interestingStates.length === expectedInterestingStateCount; - - if (shouldEnd) { - resolve(interestingStates); - } else { - // Using a string instead of an Error leads to better backtraces. - /* eslint-disable-next-line prefer-promise-reject-errors */ - const expectedInterestingStateCountFragment = - expectedInterestingStateCount === null - ? 'any number of' - : expectedInterestingStateCount; - const propertyPathFragment = - propertyPath === undefined ? '' : ` on \`${propertyPath.join('.')}\``; - const actualInterestingStateCountFragment = - expectedInterestingStateCount === null - ? 'none' - : interestingStates.length; - const primaryMessage = `Expected to receive ${expectedInterestingStateCountFragment} state change(s)${propertyPathFragment}, but received ${actualInterestingStateCountFragment} after ${timeBeforeAssumingNoMoreStateChanges}ms.`; - reject( - [ - primaryMessage, - 'Initial state:', - inspect(initialState, { depth: null }), - 'All state changes (without filtering):', - inspect(allStates, { depth: null }), - 'Filtered state changes:', - inspect(interestingStates, { depth: null }), - ].join('\n\n'), - ); - } - } - - const resetTimer = () => { - stopTimer(); - timer = setTimeout(() => { - if (isTimerRunning) { - end(); - } - }, timeBeforeAssumingNoMoreStateChanges); - isTimerRunning = true; - }; - - eventListener = (newState) => { - const isInteresting = - propertyPath === undefined - ? true - : isStateChangeInteresting( - newState, - allStates.length > 0 - ? allStates[allStates.length - 1] - : initialState, - propertyPath, - ); - - allStates.push({ ...newState }); - - if (isInteresting) { - interestingStates.push(newState); - if (interestingStates.length === expectedInterestingStateCount) { - end(); - } else { - resetTimer(); - } - } - }; - - controller.store.subscribe(eventListener); - resetTimer(); - }); - - await operation(); - - return await promiseForStateChanges; -} - -/** - * Waits for controller events to be emitted before proceeding. - * - * @param args - The arguments to this function. - * @param args.messenger - The messenger suited for NetworkController. - * @param args.eventType - The type of NetworkController event you want to wait - * for. - * @param args.count - The number of events you expect to occur (default: 1). - * @param args.filter - A function used to discard events that are not of - * interest. - * @param args.wait - The amount of time in milliseconds to wait for the - * expected number of filtered events to occur before resolving the promise that - * this function returns (default: 150). - * @param args.operation - A function to run that will presumably produce the - * events in question. - * @param args.beforeResolving - In some tests, state updates happen so fast, we - * need to make an assertion immediately after the event in question occurs. - * However, if we wait until the promise this function returns resolves to do - * so, some other state update to the same - * property may have happened. This option allows you to make an assertion - * _before_ the promise resolves. This has the added benefit of allowing you to - * maintain the "arrange, act, assert" ordering in your test, meaning that you - * can still call the method that kicks off the event and then make the - * assertion afterward instead of the other way around. - * @returns A promise that resolves to the list of payloads for the set of - * events, optionally filtered, when a specific number of them have occurred. - */ -async function waitForPublishedEvents({ - messenger, - eventType, - count: expectedNumberOfEvents = 1, - filter: isEventPayloadInteresting = () => true, - wait: timeBeforeAssumingNoMoreEvents = 150, - operation = () => { - // do nothing - }, - beforeResolving = async () => { - // do nothing - }, -}: { - messenger: ControllerMessenger; - eventType: E['type']; - count?: number; - filter?: (payload: E['payload']) => boolean; - wait?: number; - operation?: () => void | Promise; - beforeResolving?: () => void | Promise; -}): Promise { - const promiseForEventPayloads = new Promise( - (resolve, reject) => { - let timer: NodeJS.Timeout | undefined; - const allEventPayloads: E['payload'][] = []; - const interestingEventPayloads: E['payload'][] = []; - let alreadyEnded = false; - - // We're using `any` here because there seems to be some mismatch between - // the signature of `subscribe` and the way that we're using it. Try - // changing `any` to either `((...args: E['payload']) => void)` or - // `ExtractEventHandler` to see the issue. - const eventListener: any = (...payload: E['payload']) => { - allEventPayloads.push(payload); - - if (isEventPayloadInteresting(payload)) { - interestingEventPayloads.push(payload); - if (interestingEventPayloads.length === expectedNumberOfEvents) { - stopTimer(); - end(); - } else { - resetTimer(); - } - } - }; - - async function end() { - if (!alreadyEnded) { - alreadyEnded = true; - messenger.unsubscribe(eventType, eventListener); - - await beforeResolving(); - - if (interestingEventPayloads.length === expectedNumberOfEvents) { - resolve(interestingEventPayloads); - } else { - // Using a string instead of an Error leads to better backtraces. - /* eslint-disable-next-line prefer-promise-reject-errors */ - reject( - `Expected to receive ${expectedNumberOfEvents} ${eventType} event(s), but received ${ - interestingEventPayloads.length - } after ${timeBeforeAssumingNoMoreEvents}ms.\n\nAll payloads:\n\n${inspect( - allEventPayloads, - { depth: null }, - )}`, - ); - } - } - } - - function stopTimer() { - if (timer) { - clearTimeout(timer); - } - } - - function resetTimer() { - stopTimer(); - timer = setTimeout(() => { - end(); - }, timeBeforeAssumingNoMoreEvents); - } - - messenger.subscribe(eventType, eventListener); - resetTimer(); - }, - ); - - if (operation) { - await operation(); - } - - return await promiseForEventPayloads; -} - -/** - * Returns whether two places in different state objects have different values. - * - * @param currentState - The current state object. - * @param prevState - The previous state object. - * @param propertyPath - A property path within both objects. - * @returns True or false, depending on the result. - */ -function isStateChangeInteresting( - currentState: Record, - prevState: Record, - propertyPath: PropertyKey[], -): boolean { - return !isDeepStrictEqual( - get(currentState, propertyPath), - get(prevState, propertyPath), - ); -} diff --git a/app/scripts/controllers/network/network-controller.ts b/app/scripts/controllers/network/network-controller.ts deleted file mode 100644 index a4417a8e2..000000000 --- a/app/scripts/controllers/network/network-controller.ts +++ /dev/null @@ -1,1196 +0,0 @@ -import { strict as assert } from 'assert'; -import EventEmitter from 'events'; -import { ObservableStore } from '@metamask/obs-store'; -import log from 'loglevel'; -import { - createSwappableProxy, - createEventEmitterProxy, - SwappableProxy, -} from '@metamask/swappable-obj-proxy'; -import EthQuery from 'eth-query'; -import { RestrictedControllerMessenger } from '@metamask/base-controller'; -import { v4 as uuid } from 'uuid'; -import { Hex, isPlainObject, isStrictHexString } from '@metamask/utils'; -import { errorCodes } from 'eth-rpc-errors'; -import { SafeEventEmitterProvider } from '@metamask/eth-json-rpc-provider'; -import { PollingBlockTracker } from 'eth-block-tracker'; -import { hexToDecimal } from '../../../../shared/modules/conversion.utils'; -import { - INFURA_PROVIDER_TYPES, - INFURA_BLOCKED_KEY, - TEST_NETWORK_TICKER_MAP, - CHAIN_IDS, - NETWORK_TYPES, - BUILT_IN_INFURA_NETWORKS, - BuiltInInfuraNetwork, - NetworkStatus, -} from '../../../../shared/constants/network'; -import { - isPrefixedFormattedHexString, - isSafeChainId, -} from '../../../../shared/modules/network.utils'; -import { - MetaMetricsEventCategory, - MetaMetricsEventPayload, -} from '../../../../shared/constants/metametrics'; -import { isErrorWithMessage } from '../../../../shared/modules/error'; -import { - createNetworkClient, - NetworkClientType, -} from './create-network-client'; - -/** - * The name of NetworkController. - */ -const name = 'NetworkController'; - -/** - * A block header object that `eth_getBlockByNumber` returns. Note that this - * type does not specify all of the properties present within the block header; - * within NetworkController, we are only interested in `baseFeePerGas`. - */ -type Block = { - baseFeePerGas?: unknown; -}; - -/** - * Encodes a few pieces of information: - * - * - Whether or not a provider is configured for an Infura network or a - * non-Infura network. - * - If an Infura network, then which network. - * - If a non-Infura network, then whether the network exists locally or - * remotely. - * - * Primarily used to build the network client and check the availability of a - * network. - */ -export type ProviderType = BuiltInInfuraNetwork | typeof NETWORK_TYPES.RPC; - -/** - * The network ID of a network. - */ -type NetworkId = `${number}`; - -/** - * The ID of a network configuration. - */ -type NetworkConfigurationId = string; - -/** - * The chain ID of a network. - */ -type ChainId = Hex; - -/** - * `networkWillChange` is published when the current network is about to be - * switched, but the new provider has not been created and no state changes have - * occurred yet. - */ -export type NetworkControllerNetworkWillChangeEvent = { - type: 'NetworkController:networkWillChange'; - payload: []; -}; - -/** - * `networkDidChange` is published after a provider has been created for a newly - * switched network (but before the network has been confirmed to be available). - */ -export type NetworkControllerNetworkDidChangeEvent = { - type: 'NetworkController:networkDidChange'; - payload: []; -}; - -/** - * `infuraIsBlocked` is published after the network is switched to an Infura - * network, but when Infura returns an error blocking the user based on their - * location. - */ -export type NetworkControllerInfuraIsBlockedEvent = { - type: 'NetworkController:infuraIsBlocked'; - payload: []; -}; - -/** - * `infuraIsBlocked` is published either after the network is switched to an - * Infura network and Infura does not return an error blocking the user based on - * their location, or the network is switched to a non-Infura network. - */ -export type NetworkControllerInfuraIsUnblockedEvent = { - type: 'NetworkController:infuraIsUnblocked'; - payload: []; -}; - -/** - * The set of events that the NetworkController messenger can publish. - */ -export type NetworkControllerEvent = - | NetworkControllerNetworkDidChangeEvent - | NetworkControllerNetworkWillChangeEvent - | NetworkControllerInfuraIsBlockedEvent - | NetworkControllerInfuraIsUnblockedEvent; - -export type NetworkControllerGetProviderConfigAction = { - type: `NetworkController:getProviderConfig`; - handler: () => ProviderConfiguration; -}; - -export type NetworkControllerGetEthQueryAction = { - type: `NetworkController:getEthQuery`; - handler: () => EthQuery | undefined; -}; - -export type NetworkControllerAction = - | NetworkControllerGetProviderConfigAction - | NetworkControllerGetEthQueryAction; - -/** - * The messenger that the NetworkController uses to publish events. - */ -export type NetworkControllerMessenger = RestrictedControllerMessenger< - typeof name, - NetworkControllerAction, - NetworkControllerEvent, - string, - string ->; - -/** - * Information used to set up the middleware stack for a particular kind of - * network. Currently has overlap with `NetworkConfiguration`, although the - * two will be merged down the road. - */ -export type ProviderConfiguration = { - /** - * Either a type of Infura network, "localhost" for a locally operated - * network, or "rpc" for everything else. - */ - type: ProviderType; - /** - * The chain ID as per EIP-155. - */ - chainId: ChainId; - /** - * The URL of the RPC endpoint. Only used when `type` is "localhost" or "rpc". - */ - rpcUrl?: string; - /** - * The shortname of the currency used by the network. - */ - ticker?: string; - /** - * The user-customizable name of the network. - */ - nickname?: string; - /** - * User-customizable details for the network. - */ - rpcPrefs?: { - blockExplorerUrl?: string; - }; - /** - * The ID of the network configuration used to build this provider config. - */ - id?: NetworkConfigurationId; -}; - -/** - * The contents of the `networkId` store. - */ -type NetworkIdState = NetworkId | null; - -/** - * Information about the network not held by any other part of state. Currently - * only used to capture whether a network supports EIP-1559. - */ -type NetworkDetails = { - /** - * EIPs supported by the network. - */ - EIPS: { - [eipNumber: number]: boolean | undefined; - }; - [otherProperty: string]: unknown; -}; - -/** - * A "network configuration" represents connection data directly provided by - * users via the wallet UI for a custom network (we already have this - * information for networks that come pre-shipped with the wallet). Ultimately - * used to set up the middleware stack so that the wallet can make requests to - * the network. Currently has overlap with `ProviderConfiguration`, although the - * two will be merged down the road. - */ -type NetworkConfiguration = { - /** - * The unique ID of the network configuration. Useful for switching to and - * removing specific networks. - */ - id: NetworkConfigurationId; - /** - * The URL of the RPC endpoint. Only used when `type` is "localhost" or "rpc". - */ - rpcUrl: string; - /** - * The chain ID as per EIP-155. - */ - chainId: ChainId; - /** - * The shortname of the currency used for this network. - */ - ticker: string; - /** - * The user-customizable name of the network. - */ - nickname?: string; - /** - * User-customizable details for the network. - */ - rpcPrefs?: { - blockExplorerUrl: string; - }; -}; - -/** - * A set of network configurations, keyed by ID. - */ -type NetworkConfigurations = Record< - NetworkConfigurationId, - NetworkConfiguration ->; - -/** - * The state that NetworkController holds after combining its individual stores. - */ -export type NetworkControllerState = { - providerConfig: ProviderConfiguration; - networkId: NetworkIdState; - networkStatus: NetworkStatus; - networkDetails: NetworkDetails; - networkConfigurations: NetworkConfigurations; -}; - -/** - * The options that NetworkController takes. - */ -export type NetworkControllerOptions = { - messenger: NetworkControllerMessenger; - state?: { - providerConfig?: ProviderConfiguration; - networkDetails?: NetworkDetails; - networkConfigurations?: NetworkConfigurations; - }; - infuraProjectId: string; - trackMetaMetricsEvent: (payload: MetaMetricsEventPayload) => void; -}; - -/** - * Type guard for determining whether the given value is an error object with a - * `code` property, such as an instance of Error. - * - * TODO: Move this to @metamask/utils - * - * @param error - The object to check. - * @returns True if `error` has a `code`, false otherwise. - */ -function isErrorWithCode(error: unknown): error is { code: string | number } { - return typeof error === 'object' && error !== null && 'code' in error; -} - -/** - * Convert the given value into a valid network ID. The ID is accepted - * as either a number, a decimal string, or a 0x-prefixed hex string. - * - * @param value - The network ID to convert, in an unknown format. - * @returns A valid network ID (as a decimal string) - * @throws If the given value cannot be safely parsed. - */ -function convertNetworkId(value: unknown): NetworkId { - if (typeof value === 'number' && !Number.isNaN(value)) { - return `${value}`; - } else if (isStrictHexString(value)) { - return hexToDecimal(value) as NetworkId; - } else if (typeof value === 'string' && /^\d+$/u.test(value)) { - return value as NetworkId; - } - throw new Error(`Cannot parse as a valid network ID: '${value}'`); -} - -/** - * Builds the default provider config used to initialize the network controller. - */ -function buildDefaultProviderConfigState(): ProviderConfiguration { - if (process.env.IN_TEST) { - return { - type: NETWORK_TYPES.RPC, - rpcUrl: 'http://localhost:8545', - chainId: '0x539', - nickname: 'Localhost 8545', - ticker: 'ETH', - }; - } else if ( - process.env.METAMASK_DEBUG || - process.env.METAMASK_ENVIRONMENT === 'test' - ) { - return { - type: NETWORK_TYPES.GOERLI, - chainId: CHAIN_IDS.GOERLI, - ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.GOERLI], - }; - } - - return { - type: NETWORK_TYPES.MAINNET, - chainId: CHAIN_IDS.MAINNET, - ticker: 'ETH', - }; -} - -/** - * Builds the default network ID state used to initialize the network - * controller. - */ -function buildDefaultNetworkIdState(): NetworkIdState { - return null; -} - -/** - * Builds the default network status state used to initialize the network - * controller. - */ -function buildDefaultNetworkStatusState(): NetworkStatus { - return NetworkStatus.Unknown; -} - -/** - * Builds the default network details state used to initialize the - * network controller. - */ -function buildDefaultNetworkDetailsState(): NetworkDetails { - return { - EIPS: {}, - }; -} - -/** - * Builds the default network configurations state used to initialize the - * network controller. - */ -function buildDefaultNetworkConfigurationsState(): NetworkConfigurations { - return {}; -} - -/** - * Builds the default state for the network controller. - * - * @returns The default network controller state. - */ -function buildDefaultState() { - return { - providerConfig: buildDefaultProviderConfigState(), - networkId: buildDefaultNetworkIdState(), - networkStatus: buildDefaultNetworkStatusState(), - networkDetails: buildDefaultNetworkDetailsState(), - networkConfigurations: buildDefaultNetworkConfigurationsState(), - }; -} - -/** - * Returns whether the given argument is a type that our Infura middleware - * recognizes. We can't calculate this inline because the usual type of `type`, - * which we get from the provider config, is not a subset of the type of - * `INFURA_PROVIDER_TYPES`, but rather a superset, and therefore we cannot make - * a proper comparison without TypeScript complaining. However, if we downcast - * both variables, then we are able to achieve this. As a bonus, this function - * also types the given argument as a `BuiltInInfuraNetwork` assuming that the - * check succeeds. - * - * @param type - A type to compare. - * @returns True or false, depending on whether the given type is one that our - * Infura middleware recognizes. - */ -function isInfuraProviderType(type: string): type is BuiltInInfuraNetwork { - const infuraProviderTypes: readonly string[] = INFURA_PROVIDER_TYPES; - return infuraProviderTypes.includes(type); -} - -/** - * The network controller creates and manages the "provider" object which allows - * our code and external dapps to make requests to a network. The requests are - * filtered through a set of middleware (provided by - * [`eth-json-rpc-middleware`][1]) which not only performs the HTTP request to - * the appropriate RPC endpoint but also uses caching to limit duplicate - * requests to Infura and smoothens interactions with the blockchain in general. - * - * [1]: https://github.com/MetaMask/eth-json-rpc-middleware - */ -export class NetworkController extends EventEmitter { - /** - * The messenger that NetworkController uses to publish events. - */ - #messenger: NetworkControllerMessenger; - - /** - * Observable store containing the provider configuration for the previously - * configured network. - */ - #previousProviderConfig: ProviderConfiguration; - - /** - * Observable store containing a combination of data from all of the - * individual stores. - */ - store: ObservableStore; - - #provider: SafeEventEmitterProvider | null; - - #blockTracker: PollingBlockTracker | null; - - #providerProxy: SwappableProxy | null; - - #blockTrackerProxy: SwappableProxy | null; - - #ethQuery: EthQuery | undefined; - - #infuraProjectId: NetworkControllerOptions['infuraProjectId']; - - #trackMetaMetricsEvent: NetworkControllerOptions['trackMetaMetricsEvent']; - - /** - * Constructs a network controller. - * - * @param options - Options for this constructor. - * @param options.messenger - The NetworkController messenger. - * @param options.state - Initial controller state. - * @param options.infuraProjectId - The Infura project ID. - * @param options.trackMetaMetricsEvent - A method to forward events to the - * {@link MetaMetricsController}. - */ - constructor({ - messenger, - state = {}, - infuraProjectId, - trackMetaMetricsEvent, - }: NetworkControllerOptions) { - super(); - - this.#messenger = messenger; - - this.store = new ObservableStore({ - ...buildDefaultState(), - ...state, - }); - this.#previousProviderConfig = this.store.getState().providerConfig; - - // provider and block tracker - this.#provider = null; - this.#blockTracker = null; - - // provider and block tracker proxies - because the network changes - this.#providerProxy = null; - this.#blockTrackerProxy = null; - - if (!infuraProjectId || typeof infuraProjectId !== 'string') { - throw new Error('Invalid Infura project ID'); - } - this.#infuraProjectId = infuraProjectId; - this.#trackMetaMetricsEvent = trackMetaMetricsEvent; - - this.#messenger.registerActionHandler(`${name}:getProviderConfig`, () => { - return this.store.getState().providerConfig; - }); - this.#messenger.registerActionHandler(`${name}:getEthQuery`, () => { - return this.#ethQuery; - }); - } - - /** - * Deactivates the controller, stopping any ongoing polling. - * - * In-progress requests will not be aborted. - */ - async destroy(): Promise { - await this.#blockTracker?.destroy(); - } - - /** - * Creates the provider and block tracker for the configured network, - * using the provider to gather details about the network. - */ - async initializeProvider(): Promise { - const { type, rpcUrl, chainId } = this.store.getState().providerConfig; - this.#configureProvider({ type, rpcUrl, chainId }); - await this.lookupNetwork(); - } - - /** - * Returns the proxies wrapping the currently set provider and block tracker. - */ - getProviderAndBlockTracker(): { - provider: SwappableProxy | null; - blockTracker: SwappableProxy | null; - } { - const provider = this.#providerProxy; - const blockTracker = this.#blockTrackerProxy; - return { provider, blockTracker }; - } - - /** - * Determines whether the network supports EIP-1559 by checking whether the - * latest block has a `baseFeePerGas` property, then updates state - * appropriately. - * - * @returns A promise that resolves to true if the network supports EIP-1559 - * and false otherwise. - */ - async getEIP1559Compatibility(): Promise { - const { EIPS } = this.store.getState().networkDetails; - // NOTE: This isn't necessary anymore because the block cache middleware - // already prevents duplicate requests from taking place - if (EIPS[1559] !== undefined) { - return EIPS[1559]; - } - - const { provider } = this.getProviderAndBlockTracker(); - if (!provider) { - // Really we should throw an error if a provider hasn't been initialized - // yet, but that might have undesirable repercussions, so return false for - // now - return false; - } - - const supportsEIP1559 = await this.#determineEIP1559Compatibility(provider); - const { networkDetails } = this.store.getState(); - this.store.updateState({ - networkDetails: { - ...networkDetails, - EIPS: { - ...networkDetails.EIPS, - 1559: supportsEIP1559, - }, - }, - }); - return supportsEIP1559; - } - - /** - * Performs side effects after switching to a network. If the network is - * available, updates the network state with the network ID of the network and - * stores whether the network supports EIP-1559; otherwise clears said - * information about the network that may have been previously stored. - * - * @fires infuraIsBlocked if the network is Infura-supported and is blocking - * requests. - * @fires infuraIsUnblocked if the network is Infura-supported and is not - * blocking requests, or if the network is not Infura-supported. - */ - async lookupNetwork(): Promise { - const { type } = this.store.getState().providerConfig; - const { provider } = this.getProviderAndBlockTracker(); - let networkChanged = false; - let networkId: NetworkIdState = null; - let supportsEIP1559 = false; - let networkStatus: NetworkStatus; - - if (provider === null) { - log.warn( - 'NetworkController - lookupNetwork aborted due to missing provider', - ); - return; - } - - const isInfura = isInfuraProviderType(type); - - const listener = () => { - networkChanged = true; - this.#messenger.unsubscribe( - 'NetworkController:networkDidChange', - listener, - ); - }; - this.#messenger.subscribe('NetworkController:networkDidChange', listener); - - try { - const results = await Promise.all([ - this.#getNetworkId(provider), - this.#determineEIP1559Compatibility(provider), - ]); - const possibleNetworkId = results[0]; - networkId = convertNetworkId(possibleNetworkId); - supportsEIP1559 = results[1]; - networkStatus = NetworkStatus.Available; - } catch (error) { - if (isErrorWithCode(error)) { - let responseBody; - if (isInfura && isErrorWithMessage(error)) { - try { - responseBody = JSON.parse(error.message); - } catch { - // error.message must not be JSON - } - } - - if ( - isPlainObject(responseBody) && - responseBody.error === INFURA_BLOCKED_KEY - ) { - networkStatus = NetworkStatus.Blocked; - } else if (error.code === errorCodes.rpc.internal) { - networkStatus = NetworkStatus.Unknown; - } else { - networkStatus = NetworkStatus.Unavailable; - } - } else { - log.warn( - 'NetworkController - could not determine network status', - error, - ); - networkStatus = NetworkStatus.Unknown; - } - } - - if (networkChanged) { - // If the network has changed, then `lookupNetwork` either has been or is - // in the process of being called, so we don't need to go further. - return; - } - this.#messenger.unsubscribe('NetworkController:networkDidChange', listener); - - this.store.updateState({ - networkStatus, - }); - - if (networkStatus === NetworkStatus.Available) { - const { networkDetails } = this.store.getState(); - this.store.updateState({ - networkId, - networkDetails: { - ...networkDetails, - EIPS: { - ...networkDetails.EIPS, - 1559: supportsEIP1559, - }, - }, - }); - } else { - this.#resetNetworkId(); - this.#resetNetworkDetails(); - } - - if (isInfura) { - if (networkStatus === NetworkStatus.Available) { - this.#messenger.publish('NetworkController:infuraIsUnblocked'); - } else if (networkStatus === NetworkStatus.Blocked) { - this.#messenger.publish('NetworkController:infuraIsBlocked'); - } - } else { - // Always publish infuraIsUnblocked regardless of network status to - // prevent consumers from being stuck in a blocked state if they were - // previously connected to an Infura network that was blocked - this.#messenger.publish('NetworkController:infuraIsUnblocked'); - } - } - - /** - * Switches to the network specified by a network configuration. - * - * @param networkConfigurationId - The unique identifier that refers to a - * previously added network configuration. - * @returns The URL of the RPC endpoint representing the newly switched - * network. - */ - async setActiveNetwork(networkConfigurationId: NetworkConfigurationId) { - const targetNetwork = - this.store.getState().networkConfigurations[networkConfigurationId]; - - if (!targetNetwork) { - throw new Error( - `networkConfigurationId ${networkConfigurationId} does not match a configured networkConfiguration`, - ); - } - - await this.#setProviderConfig({ - type: NETWORK_TYPES.RPC, - ...targetNetwork, - }); - - return targetNetwork.rpcUrl; - } - - /** - * Switches to an Infura-supported network. - * - * @param type - The shortname of the network. - * @throws if the `type` is "rpc" or if it is not a known Infura-supported - * network. - */ - async setProviderType(type: string) { - assert.notStrictEqual( - type, - NETWORK_TYPES.RPC, - `NetworkController - cannot call "setProviderType" with type "${NETWORK_TYPES.RPC}". Use "setActiveNetwork"`, - ); - assert.ok( - isInfuraProviderType(type), - `Unknown Infura provider type "${type}".`, - ); - const network = BUILT_IN_INFURA_NETWORKS[type]; - await this.#setProviderConfig({ - type, - rpcUrl: undefined, - chainId: network.chainId, - ticker: 'ticker' in network ? network.ticker : 'ETH', - nickname: undefined, - rpcPrefs: { blockExplorerUrl: network.blockExplorerUrl }, - id: undefined, - }); - } - - /** - * Re-initializes the provider and block tracker for the current network. - */ - async resetConnection() { - await this.#setProviderConfig(this.store.getState().providerConfig); - } - - /** - * Switches to the previous network, assuming that the current network is - * different than the initial network (if it is, then this is equivalent to - * calling `resetConnection`). - */ - async rollbackToPreviousProvider() { - const config = this.#previousProviderConfig; - this.store.updateState({ - providerConfig: config, - }); - await this.#switchNetwork(config); - } - - /** - * Fetches the latest block for the network. - * - * @param provider - A provider, which is guaranteed to be available. - * @returns A promise that either resolves to the block header or null if - * there is no latest block, or rejects with an error. - */ - #getLatestBlock(provider: SafeEventEmitterProvider): Promise { - return new Promise((resolve, reject) => { - const ethQuery = new EthQuery(provider); - ethQuery.sendAsync<['latest', false], Block | null>( - { method: 'eth_getBlockByNumber', params: ['latest', false] }, - (...args) => { - if (args[0] === null) { - resolve(args[1]); - } else { - reject(args[0]); - } - }, - ); - }); - } - - /** - * Fetches the network ID for the network. - * - * @param provider - A provider, which is guaranteed to be available. - * @returns A promise that either resolves to the network ID, or rejects with - * an error. - */ - async #getNetworkId(provider: SafeEventEmitterProvider): Promise { - const ethQuery = new EthQuery(provider); - return await new Promise((resolve, reject) => { - ethQuery.sendAsync( - { method: 'net_version' }, - (...args) => { - if (args[0] === null) { - resolve(args[1]); - } else { - reject(args[0]); - } - }, - ); - }); - } - - /** - * Clears the stored network ID. - */ - #resetNetworkId(): void { - this.store.updateState({ - networkId: buildDefaultNetworkIdState(), - }); - } - - /** - * Resets network status to the default ("unknown"). - */ - #resetNetworkStatus(): void { - this.store.updateState({ - networkStatus: buildDefaultNetworkStatusState(), - }); - } - - /** - * Clears details previously stored for the network. - */ - #resetNetworkDetails(): void { - this.store.updateState({ - networkDetails: buildDefaultNetworkDetailsState(), - }); - } - - /** - * Stores the given provider configuration representing a network in state, - * then uses it to create a new provider for that network. - * - * @param providerConfig - The provider configuration. - */ - async #setProviderConfig(providerConfig: ProviderConfiguration) { - this.#previousProviderConfig = this.store.getState().providerConfig; - this.store.updateState({ providerConfig }); - await this.#switchNetwork(providerConfig); - } - - /** - * Retrieves the latest block from the currently selected network; if the - * block has a `baseFeePerGas` property, then we know that the network - * supports EIP-1559; otherwise it doesn't. - * - * @param provider - A provider, which is guaranteed to be available. - * @returns A promise that resolves to true if the network supports EIP-1559 - * and false otherwise. - */ - async #determineEIP1559Compatibility( - provider: SafeEventEmitterProvider, - ): Promise { - const latestBlock = await this.#getLatestBlock(provider); - return latestBlock?.baseFeePerGas !== undefined; - } - - /** - * Executes a series of steps to change the current network: - * - * 1. Notifies subscribers that the network is about to change. - * 2. Clears state associated with the current network. - * 3. Creates a new network client along with a provider for the desired - * network. - * 4. Notifies subscribes that the network has changed. - * - * @param providerConfig - The provider configuration object that specifies - * the new network. - */ - async #switchNetwork(providerConfig: ProviderConfiguration) { - const { type, rpcUrl, chainId } = providerConfig; - this.#messenger.publish('NetworkController:networkWillChange'); - this.#resetNetworkId(); - this.#resetNetworkStatus(); - this.#resetNetworkDetails(); - this.#configureProvider({ type, rpcUrl, chainId }); - this.#messenger.publish('NetworkController:networkDidChange'); - await this.lookupNetwork(); - } - - /** - * Creates a network client (a stack of middleware along with a provider and - * block tracker) to talk to a network. - * - * @param args - The arguments. - * @param args.type - The provider type. - * @param args.rpcUrl - The URL of the RPC endpoint that represents the - * network. Only used for non-Infura networks. - * @param args.chainId - The chain ID of the network (as per EIP-155). Only - * used for non-Infura-supported networks (as we already know the chain ID of - * any Infura-supported network). - * @throws if the `type` if not a known Infura-supported network. - */ - #configureProvider({ - type, - rpcUrl, - chainId, - }: { - type: ProviderType; - rpcUrl: string | undefined; - chainId: Hex | undefined; - }): void { - const isInfura = isInfuraProviderType(type); - if (isInfura) { - this.#configureInfuraProvider({ - type, - infuraProjectId: this.#infuraProjectId, - }); - } else if (type === NETWORK_TYPES.RPC) { - if (chainId === undefined) { - throw new Error('chainId must be provided for custom RPC endpoints'); - } - if (rpcUrl === undefined) { - throw new Error('rpcUrl must be provided for custom RPC endpoints'); - } - this.#configureStandardProvider(rpcUrl, chainId); - } else { - throw new Error(`Unrecognized network type: '${type}'`); - } - } - - /** - * Creates a new instance of EthQuery that wraps the current provider and - * saves it for future usage. - */ - #registerProvider() { - const { provider } = this.getProviderAndBlockTracker(); - - if (provider) { - this.#ethQuery = new EthQuery(provider); - } - } - - /** - * Creates a network client (a stack of middleware along with a provider and - * block tracker) to talk to an Infura-supported network. - * - * @param args - The arguments. - * @param args.type - The shortname of the Infura network (see - * {@link NETWORK_TYPES}). - * @param args.infuraProjectId - An Infura API key. ("Project ID" is a - * now-obsolete term we've retained for backward compatibility.) - */ - #configureInfuraProvider({ - type, - infuraProjectId, - }: { - type: BuiltInInfuraNetwork; - infuraProjectId: NetworkControllerOptions['infuraProjectId']; - }): void { - log.info('NetworkController - #configureInfuraProvider', type); - const { provider, blockTracker } = createNetworkClient({ - network: type, - infuraProjectId, - type: NetworkClientType.Infura, - }); - this.#updateProvider(provider, blockTracker); - } - - /** - * Creates a network client (a stack of middleware along with a provider and - * block tracker) to talk to a non-Infura-supported network. - * - * @param rpcUrl - The URL of the RPC endpoint that represents the network. - * @param chainId - The chain ID of the network (as per EIP-155). - */ - #configureStandardProvider(rpcUrl: string, chainId: ChainId): void { - log.info('NetworkController - #configureStandardProvider', rpcUrl); - const { provider, blockTracker } = createNetworkClient({ - chainId, - rpcUrl, - type: NetworkClientType.Custom, - }); - this.#updateProvider(provider, blockTracker); - } - - /** - * Given a provider and a block tracker, updates any proxies pointing to - * these objects that have been previously set, or initializes any proxies - * that have not been previously set, then creates an instance of EthQuery - * that wraps the provider. - * - * @param provider - The provider. - * @param blockTracker - The block tracker. - */ - #updateProvider( - provider: SafeEventEmitterProvider, - blockTracker: PollingBlockTracker, - ) { - this.#setProviderAndBlockTracker({ - provider, - blockTracker, - }); - this.#registerProvider(); - } - - /** - * Given a provider and a block tracker, updates any proxies pointing to - * these objects that have been previously set, or initializes any proxies - * that have not been previously set. - * - * @param args - The arguments. - * @param args.provider - The provider. - * @param args.blockTracker - The block tracker. - */ - #setProviderAndBlockTracker({ - provider, - blockTracker, - }: { - provider: SafeEventEmitterProvider; - blockTracker: PollingBlockTracker; - }): void { - // update or initialize proxies - if (this.#providerProxy) { - this.#providerProxy.setTarget(provider); - } else { - this.#providerProxy = createSwappableProxy(provider); - } - if (this.#blockTrackerProxy) { - this.#blockTrackerProxy.setTarget(blockTracker); - } else { - this.#blockTrackerProxy = createEventEmitterProxy(blockTracker, { - eventFilter: 'skipInternal', - }); - } - // set new provider and blockTracker - this.#provider = provider; - this.#blockTracker = blockTracker; - } - - /** - * Network Configuration management functions - */ - - /** - * Updates an existing network configuration matching the same RPC URL as the - * given network configuration; otherwise adds the network configuration. - * Following the upsert, the `trackMetaMetricsEvent` callback specified - * via the NetworkController constructor will be called to (presumably) create - * a MetaMetrics event. - * - * @param networkConfiguration - The network configuration to upsert. - * @param networkConfiguration.chainId - The chain ID of the network as per - * EIP-155. - * @param networkConfiguration.ticker - The shortname of the currency used by - * the network. - * @param networkConfiguration.nickname - The user-customizable name of the - * network. - * @param networkConfiguration.rpcPrefs - User-customizable details for the - * network. - * @param networkConfiguration.rpcUrl - The URL of the RPC endpoint. - * @param additionalArgs - Additional arguments. - * @param additionalArgs.setActive - Switches to the network specified by - * the given network configuration following the upsert. - * @param additionalArgs.referrer - The site from which the call originated, - * or 'metamask' for internal calls; used for event metrics. - * @param additionalArgs.source - Where the metric event originated (i.e. from - * a dapp or from the network form); used for event metrics. - * @throws if the `chainID` does not match EIP-155 or is too large. - * @throws if `rpcUrl` is not a valid URL. - * @returns The ID for the added or updated network configuration. - */ - async upsertNetworkConfiguration( - { - rpcUrl, - chainId, - ticker, - nickname, - rpcPrefs, - }: Omit, - { - setActive = false, - referrer, - source, - }: { - setActive?: boolean; - referrer: string; - source: string; - }, - ): Promise { - assert.ok( - isPrefixedFormattedHexString(chainId), - `Invalid chain ID "${chainId}": invalid hex string.`, - ); - assert.ok( - isSafeChainId(parseInt(chainId, 16)), - `Invalid chain ID "${chainId}": numerical value greater than max safe value.`, - ); - - if (!rpcUrl) { - throw new Error( - 'An rpcUrl is required to add or update network configuration', - ); - } - - if (!referrer || !source) { - throw new Error( - 'referrer and source are required arguments for adding or updating a network configuration', - ); - } - - try { - // eslint-disable-next-line no-new - new URL(rpcUrl); - } catch (e) { - if (isErrorWithMessage(e) && e.message.includes('Invalid URL')) { - throw new Error('rpcUrl must be a valid URL'); - } - } - - if (!ticker) { - throw new Error( - 'A ticker is required to add or update networkConfiguration', - ); - } - - const { networkConfigurations } = this.store.getState(); - const newNetworkConfiguration = { - rpcUrl, - chainId, - ticker, - nickname, - rpcPrefs, - }; - - const oldNetworkConfigurationId = Object.values(networkConfigurations).find( - (networkConfiguration) => - networkConfiguration.rpcUrl?.toLowerCase() === rpcUrl?.toLowerCase(), - )?.id; - - const newNetworkConfigurationId = oldNetworkConfigurationId || uuid(); - this.store.updateState({ - networkConfigurations: { - ...networkConfigurations, - [newNetworkConfigurationId]: { - ...newNetworkConfiguration, - id: newNetworkConfigurationId, - }, - }, - }); - - if (!oldNetworkConfigurationId) { - this.#trackMetaMetricsEvent({ - event: 'Custom Network Added', - category: MetaMetricsEventCategory.Network, - referrer: { - url: referrer, - }, - properties: { - chain_id: chainId, - symbol: ticker, - source, - }, - }); - } - - if (setActive) { - await this.setActiveNetwork(newNetworkConfigurationId); - } - - return newNetworkConfigurationId; - } - - /** - * Removes a network configuration from state. - * - * @param networkConfigurationId - The unique id for the network configuration - * to remove. - */ - removeNetworkConfiguration(networkConfigurationId: NetworkConfigurationId) { - if (!this.store.getState().networkConfigurations[networkConfigurationId]) { - throw new Error( - `networkConfigurationId ${networkConfigurationId} does not match a configured networkConfiguration`, - ); - } - const networkConfigurations = { - ...this.store.getState().networkConfigurations, - }; - delete networkConfigurations[networkConfigurationId]; - this.store.updateState({ - networkConfigurations, - }); - } -} diff --git a/app/scripts/controllers/network/provider-api-tests/block-hash-in-response.ts b/app/scripts/controllers/network/provider-api-tests/block-hash-in-response.ts deleted file mode 100644 index 4ee0f633d..000000000 --- a/app/scripts/controllers/network/provider-api-tests/block-hash-in-response.ts +++ /dev/null @@ -1,284 +0,0 @@ -/* eslint-disable jest/require-top-level-describe, jest/no-export */ - -import { - ProviderType, - withMockedCommunications, - withNetworkClient, -} from './helpers'; - -type TestsForRpcMethodThatCheckForBlockHashInResponseOptions = { - providerType: ProviderType; - numberOfParameters: number; -}; - -/** - * Defines tests which exercise the behavior exhibited by an RPC method that - * use `blockHash` in the response data to determine whether the response is - * cacheable. - * - * @param method - The name of the RPC method under test. - * @param additionalArgs - Additional arguments. - * @param additionalArgs.numberOfParameters - The number of parameters supported - * by the method under test. - * @param additionalArgs.providerType - The type of provider being tested; - * either `infura` or `custom` (default: "infura"). - */ -export function testsForRpcMethodsThatCheckForBlockHashInResponse( - method: string, - { - numberOfParameters, - providerType, - }: TestsForRpcMethodThatCheckForBlockHashInResponseOptions, -) { - if (providerType !== 'infura' && providerType !== 'custom') { - throw new Error( - `providerType must be either "infura" or "custom", was "${providerType}" instead`, - ); - } - - it('does not hit the RPC endpoint more than once for identical requests and it has a valid blockHash', async () => { - const requests = [{ method }, { method }]; - const mockResult = { blockHash: '0x1' }; - - await withMockedCommunications({ providerType }, async (comms) => { - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request: requests[0], - response: { result: mockResult }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual([mockResult, mockResult]); - }); - }); - - it('hits the RPC endpoint and does not reuse the result of a previous request if the latest block number was updated since', async () => { - const requests = [{ method }, { method }]; - const mockResults = [{ blockHash: '0x100' }, { blockHash: '0x200' }]; - - await withMockedCommunications({ providerType }, async (comms) => { - // Note that we have to mock these requests in a specific order. The - // first block tracker request occurs because of the first RPC - // request. The second block tracker request, however, does not occur - // because of the second RPC request, but rather because we call - // `clock.runAll()` below. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x1' }); - comms.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - }); - comms.mockNextBlockTrackerRequest({ blockNumber: '0x2' }); - comms.mockRpcCall({ - request: requests[1], - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - async (client) => { - const firstResult = await client.makeRpcCall(requests[0]); - // Proceed to the next iteration of the block tracker so that a new - // block is fetched and the current block is updated. - client.clock.runAll(); - const secondResult = await client.makeRpcCall(requests[1]); - return [firstResult, secondResult]; - }, - ); - - expect(results).toStrictEqual(mockResults); - }); - }); - - it('does not reuse the result of a previous request if result.blockHash was null', async () => { - const requests = [{ method }, { method }]; - const mockResults = [ - { blockHash: null, extra: 'some value' }, - { blockHash: '0x100', extra: 'some other value' }, - ]; - - await withMockedCommunications({ providerType }, async (comms) => { - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - }); - comms.mockRpcCall({ - request: requests[1], - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual(mockResults); - }); - }); - - it('does not reuse the result of a previous request if result.blockHash was undefined', async () => { - const requests = [{ method }, { method }]; - const mockResults = [ - { extra: 'some value' }, - { blockHash: '0x100', extra: 'some other value' }, - ]; - - await withMockedCommunications({ providerType }, async (comms) => { - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - }); - comms.mockRpcCall({ - request: requests[1], - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual(mockResults); - }); - }); - - it('does not reuse the result of a previous request if result.blockHash was "0x0000000000000000000000000000000000000000000000000000000000000000"', async () => { - const requests = [{ method }, { method }]; - const mockResults = [ - { - blockHash: - '0x0000000000000000000000000000000000000000000000000000000000000000', - extra: 'some value', - }, - { blockHash: '0x100', extra: 'some other value' }, - ]; - - await withMockedCommunications({ providerType }, async (comms) => { - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - }); - comms.mockRpcCall({ - request: requests[1], - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual(mockResults); - }); - }); - - for (const emptyValue of [null, undefined, '\u003cnil\u003e']) { - it(`does not retry an empty response of "${emptyValue}"`, async () => { - const request = { method }; - const mockResult = emptyValue; - - await withMockedCommunications({ providerType }, async (comms) => { - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request, - response: { result: mockResult }, - }); - - const result = await withNetworkClient( - { providerType }, - ({ makeRpcCall }) => makeRpcCall(request), - ); - - expect(result).toStrictEqual(mockResult); - }); - }); - - it(`does not reuse the result of a previous request if it was "${emptyValue}"`, async () => { - const requests = [{ method }, { method }]; - const mockResults = [emptyValue, { blockHash: '0x100' }]; - - await withMockedCommunications({ providerType }, async (comms) => { - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - }); - comms.mockRpcCall({ - request: requests[1], - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual(mockResults); - }); - }); - } - - for (const paramIndex of [...Array(numberOfParameters).keys()]) { - it(`does not reuse the result of a previous request with a valid blockHash if parameter at index "${paramIndex}" differs`, async () => { - const firstMockParams = [ - ...new Array(numberOfParameters).fill('some value'), - ]; - const secondMockParams = firstMockParams.slice(); - secondMockParams[paramIndex] = 'another value'; - const requests = [ - { - method, - params: firstMockParams, - }, - { method, params: secondMockParams }, - ]; - const mockResults = [{ blockHash: '0x100' }, { blockHash: '0x200' }]; - - await withMockedCommunications({ providerType }, async (comms) => { - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - }); - comms.mockRpcCall({ - request: requests[1], - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual([mockResults[0], mockResults[1]]); - }); - }); - } -} diff --git a/app/scripts/controllers/network/provider-api-tests/block-param.ts b/app/scripts/controllers/network/provider-api-tests/block-param.ts deleted file mode 100644 index 92fb65f04..000000000 --- a/app/scripts/controllers/network/provider-api-tests/block-param.ts +++ /dev/null @@ -1,2070 +0,0 @@ -/* eslint-disable jest/require-top-level-describe, jest/no-export */ - -import { - buildMockParams, - buildRequestWithReplacedBlockParam, - ProviderType, - waitForPromiseToBeFulfilledAfterRunningAllTimers, - withMockedCommunications, - withNetworkClient, -} from './helpers'; -import { - buildFetchFailedErrorMessage, - buildInfuraClientRetriesExhaustedErrorMessage, - buildJsonRpcEngineEmptyResponseErrorMessage, -} from './shared-tests'; - -type TestsForRpcMethodSupportingBlockParam = { - providerType: ProviderType; - blockParamIndex: number; - numberOfParameters: number; -}; - -/** - * Defines tests which exercise the behavior exhibited by an RPC method that - * takes a block parameter. The value of this parameter can be either a block - * number or a block tag ("latest", "earliest", or "pending") and affects how - * the method is cached. - * - * @param method - The name of the RPC method under test. - * @param additionalArgs - Additional arguments. - * @param additionalArgs.blockParamIndex - The index of the block parameter. - * @param additionalArgs.numberOfParameters - The number of parameters supported by the method under test. - * @param additionalArgs.providerType - The type of provider being tested. - * either `infura` or `custom` (default: "infura"). - */ -/* eslint-disable-next-line jest/no-export */ -export function testsForRpcMethodSupportingBlockParam( - method: string, - { - blockParamIndex, - numberOfParameters, - providerType, - }: TestsForRpcMethodSupportingBlockParam, -) { - describe.each([ - ['given no block tag', undefined], - ['given a block tag of "latest"', 'latest'], - ])('%s', (_desc, blockParam) => { - it('does not hit the RPC endpoint more than once for identical requests', async () => { - const requests = [ - { - method, - params: buildMockParams({ blockParamIndex, blockParam }), - }, - { method, params: buildMockParams({ blockParamIndex, blockParam }) }, - ]; - const mockResults = ['first result', 'second result']; - - await withMockedCommunications({ providerType }, async (comms) => { - // The first time a block-cacheable request is made, the block-cache - // middleware will request the latest block number through the block - // tracker to determine the cache key. Later, the block-ref - // middleware will request the latest block number again to resolve - // the value of "latest", but the block number is cached once made, - // so we only need to mock the request once. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // The block-ref middleware will make the request as specified - // except that the block param is replaced with the latest block - // number. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - requests[0], - blockParamIndex, - '0x100', - ), - response: { result: mockResults[0] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual([mockResults[0], mockResults[0]]); - }); - }); - - for (const paramIndex of [...Array(numberOfParameters).keys()]) { - if (paramIndex === blockParamIndex) { - // testing changes in block param is covered under later tests - continue; - } - - it(`does not reuse the result of a previous request if parameter at index "${paramIndex}" differs`, async () => { - const firstMockParams = [ - ...new Array(numberOfParameters).fill('some value'), - ]; - firstMockParams[blockParamIndex] = blockParam; - const secondMockParams = firstMockParams.slice(); - secondMockParams[paramIndex] = 'another value'; - const requests = [ - { - method, - params: firstMockParams, - }, - { method, params: secondMockParams }, - ]; - const mockResults = ['first result', 'second result']; - - await withMockedCommunications({ providerType }, async (comms) => { - // The first time a block-cacheable request is made, the block-cache - // middleware will request the latest block number through the block - // tracker to determine the cache key. Later, the block-ref - // middleware will request the latest block number again to resolve - // the value of "latest", but the block number is cached once made, - // so we only need to mock the request once. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // The block-ref middleware will make the request as specified - // except that the block param is replaced with the latest block - // number. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - requests[0], - blockParamIndex, - '0x100', - ), - response: { result: mockResults[0] }, - }); - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - requests[1], - blockParamIndex, - '0x100', - ), - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual([mockResults[0], mockResults[1]]); - }); - }); - } - - it('hits the RPC endpoint and does not reuse the result of a previous request if the latest block number was updated since', async () => { - const requests = [ - { method, params: buildMockParams({ blockParamIndex, blockParam }) }, - { method, params: buildMockParams({ blockParamIndex, blockParam }) }, - ]; - const mockResults = ['first result', 'second result']; - - await withMockedCommunications({ providerType }, async (comms) => { - // Note that we have to mock these requests in a specific order. - // The first block tracker request occurs because of the first RPC - // request. The second block tracker request, however, does not - // occur because of the second RPC request, but rather because we - // call `clock.runAll()` below. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // The block-ref middleware will make the request as specified - // except that the block param is replaced with the latest block - // number. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - requests[0], - blockParamIndex, - '0x100', - ), - response: { result: mockResults[0] }, - }); - comms.mockNextBlockTrackerRequest({ blockNumber: '0x200' }); - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - requests[1], - blockParamIndex, - '0x200', - ), - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - async (client) => { - const firstResult = await client.makeRpcCall(requests[0]); - // Proceed to the next iteration of the block tracker so that a - // new block is fetched and the current block is updated. - client.clock.runAll(); - const secondResult = await client.makeRpcCall(requests[1]); - return [firstResult, secondResult]; - }, - ); - - expect(results).toStrictEqual(mockResults); - }); - }); - - for (const emptyValue of [null, undefined, '\u003cnil\u003e']) { - it(`does not retry an empty response of "${emptyValue}"`, async () => { - const request = { - method, - params: buildMockParams({ blockParamIndex, blockParam }), - }; - const mockResult = emptyValue; - - await withMockedCommunications({ providerType }, async (comms) => { - // The first time a block-cacheable request is made, the - // block-cache middleware will request the latest block number - // through the block tracker to determine the cache key. Later, - // the block-ref middleware will request the latest block number - // again to resolve the value of "latest", but the block number is - // cached once made, so we only need to mock the request once. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // The block-ref middleware will make the request as specified - // except that the block param is replaced with the latest block - // number. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { result: mockResult }, - }); - - const result = await withNetworkClient( - { providerType }, - ({ makeRpcCall }) => makeRpcCall(request), - ); - - expect(result).toStrictEqual(mockResult); - }); - }); - - it(`does not reuse the result of a previous request if it was "${emptyValue}"`, async () => { - const requests = [ - { method, params: buildMockParams({ blockParamIndex, blockParam }) }, - { method, params: buildMockParams({ blockParamIndex, blockParam }) }, - ]; - const mockResults = [emptyValue, 'some result']; - - await withMockedCommunications({ providerType }, async (comms) => { - // The first time a block-cacheable request is made, the - // block-cache middleware will request the latest block number - // through the block tracker to determine the cache key. Later, - // the block-ref middleware will request the latest block number - // again to resolve the value of "latest", but the block number is - // cached once made, so we only need to mock the request once. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // The block-ref middleware will make the request as specified - // except that the block param is replaced with the latest block - // number. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - requests[0], - blockParamIndex, - '0x100', - ), - response: { result: mockResults[0] }, - }); - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - requests[1], - blockParamIndex, - '0x100', - ), - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual(mockResults); - }); - }); - } - - it('queues requests while a previous identical call is still pending, then runs the queue when it finishes, reusing the result from the first request', async () => { - const requests = [ - { method, params: buildMockParams({ blockParamIndex, blockParam }) }, - { method, params: buildMockParams({ blockParamIndex, blockParam }) }, - { method, params: buildMockParams({ blockParamIndex, blockParam }) }, - ]; - const mockResults = ['first result', 'second result', 'third result']; - - await withMockedCommunications({ providerType }, async (comms) => { - // The first time a block-cacheable request is made, the - // block-cache middleware will request the latest block number - // through the block tracker to determine the cache key. Later, - // the block-ref middleware will request the latest block number - // again to resolve the value of "latest", but the block number is - // cached once made, so we only need to mock the request once. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // The block-ref middleware will make the request as specified - // except that the block param is replaced with the latest block - // number, and we delay it. - comms.mockRpcCall({ - delay: 100, - request: buildRequestWithReplacedBlockParam( - requests[0], - blockParamIndex, - '0x100', - ), - response: { result: mockResults[0] }, - }); - // The previous two requests will happen again, in the same order. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - requests[1], - blockParamIndex, - '0x100', - ), - response: { result: mockResults[1] }, - }); - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - requests[2], - blockParamIndex, - '0x100', - ), - response: { result: mockResults[2] }, - }); - - const results = await withNetworkClient( - { providerType }, - async (client) => { - const resultPromises = [ - client.makeRpcCall(requests[0]), - client.makeRpcCall(requests[1]), - client.makeRpcCall(requests[2]), - ]; - const firstResult = await resultPromises[0]; - // The inflight cache middleware uses setTimeout to run the - // handlers, so run them now - client.clock.runAll(); - const remainingResults = await Promise.all(resultPromises.slice(1)); - return [firstResult, ...remainingResults]; - }, - ); - - expect(results).toStrictEqual([ - mockResults[0], - mockResults[0], - mockResults[0], - ]); - }); - }); - - it('throws an error with a custom message if the request to the RPC endpoint returns a 405 response', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - - // The first time a block-cacheable request is made, the - // block-cache middleware will request the latest block number - // through the block tracker to determine the cache key. Later, - // the block-ref middleware will request the latest block number - // again to resolve the value of "latest", but the block number is - // cached once made, so we only need to mock the request once. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // The block-ref middleware will make the request as specified - // except that the block param is replaced with the latest block - // number. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { - httpStatus: 405, - }, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - 'The method does not exist / is not available', - ); - }); - }); - - // There is a difference in how we are testing the Infura middleware vs. the - // custom RPC middleware (or, more specifically, the fetch middleware) - // because of what both middleware treat as rate limiting errors. In this - // case, the fetch middleware treats a 418 response from the RPC endpoint as - // such an error, whereas to the Infura middleware, it is a 429 response. - if (providerType === 'infura') { - it('throws a generic, undescriptive error if the request to the RPC endpoint returns a 418 response', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - id: 123, - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - - // The first time a block-cacheable request is made, the - // block-cache middleware will request the latest block number - // through the block tracker to determine the cache key. Later, - // the block-ref middleware will request the latest block number - // again to resolve the value of "latest", but the block number is - // cached once made, so we only need to mock the request once. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // The block-ref middleware will make the request as specified - // except that the block param is replaced with the latest block - // number. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { - httpStatus: 418, - }, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - '{"id":123,"jsonrpc":"2.0"}', - ); - }); - }); - - it('throws an error with a custom message if the request to the RPC endpoint returns a 429 response', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - - // The first time a block-cacheable request is made, the - // block-cache middleware will request the latest block number - // through the block tracker to determine the cache key. Later, - // the block-ref middleware will request the latest block number - // again to resolve the value of "latest", but the block number is - // cached once made, so we only need to mock the request once. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // The block-ref middleware will make the request as specified - // except that the block param is replaced with the latest block - // number. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { - httpStatus: 429, - }, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - 'Request is being rate limited', - ); - }); - }); - } else { - it('throws an error with a custom message if the request to the RPC endpoint returns a 418 response', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - - // The first time a block-cacheable request is made, the - // block-cache middleware will request the latest block number - // through the block tracker to determine the cache key. Later, - // the block-ref middleware will request the latest block number - // again to resolve the value of "latest", but the block number is - // cached once made, so we only need to mock the request once. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // The block-ref middleware will make the request as specified - // except that the block param is replaced with the latest block - // number. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { - httpStatus: 418, - }, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - 'Request is being rate limited.', - ); - }); - }); - - it('throws an undescriptive error if the request to the RPC endpoint returns a 429 response', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - - // The first time a block-cacheable request is made, the - // block-cache middleware will request the latest block number - // through the block tracker to determine the cache key. Later, - // the block-ref middleware will request the latest block number - // again to resolve the value of "latest", but the block number is - // cached once made, so we only need to mock the request once. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // The block-ref middleware will make the request as specified - // except that the block param is replaced with the latest block - // number. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { - httpStatus: 429, - }, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - "Non-200 status code: '429'", - ); - }); - }); - } - - it('throws an undescriptive error message if the request to the RPC endpoint returns a response that is not 405, 418, 429, 503, or 504', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - - // The first time a block-cacheable request is made, the - // block-cache middleware will request the latest block number - // through the block tracker to determine the cache key. Later, - // the block-ref middleware will request the latest block number - // again to resolve the value of "latest", but the block number is - // cached once made, so we only need to mock the request once. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // The block-ref middleware will make the request as specified - // except that the block param is replaced with the latest block - // number. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { - id: 12345, - jsonrpc: '2.0', - error: 'some error', - httpStatus: 420, - }, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - const msg = - providerType === 'infura' - ? '{"id":12345,"jsonrpc":"2.0","error":"some error"}' - : "Non-200 status code: '420'"; - await expect(promiseForResult).rejects.toThrow(msg); - }); - }); - - [503, 504].forEach((httpStatus) => { - it(`retries the request to the RPC endpoint up to 5 times if it returns a ${httpStatus} response, returning the successful result if there is one on the 5th try`, async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - - // The first time a block-cacheable request is made, the - // block-cache middleware will request the latest block number - // through the block tracker to determine the cache key. Later, - // the block-ref middleware will request the latest block number - // again to resolve the value of "latest", but the block number is - // cached once made, so we only need to mock the request once. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // The block-ref middleware will make the request as specified - // except that the block param is replaced with the latest block - // number. - // - // Here we have the request fail for the first 4 tries, then succeed - // on the 5th try. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { - error: 'some error', - httpStatus, - }, - times: 4, - }); - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { - result: 'the result', - httpStatus: 200, - }, - }); - const result = await withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - expect(result).toStrictEqual('the result'); - }); - }); - - // Both the Infura middleware and custom RPC middleware detect a 503 or - // 504 response and retry the request to the RPC endpoint automatically - // but differ in what sort of response is returned when the number of - // retries is exhausted. - if (providerType === 'infura') { - it(`causes a request to fail with a custom error if the request to the RPC endpoint returns a ${httpStatus} response 5 times in a row`, async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - - // The first time a block-cacheable request is made, the - // block-cache middleware will request the latest block number - // through the block tracker to determine the cache key. Later, - // the block-ref middleware will request the latest block number - // again to resolve the value of "latest", but the block number is - // cached once made, so we only need to mock the request once. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // The block-ref middleware will make the request as specified - // except that the block param is replaced with the latest block - // number. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { - error: 'Some error', - httpStatus, - }, - times: 5, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - await expect(promiseForResult).rejects.toThrow( - buildInfuraClientRetriesExhaustedErrorMessage('Gateway timeout'), - ); - }); - }); - } else { - it(`produces an empty response if the request to the RPC endpoint returns a ${httpStatus} response 5 times in a row`, async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - - // The first time a block-cacheable request is made, the - // block-cache middleware will request the latest block number - // through the block tracker to determine the cache key. Later, - // the block-ref middleware will request the latest block number - // again to resolve the value of "latest", but the block number is - // cached once made, so we only need to mock the request once. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // The block-ref middleware will make the request as specified - // except that the block param is replaced with the latest block - // number. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { - error: 'Some error', - httpStatus, - }, - times: 5, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - await expect(promiseForResult).rejects.toThrow( - buildJsonRpcEngineEmptyResponseErrorMessage(method), - ); - }); - }); - } - }); - - it('retries the request to the RPC endpoint up to 5 times if an "ETIMEDOUT" error is thrown while making the request, returning the successful result if there is one on the 5th try', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - - // The first time a block-cacheable request is made, the - // block-cache middleware will request the latest block number - // through the block tracker to determine the cache key. Later, - // the block-ref middleware will request the latest block number - // again to resolve the value of "latest", but the block number is - // cached once made, so we only need to mock the request once. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // The block-ref middleware will make the request as specified - // except that the block param is replaced with the latest block - // number. - // - // Here we have the request fail for the first 4 tries, then - // succeed on the 5th try. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - error: 'ETIMEDOUT: Some message', - times: 4, - }); - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { - result: 'the result', - httpStatus: 200, - }, - }); - - const result = await withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - expect(result).toStrictEqual('the result'); - }); - }); - - // Both the Infura and fetch middleware detect ETIMEDOUT errors and will - // automatically retry the request to the RPC endpoint in question, but each - // produces a different error if the number of retries is exhausted. - if (providerType === 'infura') { - it('causes a request to fail with a custom error if an "ETIMEDOUT" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - const errorMessage = 'ETIMEDOUT: Some message'; - - // The first time a block-cacheable request is made, the - // block-cache middleware will request the latest block number - // through the block tracker to determine the cache key. Later, - // the block-ref middleware will request the latest block number - // again to resolve the value of "latest", but the block number is - // cached once made, so we only need to mock the request once. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // The block-ref middleware will make the request as specified - // except that the block param is replaced with the latest block - // number. - - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - error: errorMessage, - times: 5, - }); - - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - await expect(promiseForResult).rejects.toThrow( - buildInfuraClientRetriesExhaustedErrorMessage(errorMessage), - ); - }); - }); - } else { - it('produces an empty response if an "ETIMEDOUT" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - const errorMessage = 'ETIMEDOUT: Some message'; - - // The first time a block-cacheable request is made, the - // block-cache middleware will request the latest block number - // through the block tracker to determine the cache key. Later, - // the block-ref middleware will request the latest block number - // again to resolve the value of "latest", but the block number is - // cached once made, so we only need to mock the request once. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // The block-ref middleware will make the request as specified - // except that the block param is replaced with the latest block - // number. - - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - error: errorMessage, - times: 5, - }); - - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - await expect(promiseForResult).rejects.toThrow( - buildJsonRpcEngineEmptyResponseErrorMessage(method), - ); - }); - }); - } - - // The Infura middleware treats a response that contains an ECONNRESET - // message as an innocuous error that is likely to disappear on a retry. The - // custom RPC middleware, on the other hand, does not specially handle this - // error. - if (providerType === 'infura') { - it('retries the request to the RPC endpoint up to 5 times if an "ECONNRESET" error is thrown while making the request, returning the successful result if there is one on the 5th try', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - - // The first time a block-cacheable request is made, the - // block-cache middleware will request the latest block number - // through the block tracker to determine the cache key. Later, - // the block-ref middleware will request the latest block number - // again to resolve the value of "latest", but the block number is - // cached once made, so we only need to mock the request once. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // The block-ref middleware will make the request as specified - // except that the block param is replaced with the latest block - // number. - // - // Here we have the request fail for the first 4 tries, then - // succeed on the 5th try. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - error: 'ECONNRESET: Some message', - times: 4, - }); - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { - result: 'the result', - httpStatus: 200, - }, - }); - - const result = await withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - expect(result).toStrictEqual('the result'); - }); - }); - - it('causes a request to fail with a custom error if an "ECONNRESET" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - const errorMessage = 'ECONNRESET: Some message'; - - // The first time a block-cacheable request is made, the - // block-cache middleware will request the latest block number - // through the block tracker to determine the cache key. Later, - // the block-ref middleware will request the latest block number - // again to resolve the value of "latest", but the block number is - // cached once made, so we only need to mock the request once. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // The block-ref middleware will make the request as specified - // except that the block param is replaced with the latest block - // number. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - error: errorMessage, - times: 5, - }); - - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - await expect(promiseForResult).rejects.toThrow( - buildInfuraClientRetriesExhaustedErrorMessage(errorMessage), - ); - }); - }); - } else { - it('does not retry the request to the RPC endpoint, but throws immediately, if an "ECONNRESET" error is thrown while making the request', async () => { - const customRpcUrl = 'http://example.com'; - - await withMockedCommunications( - { providerType, customRpcUrl }, - async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - const errorMessage = 'ECONNRESET: Some message'; - - // The first time a block-cacheable request is made, the - // block-cache middleware will request the latest block number - // through the block tracker to determine the cache key. Later, - // the block-ref middleware will request the latest block number - // again to resolve the value of "latest", but the block number is - // cached once made, so we only need to mock the request once. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // The block-ref middleware will make the request as specified - // except that the block param is replaced with the latest block - // number. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - error: errorMessage, - }); - - const promiseForResult = withNetworkClient( - { providerType, customRpcUrl }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - buildFetchFailedErrorMessage(customRpcUrl, errorMessage), - ); - }, - ); - }); - } - - // Both the Infura and fetch middleware will attempt to parse the response - // body as JSON, and if this step produces an error, both middleware will - // also attempt to retry the request. However, this error handling code is - // slightly different between the two. As the error in this case is a - // SyntaxError, the Infura middleware will catch it immediately, whereas the - // custom RPC middleware will catch it and re-throw a separate error, which - // it then catches later. - if (providerType === 'infura') { - it('retries the request to the RPC endpoint up to 5 times if a "SyntaxError" error is thrown while making the request, returning the successful result if there is one on the 5th try', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - - // The first time a block-cacheable request is made, the - // block-cache middleware will request the latest block number - // through the block tracker to determine the cache key. Later, - // the block-ref middleware will request the latest block number - // again to resolve the value of "latest", but the block number is - // cached once made, so we only need to mock the request once. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // The block-ref middleware will make the request as specified - // except that the block param is replaced with the latest block - // number. - // - // Here we have the request fail for the first 4 tries, then - // succeed on the 5th try. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - error: 'SyntaxError: Some message', - times: 4, - }); - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { - result: 'the result', - httpStatus: 200, - }, - }); - const result = await withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - expect(result).toStrictEqual('the result'); - }); - }); - - it('causes a request to fail with a custom error if a "SyntaxError" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - const errorMessage = 'SyntaxError: Some message'; - - // The first time a block-cacheable request is made, the - // block-cache middleware will request the latest block number - // through the block tracker to determine the cache key. Later, - // the block-ref middleware will request the latest block number - // again to resolve the value of "latest", but the block number is - // cached once made, so we only need to mock the request once. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // The block-ref middleware will make the request as specified - // except that the block param is replaced with the latest block - // number. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - error: errorMessage, - times: 5, - }); - - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - await expect(promiseForResult).rejects.toThrow( - buildInfuraClientRetriesExhaustedErrorMessage(errorMessage), - ); - }); - }); - - it('does not retry the request to the RPC endpoint, but throws immediately, if a "failed to parse response body" error is thrown while making the request', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - const errorMessage = 'failed to parse response body: Some message'; - - // The first time a block-cacheable request is made, the - // block-cache middleware will request the latest block number - // through the block tracker to determine the cache key. Later, - // the block-ref middleware will request the latest block number - // again to resolve the value of "latest", but the block number is - // cached once made, so we only need to mock the request once. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // The block-ref middleware will make the request as specified - // except that the block param is replaced with the latest block - // number. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - error: errorMessage, - }); - - const promiseForResult = withNetworkClient( - { providerType, infuraNetwork: comms.infuraNetwork }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - buildFetchFailedErrorMessage(comms.rpcUrl, errorMessage), - ); - }); - }); - } else { - it('does not retry the request to the RPC endpoint, but throws immediately, if a "SyntaxError" error is thrown while making the request', async () => { - const customRpcUrl = 'http://example.com'; - - await withMockedCommunications( - { providerType, customRpcUrl }, - async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - const errorMessage = 'SyntaxError: Some message'; - - // The first time a block-cacheable request is made, the - // block-cache middleware will request the latest block number - // through the block tracker to determine the cache key. Later, - // the block-ref middleware will request the latest block number - // again to resolve the value of "latest", but the block number is - // cached once made, so we only need to mock the request once. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // The block-ref middleware will make the request as specified - // except that the block param is replaced with the latest block - // number. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - error: errorMessage, - }); - - const promiseForResult = withNetworkClient( - { providerType, customRpcUrl }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - buildFetchFailedErrorMessage(customRpcUrl, errorMessage), - ); - }, - ); - }); - - it('retries the request to the RPC endpoint up to 5 times if a "failed to parse response body" error is thrown while making the request, returning the successful result if there is one on the 5th try', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - - // The first time a block-cacheable request is made, the - // block-cache middleware will request the latest block number - // through the block tracker to determine the cache key. Later, - // the block-ref middleware will request the latest block number - // again to resolve the value of "latest", but the block number is - // cached once made, so we only need to mock the request once. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // The block-ref middleware will make the request as specified - // except that the block param is replaced with the latest block - // number. - // - // Here we have the request fail for the first 4 tries, then - // succeed on the 5th try. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - error: 'failed to parse response body: Some message', - times: 4, - }); - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { - result: 'the result', - httpStatus: 200, - }, - }); - - const result = await withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - expect(result).toStrictEqual('the result'); - }); - }); - - it('produces an empty response if a "failed to parse response body" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - const errorMessage = 'failed to parse response body: some message'; - - // The first time a block-cacheable request is made, the - // block-cache middleware will request the latest block number - // through the block tracker to determine the cache key. Later, - // the block-ref middleware will request the latest block number - // again to resolve the value of "latest", but the block number is - // cached once made, so we only need to mock the request once. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // The block-ref middleware will make the request as specified - // except that the block param is replaced with the latest block - // number. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - error: errorMessage, - times: 5, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - await expect(promiseForResult).rejects.toThrow( - buildJsonRpcEngineEmptyResponseErrorMessage(method), - ); - }); - }); - } - - // Only the custom RPC middleware will detect a "Failed to fetch" error and - // attempt to retry the request to the RPC endpoint; the Infura middleware - // does not. - if (providerType === 'infura') { - it('does not retry the request to the RPC endpoint, but throws immediately, if a "Failed to fetch" error is thrown while making the request', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - const errorMessage = 'Failed to fetch: Some message'; - - // The first time a block-cacheable request is made, the - // block-cache middleware will request the latest block number - // through the block tracker to determine the cache key. Later, - // the block-ref middleware will request the latest block number - // again to resolve the value of "latest", but the block number is - // cached once made, so we only need to mock the request once. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // The block-ref middleware will make the request as specified - // except that the block param is replaced with the latest block - // number. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - error: errorMessage, - }); - - const promiseForResult = withNetworkClient( - { providerType, infuraNetwork: comms.infuraNetwork }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - buildFetchFailedErrorMessage(comms.rpcUrl, errorMessage), - ); - }); - }); - } else { - it('retries the request to the RPC endpoint up to 5 times if a "Failed to fetch" error is thrown while making the request, returning the successful result if there is one on the 5th try', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - - // The first time a block-cacheable request is made, the - // block-cache middleware will request the latest block number - // through the block tracker to determine the cache key. Later, - // the block-ref middleware will request the latest block number - // again to resolve the value of "latest", but the block number is - // cached once made, so we only need to mock the request once. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // The block-ref middleware will make the request as specified - // except that the block param is replaced with the latest block - // number. - // - // Here we have the request fail for the first 4 tries, then - // succeed on the 5th try. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - error: 'Failed to fetch: Some message', - times: 4, - }); - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { - result: 'the result', - httpStatus: 200, - }, - }); - - const result = await withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - expect(result).toStrictEqual('the result'); - }); - }); - - it('produces an empty response if a "Failed to fetch" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - const errorMessage = 'Failed to fetch: some message'; - - // The first time a block-cacheable request is made, the - // block-cache middleware will request the latest block number - // through the block tracker to determine the cache key. Later, - // the block-ref middleware will request the latest block number - // again to resolve the value of "latest", but the block number is - // cached once made, so we only need to mock the request once. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // The block-ref middleware will make the request as specified - // except that the block param is replaced with the latest block - // number. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - error: errorMessage, - times: 5, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - await expect(promiseForResult).rejects.toThrow( - buildJsonRpcEngineEmptyResponseErrorMessage(method), - ); - }); - }); - } - }); - - describe.each([ - ['given a block tag of "earliest"', 'earliest', 'earliest'], - ['given a block number', 'block number', '0x100'], - ])('%s', (_desc, blockParamType, blockParam) => { - it('does not hit the RPC endpoint more than once for identical requests', async () => { - const requests = [ - { - method, - params: buildMockParams({ blockParamIndex, blockParam }), - }, - { - method, - params: buildMockParams({ blockParamIndex, blockParam }), - }, - ]; - const mockResults = ['first result', 'second result']; - - await withMockedCommunications({ providerType }, async (comms) => { - // The first time a block-cacheable request is made, the block-cache - // middleware will request the latest block number through the block - // tracker to determine the cache key. This block number doesn't - // matter. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual([mockResults[0], mockResults[0]]); - }); - }); - - for (const paramIndex of [...Array(numberOfParameters).keys()]) { - if (paramIndex === blockParamIndex) { - // testing changes in block param is covered under later tests - continue; - } - it(`does not reuse the result of a previous request if parameter at index "${paramIndex}" differs`, async () => { - const firstMockParams = [ - ...new Array(numberOfParameters).fill('some value'), - ]; - firstMockParams[blockParamIndex] = blockParam; - const secondMockParams = firstMockParams.slice(); - secondMockParams[paramIndex] = 'another value'; - const requests = [ - { - method, - params: firstMockParams, - }, - { method, params: secondMockParams }, - ]; - const mockResults = ['first result', 'second result']; - - await withMockedCommunications({ providerType }, async (comms) => { - // The first time a block-cacheable request is made, the block-cache - // middleware will request the latest block number through the block - // tracker to determine the cache key. Later, the block-ref - // middleware will request the latest block number again to resolve - // the value of "latest", but the block number is cached once made, - // so we only need to mock the request once. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // The block-ref middleware will make the request as specified - // except that the block param is replaced with the latest block - // number. - comms.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - }); - comms.mockRpcCall({ - request: requests[1], - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual([mockResults[0], mockResults[1]]); - }); - }); - } - - it('reuses the result of a previous request even if the latest block number was updated since', async () => { - const requests = [ - { - method, - params: buildMockParams({ blockParamIndex, blockParam }), - }, - { - method, - params: buildMockParams({ blockParamIndex, blockParam }), - }, - ]; - const mockResults = ['first result', 'second result']; - - await withMockedCommunications({ providerType }, async (comms) => { - // Note that we have to mock these requests in a specific order. The - // first block tracker request occurs because of the first RPC - // request. The second block tracker request, however, does not - // occur because of the second RPC request, but rather because we - // call `clock.runAll()` below. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x1' }); - comms.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - }); - comms.mockNextBlockTrackerRequest({ blockNumber: '0x2' }); - comms.mockRpcCall({ - request: requests[1], - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - async (client) => { - const firstResult = await client.makeRpcCall(requests[0]); - // Proceed to the next iteration of the block tracker so that a - // new block is fetched and the current block is updated. - client.clock.runAll(); - const secondResult = await client.makeRpcCall(requests[1]); - return [firstResult, secondResult]; - }, - ); - - expect(results).toStrictEqual([mockResults[0], mockResults[0]]); - }); - }); - - if (blockParamType === 'earliest') { - it('treats "0x00" as a synonym for "earliest"', async () => { - const requests = [ - { - method, - params: buildMockParams({ blockParamIndex, blockParam }), - }, - { - method, - params: buildMockParams({ blockParamIndex, blockParam: '0x00' }), - }, - ]; - const mockResults = ['first result', 'second result']; - - await withMockedCommunications({ providerType }, async (comms) => { - // The first time a block-cacheable request is made, the latest - // block number is retrieved through the block tracker first. It - // doesn't matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual([mockResults[0], mockResults[0]]); - }); - }); - - for (const emptyValue of [null, undefined, '\u003cnil\u003e']) { - it(`does not retry an empty response of "${emptyValue}"`, async () => { - const request = { - method, - params: buildMockParams({ blockParamIndex, blockParam }), - }; - const mockResult = emptyValue; - - await withMockedCommunications({ providerType }, async (comms) => { - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request, - response: { result: mockResult }, - }); - - const result = await withNetworkClient( - { providerType }, - ({ makeRpcCall }) => makeRpcCall(request), - ); - - expect(result).toStrictEqual(mockResult); - }); - }); - - it(`does not reuse the result of a previous request if it was "${emptyValue}"`, async () => { - const requests = [ - { - method, - params: buildMockParams({ blockParamIndex, blockParam }), - }, - { - method, - params: buildMockParams({ blockParamIndex, blockParam }), - }, - ]; - const mockResults = [emptyValue, 'some result']; - - await withMockedCommunications({ providerType }, async (comms) => { - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - }); - comms.mockRpcCall({ - request: requests[1], - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual(mockResults); - }); - }); - } - } - - if (blockParamType === 'block number') { - it('does not reuse the result of a previous request if it was made with different arguments than this one', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const requests = [ - { - method, - params: buildMockParams({ blockParamIndex, blockParam: '0x100' }), - }, - { - method, - params: buildMockParams({ blockParamIndex, blockParam: '0x200' }), - }, - ]; - - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request: requests[0], - response: { result: 'first result' }, - }); - comms.mockRpcCall({ - request: requests[1], - response: { result: 'second result' }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual(['first result', 'second result']); - }); - }); - - describe.each( - [ - ['less than the current block number', '0x200'], - ['equal to the curent block number', '0x100'], - ] as any, - '%s', - (_nestedDesc: string, currentBlockNumber: string) => { - it('makes an additional request to the RPC endpoint', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - // Note that `blockParam` is `0x100` here - params: buildMockParams({ blockParamIndex, blockParam }), - }; - - // The first time a block-cacheable request is made, the latest - // block number is retrieved through the block tracker first. - comms.mockNextBlockTrackerRequest({ - blockNumber: currentBlockNumber, - }); - comms.mockRpcCall({ - request, - response: { result: 'the result' }, - }); - - const result = await withNetworkClient( - { providerType }, - ({ makeRpcCall }) => makeRpcCall(request), - ); - - expect(result).toStrictEqual('the result'); - }); - }); - - for (const emptyValue of [null, undefined, '\u003cnil\u003e']) { - if (providerType === 'infura') { - it(`retries up to 10 times if a "${emptyValue}" response is returned, returning successful non-empty response if there is one on the 10th try`, async () => { - const request = { - method, - // Note that `blockParam` is `0x100` here - params: buildMockParams({ blockParamIndex, blockParam }), - }; - - await withMockedCommunications( - { providerType }, - async (comms) => { - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. - comms.mockNextBlockTrackerRequest({ - blockNumber: currentBlockNumber, - }); - comms.mockRpcCall({ - request, - response: { result: emptyValue }, - times: 9, - }); - comms.mockRpcCall({ - request, - response: { result: 'some value' }, - }); - - const result = await withNetworkClient( - { providerType }, - ({ makeRpcCall, clock }) => - waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ), - ); - - expect(result).toStrictEqual('some value'); - }, - ); - }); - - it(`retries up to 10 times if a "${emptyValue}" response is returned, failing after the 10th try`, async () => { - const request = { - method, - // Note that `blockParam` is `0x100` here - params: buildMockParams({ blockParamIndex, blockParam }), - }; - const mockResult = emptyValue; - - await withMockedCommunications( - { providerType }, - async (comms) => { - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. - comms.mockNextBlockTrackerRequest({ - blockNumber: currentBlockNumber, - }); - comms.mockRpcCall({ - request, - response: { result: mockResult }, - times: 10, - }); - - const promiseForResult = withNetworkClient( - { providerType }, - ({ makeRpcCall, clock }) => - waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ), - ); - - await expect(promiseForResult).rejects.toThrow( - 'RetryOnEmptyMiddleware - retries exhausted', - ); - }, - ); - }); - } else { - it(`does not retry an empty response of "${emptyValue}"`, async () => { - const request = { - method, - // Note that `blockParam` is `0x100` here - params: buildMockParams({ blockParamIndex, blockParam }), - }; - const mockResult = emptyValue; - - await withMockedCommunications( - { providerType }, - async (comms) => { - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. - comms.mockNextBlockTrackerRequest({ - blockNumber: currentBlockNumber, - }); - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { result: mockResult }, - }); - - const result = await withNetworkClient( - { providerType }, - ({ makeRpcCall }) => makeRpcCall(request), - ); - - expect(result).toStrictEqual(mockResult); - }, - ); - }); - - it(`does not reuse the result of a previous request if it was "${emptyValue}"`, async () => { - const requests = [ - { - method, - // Note that `blockParam` is `0x100` here - params: buildMockParams({ blockParamIndex, blockParam }), - }, - { - method, - // Note that `blockParam` is `0x100` here - params: buildMockParams({ blockParamIndex, blockParam }), - }, - ]; - const mockResults = [emptyValue, { blockHash: '0x100' }]; - - await withMockedCommunications( - { providerType }, - async (comms) => { - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. - comms.mockNextBlockTrackerRequest({ - blockNumber: currentBlockNumber, - }); - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - requests[0], - blockParamIndex, - '0x100', - ), - response: { result: mockResults[0] }, - }); - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - requests[1], - blockParamIndex, - '0x100', - ), - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => - makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual(mockResults); - }, - ); - }); - } - } - }, - ); - - describe('greater than the current block number', () => { - it('makes an additional request to the RPC endpoint', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - // Note that `blockParam` is `0x100` here - params: buildMockParams({ blockParamIndex, blockParam }), - }; - - // The first time a block-cacheable request is made, the latest - // block number is retrieved through the block tracker first. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x42' }); - comms.mockRpcCall({ - request, - response: { result: 'the result' }, - }); - - const result = await withNetworkClient( - { providerType }, - ({ makeRpcCall }) => makeRpcCall(request), - ); - - expect(result).toStrictEqual('the result'); - }); - }); - - for (const emptyValue of [null, undefined, '\u003cnil\u003e']) { - it(`does not retry an empty response of "${emptyValue}"`, async () => { - const request = { - method, - // Note that `blockParam` is `0x100` here - params: buildMockParams({ blockParamIndex, blockParam }), - }; - const mockResult = emptyValue; - - await withMockedCommunications({ providerType }, async (comms) => { - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x42' }); - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { result: mockResult }, - }); - - const result = await withNetworkClient( - { providerType }, - ({ makeRpcCall }) => makeRpcCall(request), - ); - - expect(result).toStrictEqual(mockResult); - }); - }); - - it(`does not reuse the result of a previous request if it was "${emptyValue}"`, async () => { - const requests = [ - { - method, - // Note that `blockParam` is `0x100` here - params: buildMockParams({ blockParamIndex, blockParam }), - }, - { - method, - // Note that `blockParam` is `0x100` here - params: buildMockParams({ blockParamIndex, blockParam }), - }, - ]; - const mockResults = [emptyValue, { blockHash: '0x100' }]; - - await withMockedCommunications({ providerType }, async (comms) => { - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x42' }); - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - requests[0], - blockParamIndex, - '0x100', - ), - response: { result: mockResults[0] }, - }); - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - requests[1], - blockParamIndex, - '0x100', - ), - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual(mockResults); - }); - }); - } - }); - } - }); - - describe('given a block tag of "pending"', () => { - const params = buildMockParams({ blockParamIndex, blockParam: 'pending' }); - - it('hits the RPC endpoint on all calls and does not cache anything', async () => { - const requests = [ - { method, params }, - { method, params }, - ]; - const mockResults = ['first result', 'second result']; - - await withMockedCommunications({ providerType }, async (comms) => { - // The first time a block-cacheable request is made, the latest - // block number is retrieved through the block tracker first. It - // doesn't matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - }); - comms.mockRpcCall({ - request: requests[1], - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual(mockResults); - }); - }); - }); -} diff --git a/app/scripts/controllers/network/provider-api-tests/helpers.ts b/app/scripts/controllers/network/provider-api-tests/helpers.ts deleted file mode 100644 index 104715d13..000000000 --- a/app/scripts/controllers/network/provider-api-tests/helpers.ts +++ /dev/null @@ -1,543 +0,0 @@ -import nock, { Scope as NockScope } from 'nock'; -import sinon from 'sinon'; -import type { JSONRPCResponse } from '@json-rpc-specification/meta-schema'; -import EthQuery from 'eth-query'; -import { Hex } from '@metamask/utils'; -import { BuiltInInfuraNetwork } from '../../../../../shared/constants/network'; -import { - createNetworkClient, - NetworkClientType, -} from '../create-network-client'; - -/** - * A dummy value for the `infuraProjectId` option that `createInfuraClient` - * needs. (Infura should not be hit during tests, but just in case, this should - * not refer to a real project ID.) - */ -const MOCK_INFURA_PROJECT_ID = 'abc123'; - -/** - * A dummy value for the `rpcUrl` option that `createJsonRpcClient` needs. (This - * should not be hit during tests, but just in case, this should also not refer - * to a real Infura URL.) - */ -const MOCK_RPC_URL = 'http://foo.com'; - -/** - * A default value for the `eth_blockNumber` request that the block tracker - * makes. - */ -const DEFAULT_LATEST_BLOCK_NUMBER = '0x42'; - -/** - * A reference to the original `setTimeout` function so that we can use it even - * when using fake timers. - */ -const originalSetTimeout = setTimeout; - -/** - * If you're having trouble writing a test and you're wondering why the test - * keeps failing, you can set `process.env.DEBUG_PROVIDER_TESTS` to `1`. This - * will turn on some extra logging. - * - * @param args - The arguments that `console.log` takes. - */ -function debug(...args: any) { - if (process.env.DEBUG_PROVIDER_TESTS === '1') { - console.log(...args); - } -} - -/** - * Builds a Nock scope object for mocking provider requests. - * - * @param rpcUrl - The URL of the RPC endpoint. - * @returns The nock scope. - */ -function buildScopeForMockingRequests(rpcUrl: string): NockScope { - return nock(rpcUrl).filteringRequestBody((body) => { - debug('Nock Received Request: ', body); - return body; - }); -} - -type Request = { method: string; params?: any[] }; -type Response = { - id?: number | string; - jsonrpc?: '2.0'; - error?: any; - result?: any; - httpStatus?: number; -}; -type ResponseBody = { body: JSONRPCResponse }; -type BodyOrResponse = ResponseBody | Response; -type CurriedMockRpcCallOptions = { - request: Request; - // The response data. - response?: BodyOrResponse; - /** - * An error to throw while making the request. - * Takes precedence over `response`. - */ - error?: Error | string; - /** - * The amount of time that should pass before the - * request resolves with the response. - */ - delay?: number; - /** - * The number of times that the request is - * expected to be made. - */ - times?: number; -}; - -type MockRpcCallOptions = { - // A nock scope (a set of mocked requests scoped to a certain base URL). - nockScope: nock.Scope; -} & CurriedMockRpcCallOptions; - -type MockRpcCallResult = nock.Interceptor | nock.Scope; - -/** - * Mocks a JSON-RPC request sent to the provider with the given response. - * Provider type is inferred from the base url set on the nockScope. - * - * @param args - The arguments. - * @param args.nockScope - A nock scope (a set of mocked requests scoped to a - * certain base URL). - * @param args.request - The request data. - * @param args.response - Information concerning the response that the request - * should have. If a `body` property is present, this is taken as the complete - * response body. If an `httpStatus` property is present, then it is taken as - * the HTTP status code to respond with. Properties other than these two are - * used to build a complete response body (including `id` and `jsonrpc` - * properties). - * @param args.error - An error to throw while making the request. Takes - * precedence over `response`. - * @param args.delay - The amount of time that should pass before the request - * resolves with the response. - * @param args.times - The number of times that the request is expected to be - * made. - * @returns The nock scope. - */ -function mockRpcCall({ - nockScope, - request, - response, - error, - delay, - times, -}: MockRpcCallOptions): MockRpcCallResult { - // eth-query always passes `params`, so even if we don't supply this property, - // for consistency with makeRpcCall, assume that the `body` contains it - const { method, params = [], ...rest } = request; - let httpStatus = 200; - let completeResponse: JSONRPCResponse = { id: 2, jsonrpc: '2.0' }; - if (response !== undefined) { - if ('body' in response) { - completeResponse = response.body; - } else { - if (response.error) { - completeResponse.error = response.error; - } else { - completeResponse.result = response.result; - } - if (response.httpStatus) { - httpStatus = response.httpStatus; - } - } - } - /* @ts-expect-error The types for Nock do not include `basePath` in the interface for Nock.Scope. */ - const url = new URL(nockScope.basePath).hostname.match(/(\.|^)infura.io$/u) - ? `/v3/${MOCK_INFURA_PROJECT_ID}` - : '/'; - - debug('Mocking request:', { - url, - method, - params, - response, - error, - ...rest, - times, - }); - - let nockRequest = nockScope.post(url, { - id: /\d*/u, - jsonrpc: '2.0', - method, - params, - ...rest, - }); - - if (delay !== undefined) { - nockRequest = nockRequest.delay(delay); - } - - if (times !== undefined) { - nockRequest = nockRequest.times(times); - } - - if (error !== undefined) { - return nockRequest.replyWithError(error); - } else if (completeResponse !== undefined) { - return nockRequest.reply(httpStatus, (_, requestBody: any) => { - if (response !== undefined && !('body' in response)) { - if (response.id === undefined) { - completeResponse.id = requestBody.id; - } else { - completeResponse.id = response.id; - } - } - debug('Nock returning Response', completeResponse); - return completeResponse; - }); - } - return nockRequest; -} - -type MockBlockTrackerRequestOptions = { - /** - * A nock scope (a set of mocked requests scoped to a certain base url). - */ - nockScope: NockScope; - /** - * The block number that the block tracker should report, as a 0x-prefixed hex - * string. - */ - blockNumber: string; -}; - -/** - * Mocks the next request for the latest block that the block tracker will make. - * - * @param args - The arguments. - * @param args.nockScope - A nock scope (a set of mocked requests scoped to a - * certain base URL). - * @param args.blockNumber - The block number that the block tracker should - * report, as a 0x-prefixed hex string. - */ -function mockNextBlockTrackerRequest({ - nockScope, - blockNumber = DEFAULT_LATEST_BLOCK_NUMBER, -}: MockBlockTrackerRequestOptions) { - mockRpcCall({ - nockScope, - request: { method: 'eth_blockNumber', params: [] }, - response: { result: blockNumber }, - }); -} - -/** - * Mocks all requests for the latest block that the block tracker will make. - * - * @param args - The arguments. - * @param args.nockScope - A nock scope (a set of mocked requests scoped to a - * certain base URL). - * @param args.blockNumber - The block number that the block tracker should - * report, as a 0x-prefixed hex string. - */ -async function mockAllBlockTrackerRequests({ - nockScope, - blockNumber = DEFAULT_LATEST_BLOCK_NUMBER, -}: MockBlockTrackerRequestOptions) { - const result = await mockRpcCall({ - nockScope, - request: { method: 'eth_blockNumber', params: [] }, - response: { result: blockNumber }, - }); - - if ('persist' in result) { - result.persist(); - } -} - -/** - * Makes a JSON-RPC call through the given eth-query object. - * - * @param ethQuery - The eth-query object. - * @param request - The request data. - * @returns A promise that either resolves with the result from the JSON-RPC - * response if it is successful or rejects with the error from the JSON-RPC - * response otherwise. - */ -function makeRpcCall(ethQuery: EthQuery, request: Request) { - return new Promise((resolve, reject) => { - debug('[makeRpcCall] making request', request); - ethQuery.sendAsync(request, (error, result) => { - debug('[makeRpcCall > ethQuery handler] error', error, 'result', result); - if (error) { - reject(error); - } else { - resolve(result); - } - }); - }); -} - -export type ProviderType = 'infura' | 'custom'; - -export type MockOptions = { - infuraNetwork?: BuiltInInfuraNetwork; - providerType: ProviderType; - customRpcUrl?: string; - customChainId?: Hex; -}; - -export type MockCommunications = { - mockNextBlockTrackerRequest: (options?: any) => void; - mockAllBlockTrackerRequests: (options?: any) => void; - mockRpcCall: (options: CurriedMockRpcCallOptions) => MockRpcCallResult; - rpcUrl: string; - infuraNetwork: BuiltInInfuraNetwork; -}; - -/** - * Sets up request mocks for requests to the provider. - * - * @param options - An options bag. - * @param options.providerType - The type of network client being tested. - * @param options.infuraNetwork - The name of the Infura network being tested, - * assuming that `providerType` is "infura" (default: "mainnet"). - * @param options.customRpcUrl - The URL of the custom RPC endpoint, assuming - * that `providerType` is "custom". - * @param fn - A function which will be called with an object that allows - * interaction with the network client. - * @returns The return value of the given function. - */ -export async function withMockedCommunications( - { - providerType, - infuraNetwork = 'mainnet', - customRpcUrl = MOCK_RPC_URL, - }: MockOptions, - fn: (comms: MockCommunications) => Promise, -) { - if (providerType !== 'infura' && providerType !== 'custom') { - throw new Error( - `providerType must be either "infura" or "custom", was "${providerType}" instead`, - ); - } - - const rpcUrl = - providerType === 'infura' - ? `https://${infuraNetwork}.infura.io` - : customRpcUrl; - const nockScope = buildScopeForMockingRequests(rpcUrl); - const curriedMockNextBlockTrackerRequest = (localOptions: any) => - mockNextBlockTrackerRequest({ nockScope, ...localOptions }); - const curriedMockAllBlockTrackerRequests = (localOptions: any) => - mockAllBlockTrackerRequests({ nockScope, ...localOptions }); - const curriedMockRpcCall = (localOptions: any) => - mockRpcCall({ nockScope, ...localOptions }); - - const comms = { - mockNextBlockTrackerRequest: curriedMockNextBlockTrackerRequest, - mockAllBlockTrackerRequests: curriedMockAllBlockTrackerRequests, - mockRpcCall: curriedMockRpcCall, - rpcUrl, - infuraNetwork, - }; - - try { - return await fn(comms); - } finally { - nock.isDone(); - nock.cleanAll(); - } -} - -type MockNetworkClient = { - blockTracker: any; - clock: sinon.SinonFakeTimers; - makeRpcCall: (request: Request) => Promise; - makeRpcCallsInSeries: (requests: Request[]) => Promise; -}; - -/** - * Some middleware contain logic which retries the request if some condition - * applies. This retrying always happens out of band via `setTimeout`, and - * because we are stubbing time via Jest's fake timers, we have to manually - * advance the clock so that the `setTimeout` handlers get fired. We don't know - * when these timers will get created, however, so we have to keep advancing - * timers until the request has been made an appropriate number of times. - * Unfortunately we don't have a good way to know how many times a request has - * been retried, but the good news is that the middleware won't end, and thus - * the promise which the RPC call returns won't get fulfilled, until all retries - * have been made. - * - * @param promise - The promise which is returned by the RPC call. - * @param clock - A Sinon clock object which can be used to advance to the next - * `setTimeout` handler. - */ -export async function waitForPromiseToBeFulfilledAfterRunningAllTimers( - promise: any, - clock: any, -) { - let hasPromiseBeenFulfilled = false; - let numTimesClockHasBeenAdvanced = 0; - - promise - .catch((error: any) => { - // This is used to silence Node.js warnings about the rejection - // being handled asynchronously. The error is handled later when - // `promise` is awaited, but we log it here anyway in case it gets - // swallowed. - debug(error); - }) - .finally(() => { - hasPromiseBeenFulfilled = true; - }); - - // `hasPromiseBeenFulfilled` is modified asynchronously. - /* eslint-disable-next-line no-unmodified-loop-condition */ - while (!hasPromiseBeenFulfilled && numTimesClockHasBeenAdvanced < 15) { - clock.runAll(); - await new Promise((resolve) => originalSetTimeout(resolve, 10)); - numTimesClockHasBeenAdvanced += 1; - } - - return promise; -} - -/** - * Builds a provider from the middleware (for the provider type) along with a - * block tracker, runs the given function with those two things, and then - * ensures the block tracker is stopped at the end. - * - * @param options - An options bag. - * @param options.providerType - The type of network client being tested. - * @param options.infuraNetwork - The name of the Infura network being tested, - * assuming that `providerType` is "infura" (default: "mainnet"). - * @param options.customRpcUrl - The URL of the custom RPC endpoint, assuming - * that `providerType` is "custom". - * @param options.customChainId - The chain id belonging to the custom RPC - * endpoint, assuming that `providerType` is "custom" (default: "0x1"). - * @param fn - A function which will be called with an object that allows - * interaction with the network client. - * @returns The return value of the given function. - */ -export async function withNetworkClient( - { - providerType, - infuraNetwork = 'mainnet', - customRpcUrl = MOCK_RPC_URL, - customChainId = '0x1', - }: MockOptions, - fn: (client: MockNetworkClient) => Promise, -) { - if (providerType !== 'infura' && providerType !== 'custom') { - throw new Error( - `providerType must be either "infura" or "custom", was "${providerType}" instead`, - ); - } - - // Faking timers ends up doing two things: - // 1. Halting the block tracker (which depends on `setTimeout` to periodically - // request the latest block) set up in `eth-json-rpc-middleware` - // 2. Halting the retry logic in `@metamask/eth-json-rpc-infura` (which also - // depends on `setTimeout`) - const clock = sinon.useFakeTimers(); - - // The JSON-RPC client wraps `eth_estimateGas` so that it takes 2 seconds longer - // than it usually would to complete. Or at least it should — this doesn't - // appear to be working correctly. Unset `IN_TEST` on `process.env` to prevent - // this behavior. - const inTest = process.env.IN_TEST; - delete process.env.IN_TEST; - const clientUnderTest = - providerType === 'infura' - ? createNetworkClient({ - network: infuraNetwork, - infuraProjectId: MOCK_INFURA_PROJECT_ID, - type: NetworkClientType.Infura, - }) - : createNetworkClient({ - chainId: customChainId, - rpcUrl: customRpcUrl, - type: NetworkClientType.Custom, - }); - process.env.IN_TEST = inTest; - - const { provider, blockTracker } = clientUnderTest; - - const ethQuery = new EthQuery(provider); - const curriedMakeRpcCall = (request: Request) => - makeRpcCall(ethQuery, request); - const makeRpcCallsInSeries = async (requests: Request[]) => { - const responses = []; - for (const request of requests) { - responses.push(await curriedMakeRpcCall(request)); - } - return responses; - }; - - const client = { - blockTracker, - clock, - makeRpcCall: curriedMakeRpcCall, - makeRpcCallsInSeries, - }; - - try { - return await fn(client); - } finally { - await blockTracker.destroy(); - - clock.restore(); - } -} - -type BuildMockParamsOptions = { - // The block parameter value to set. - blockParam: any; - // The index of the block parameter. - blockParamIndex: number; -}; - -/** - * Build mock parameters for a JSON-RPC call. - * - * The string 'some value' is used as the default value for each entry. The - * block parameter index determines the number of parameters to generate. - * - * The block parameter can be set to a custom value. If no value is given, it - * is set as undefined. - * - * @param args - Arguments. - * @param args.blockParamIndex - The index of the block parameter. - * @param args.blockParam - The block parameter value to set. - * @returns The mock params. - */ -export function buildMockParams({ - blockParam, - blockParamIndex, -}: BuildMockParamsOptions) { - const params = new Array(blockParamIndex).fill('some value'); - params[blockParamIndex] = blockParam; - - return params; -} - -/** - * Returns a partial JSON-RPC request object, with the "block" param replaced - * with the given value. - * - * @param request - The request object. - * @param request.method - The request method. - * @param request.params - The request params. - * @param blockParamIndex - The index within the `params` array of the block - * param. - * @param blockParam - The desired block param value. - * @returns The updated request object. - */ -export function buildRequestWithReplacedBlockParam( - { method, params = [] }: Request, - blockParamIndex: number, - blockParam: any, -) { - const updatedParams = params.slice(); - updatedParams[blockParamIndex] = blockParam; - return { method, params: updatedParams }; -} diff --git a/app/scripts/controllers/network/provider-api-tests/no-block-param.ts b/app/scripts/controllers/network/provider-api-tests/no-block-param.ts deleted file mode 100644 index 662c7fac9..000000000 --- a/app/scripts/controllers/network/provider-api-tests/no-block-param.ts +++ /dev/null @@ -1,977 +0,0 @@ -/* eslint-disable jest/require-top-level-describe, jest/no-export */ - -import { - ProviderType, - waitForPromiseToBeFulfilledAfterRunningAllTimers, - withMockedCommunications, - withNetworkClient, -} from './helpers'; -import { - buildFetchFailedErrorMessage, - buildInfuraClientRetriesExhaustedErrorMessage, - buildJsonRpcEngineEmptyResponseErrorMessage, -} from './shared-tests'; - -type TestsForRpcMethodAssumingNoBlockParamOptions = { - providerType: ProviderType; - numberOfParameters: number; -}; - -/** - * Defines tests which exercise the behavior exhibited by an RPC method which is - * assumed to not take a block parameter. Even if it does, the value of this - * parameter will not be used in determining how to cache the method. - * - * @param method - The name of the RPC method under test. - * @param additionalArgs - Additional arguments. - * @param additionalArgs.numberOfParameters - The number of parameters supported by the method under test. - * @param additionalArgs.providerType - The type of provider being tested; - * either `infura` or `custom` (default: "infura"). - */ -export function testsForRpcMethodAssumingNoBlockParam( - method: string, - { - numberOfParameters, - providerType, - }: TestsForRpcMethodAssumingNoBlockParamOptions, -) { - if (providerType !== 'infura' && providerType !== 'custom') { - throw new Error( - `providerType must be either "infura" or "custom", was "${providerType}" instead`, - ); - } - - it('does not hit the RPC endpoint more than once for identical requests', async () => { - const requests = [{ method }, { method }]; - const mockResults = ['first result', 'second result']; - - await withMockedCommunications({ providerType }, async (comms) => { - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual([mockResults[0], mockResults[0]]); - }); - }); - - for (const paramIndex of [...Array(numberOfParameters).keys()]) { - it(`does not reuse the result of a previous request if parameter at index "${paramIndex}" differs`, async () => { - const firstMockParams = [ - ...new Array(numberOfParameters).fill('some value'), - ]; - const secondMockParams = firstMockParams.slice(); - secondMockParams[paramIndex] = 'another value'; - const requests = [ - { - method, - params: firstMockParams, - }, - { method, params: secondMockParams }, - ]; - const mockResults = ['some result', 'another result']; - - await withMockedCommunications({ providerType }, async (comms) => { - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - }); - comms.mockRpcCall({ - request: requests[1], - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual([mockResults[0], mockResults[1]]); - }); - }); - } - - it('hits the RPC endpoint and does not reuse the result of a previous request if the latest block number was updated since', async () => { - const requests = [{ method }, { method }]; - const mockResults = ['first result', 'second result']; - - await withMockedCommunications({ providerType }, async (comms) => { - // Note that we have to mock these requests in a specific order. The - // first block tracker request occurs because of the first RPC request. - // The second block tracker request, however, does not occur because of - // the second RPC request, but rather because we call `clock.runAll()` - // below. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x1' }); - comms.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - }); - comms.mockNextBlockTrackerRequest({ blockNumber: '0x2' }); - comms.mockRpcCall({ - request: requests[1], - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - async (client) => { - const firstResult = await client.makeRpcCall(requests[0]); - // Proceed to the next iteration of the block tracker so that a new - // block is fetched and the current block is updated. - client.clock.runAll(); - const secondResult = await client.makeRpcCall(requests[1]); - return [firstResult, secondResult]; - }, - ); - - expect(results).toStrictEqual(mockResults); - }); - }); - - for (const emptyValue of [null, undefined, '\u003cnil\u003e']) { - it(`does not retry an empty response of "${emptyValue}"`, async () => { - const request = { method }; - const mockResult = emptyValue; - - await withMockedCommunications({ providerType }, async (comms) => { - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request, - response: { result: mockResult }, - }); - - const result = await withNetworkClient( - { providerType }, - ({ makeRpcCall }) => makeRpcCall(request), - ); - - expect(result).toStrictEqual(mockResult); - }); - }); - - it(`does not reuse the result of a previous request if it was "${emptyValue}"`, async () => { - const requests = [{ method }, { method }]; - const mockResults = [emptyValue, 'some result']; - - await withMockedCommunications({ providerType }, async (comms) => { - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - }); - comms.mockRpcCall({ - request: requests[1], - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual(mockResults); - }); - }); - } - - it('queues requests while a previous identical call is still pending, then runs the queue when it finishes, reusing the result from the first request', async () => { - const requests = [{ method }, { method }, { method }]; - const mockResults = ['first result', 'second result', 'third result']; - - await withMockedCommunications({ providerType }, async (comms) => { - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - delay: 100, - }); - - comms.mockRpcCall({ - request: requests[1], - response: { result: mockResults[1] }, - }); - - comms.mockRpcCall({ - request: requests[2], - response: { result: mockResults[2] }, - }); - - const results = await withNetworkClient( - { providerType }, - async (client) => { - const resultPromises = [ - client.makeRpcCall(requests[0]), - client.makeRpcCall(requests[1]), - client.makeRpcCall(requests[2]), - ]; - const firstResult = await resultPromises[0]; - // The inflight cache middleware uses setTimeout to run the handlers, - // so run them now - client.clock.runAll(); - const remainingResults = await Promise.all(resultPromises.slice(1)); - return [firstResult, ...remainingResults]; - }, - ); - - expect(results).toStrictEqual([ - mockResults[0], - mockResults[0], - mockResults[0], - ]); - }); - }); - - it('throws a custom error if the request to the RPC endpoint returns a 405 response', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request, - response: { - httpStatus: 405, - }, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - 'The method does not exist / is not available', - ); - }); - }); - - // There is a difference in how we are testing the Infura middleware vs. the - // custom RPC middleware (or, more specifically, the fetch middleware) because - // of what both middleware treat as rate limiting errors. In this case, the - // fetch middleware treats a 418 response from the RPC endpoint as such an - // error, whereas to the Infura middleware, it is a 429 response. - if (providerType === 'infura') { - it('throws an undescriptive error if the request to the RPC endpoint returns a 418 response', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { id: 123, method }; - - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request, - response: { - httpStatus: 418, - }, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - '{"id":123,"jsonrpc":"2.0"}', - ); - }); - }); - - it('throws an error with a custom message if the request to the RPC endpoint returns a 429 response', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request, - response: { - httpStatus: 429, - }, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - 'Request is being rate limited', - ); - }); - }); - } else { - it('throws a custom error if the request to the RPC endpoint returns a 418 response', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request, - response: { - httpStatus: 418, - }, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - 'Request is being rate limited.', - ); - }); - }); - - it('throws an undescriptive error if the request to the RPC endpoint returns a 429 response', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request, - response: { - httpStatus: 429, - }, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - "Non-200 status code: '429'", - ); - }); - }); - } - - it('throws a generic, undescriptive error if the request to the RPC endpoint returns a response that is not 405, 418, 429, 503, or 504', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request, - response: { - id: 12345, - jsonrpc: '2.0', - error: 'some error', - httpStatus: 420, - }, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - const errorMessage = - providerType === 'infura' - ? '{"id":12345,"jsonrpc":"2.0","error":"some error"}' - : "Non-200 status code: '420'"; - await expect(promiseForResult).rejects.toThrow(errorMessage); - }); - }); - - [503, 504].forEach((httpStatus) => { - it(`retries the request to the RPC endpoint up to 5 times if it returns a ${httpStatus} response, returning the successful result if there is one on the 5th try`, async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - // Here we have the request fail for the first 4 tries, then succeed - // on the 5th try. - comms.mockRpcCall({ - request, - response: { - error: 'Some error', - httpStatus, - }, - times: 4, - }); - comms.mockRpcCall({ - request, - response: { - result: 'the result', - httpStatus: 200, - }, - }); - const result = await withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - expect(result).toStrictEqual('the result'); - }); - }); - - it(`causes a request to fail with a custom error if the request to the RPC endpoint returns a ${httpStatus} response 5 times in a row`, async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request, - response: { - error: 'Some error', - httpStatus, - }, - times: 5, - }); - comms.mockNextBlockTrackerRequest(); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - const err = - providerType === 'infura' - ? buildInfuraClientRetriesExhaustedErrorMessage('Gateway timeout') - : buildJsonRpcEngineEmptyResponseErrorMessage(method); - await expect(promiseForResult).rejects.toThrow(err); - }); - }); - }); - - it('retries the request to the RPC endpoint up to 5 times if an "ETIMEDOUT" error is thrown while making the request, returning the successful result if there is one on the 5th try', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - // Here we have the request fail for the first 4 tries, then succeed - // on the 5th try. - comms.mockRpcCall({ - request, - error: 'ETIMEDOUT: Some message', - times: 4, - }); - comms.mockRpcCall({ - request, - response: { - result: 'the result', - httpStatus: 200, - }, - }); - - const result = await withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - expect(result).toStrictEqual('the result'); - }); - }); - - // Both the Infura and fetch middleware detect ETIMEDOUT errors and will - // automatically retry the request to the RPC endpoint in question, but both - // produce a different error if the number of retries is exhausted. - if (providerType === 'infura') { - it('causes a request to fail with a custom error if an "ETIMEDOUT" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - const errorMessage = 'ETIMEDOUT: Some message'; - - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request, - error: errorMessage, - times: 5, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - await expect(promiseForResult).rejects.toThrow( - buildInfuraClientRetriesExhaustedErrorMessage(errorMessage), - ); - }); - }); - } else { - it('returns an empty response if an "ETIMEDOUT" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - const errorMessage = 'ETIMEDOUT: Some message'; - - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request, - error: errorMessage, - times: 5, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - await expect(promiseForResult).rejects.toThrow( - buildJsonRpcEngineEmptyResponseErrorMessage(method), - ); - }); - }); - } - - // The Infura middleware treats a response that contains an ECONNRESET message - // as an innocuous error that is likely to disappear on a retry. The custom - // RPC middleware, on the other hand, does not specially handle this error. - if (providerType === 'infura') { - it('retries the request to the RPC endpoint up to 5 times if an "ECONNRESET" error is thrown while making the request, returning the successful result if there is one on the 5th try', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - // Here we have the request fail for the first 4 tries, then succeed - // on the 5th try. - comms.mockRpcCall({ - request, - error: 'ECONNRESET: Some message', - times: 4, - }); - comms.mockRpcCall({ - request, - response: { - result: 'the result', - httpStatus: 200, - }, - }); - - const result = await withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - expect(result).toStrictEqual('the result'); - }); - }); - - it('causes a request to fail with a custom error if an "ECONNRESET" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - const errorMessage = 'ECONNRESET: Some message'; - - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request, - error: errorMessage, - times: 5, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - await expect(promiseForResult).rejects.toThrow( - buildInfuraClientRetriesExhaustedErrorMessage(errorMessage), - ); - }); - }); - } else { - it('does not retry the request to the RPC endpoint, but throws immediately, if an "ECONNRESET" error is thrown while making the request', async () => { - const customRpcUrl = 'http://example.com'; - - await withMockedCommunications( - { providerType, customRpcUrl }, - async (comms) => { - const request = { method }; - const errorMessage = 'ECONNRESET: Some message'; - - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request, - error: errorMessage, - }); - const promiseForResult = withNetworkClient( - { providerType, customRpcUrl }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - buildFetchFailedErrorMessage(customRpcUrl, errorMessage), - ); - }, - ); - }); - } - - // Both the Infura and fetch middleware will attempt to parse the response - // body as JSON, and if this step produces an error, both middleware will also - // attempt to retry the request. However, this error handling code is slightly - // different between the two. As the error in this case is a SyntaxError, the - // Infura middleware will catch it immediately, whereas the custom RPC - // middleware will catch it and re-throw a separate error, which it then - // catches later. - if (providerType === 'infura') { - it('retries the request to the RPC endpoint up to 5 times if an "SyntaxError" error is thrown while making the request, returning the successful result if there is one on the 5th try', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - // Here we have the request fail for the first 4 tries, then succeed - // on the 5th try. - comms.mockRpcCall({ - request, - error: 'SyntaxError: Some message', - times: 4, - }); - comms.mockRpcCall({ - request, - response: { - result: 'the result', - httpStatus: 200, - }, - }); - - const result = await withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - expect(result).toStrictEqual('the result'); - }); - }); - - it('causes a request to fail with a custom error if an "SyntaxError" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - const errorMessage = 'SyntaxError: Some message'; - - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request, - error: errorMessage, - times: 5, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - await expect(promiseForResult).rejects.toThrow( - buildInfuraClientRetriesExhaustedErrorMessage(errorMessage), - ); - }); - }); - - it('does not retry the request to the RPC endpoint, but throws immediately, if a "failed to parse response body" error is thrown while making the request', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - const errorMessage = 'failed to parse response body: some message'; - - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request, - error: errorMessage, - }); - const promiseForResult = withNetworkClient( - { providerType, infuraNetwork: comms.infuraNetwork }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - buildFetchFailedErrorMessage(comms.rpcUrl, errorMessage), - ); - }); - }); - } else { - it('does not retry the request to the RPC endpoint, but throws immediately, if a "SyntaxError" error is thrown while making the request', async () => { - const customRpcUrl = 'http://example.com'; - - await withMockedCommunications( - { providerType, customRpcUrl }, - async (comms) => { - const request = { method }; - const errorMessage = 'SyntaxError: Some message'; - - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request, - error: errorMessage, - }); - const promiseForResult = withNetworkClient( - { providerType, customRpcUrl }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - buildFetchFailedErrorMessage(customRpcUrl, errorMessage), - ); - }, - ); - }); - - it('retries the request to the RPC endpoint up to 5 times if a "failed to parse response body" error is thrown while making the request, returning the successful result if there is one on the 5th try', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - // Here we have the request fail for the first 4 tries, then succeed - // on the 5th try. - comms.mockRpcCall({ - request, - error: 'failed to parse response body: some message', - times: 4, - }); - comms.mockRpcCall({ - request, - response: { - result: 'the result', - httpStatus: 200, - }, - }); - - const result = await withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - expect(result).toStrictEqual('the result'); - }); - }); - - it('returns an empty response if a "failed to parse response body" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - const errorMessage = 'failed to parse response body: some message'; - - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request, - error: errorMessage, - times: 5, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - await expect(promiseForResult).rejects.toThrow( - buildJsonRpcEngineEmptyResponseErrorMessage(method), - ); - }); - }); - } - - // Only the custom RPC middleware will detect a "Failed to fetch" error and - // attempt to retry the request to the RPC endpoint; the Infura middleware - // does not. - if (providerType === 'infura') { - it('does not retry the request to the RPC endpoint, but throws immediately, if a "Failed to fetch" error is thrown while making the request', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - const errorMessage = 'Failed to fetch: some message'; - - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request, - error: errorMessage, - }); - const promiseForResult = withNetworkClient( - { providerType, infuraNetwork: comms.infuraNetwork }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - buildFetchFailedErrorMessage(comms.rpcUrl, errorMessage), - ); - }); - }); - } else { - it('retries the request to the RPC endpoint up to 5 times if a "Failed to fetch" error is thrown while making the request, returning the successful result if there is one on the 5th try', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - // Here we have the request fail for the first 4 tries, then succeed - // on the 5th try. - comms.mockRpcCall({ - request, - error: 'Failed to fetch: some message', - times: 4, - }); - comms.mockRpcCall({ - request, - response: { - result: 'the result', - httpStatus: 200, - }, - }); - - const result = await withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - expect(result).toStrictEqual('the result'); - }); - }); - - it('returns an empty response if a "Failed to fetch" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - const errorMessage = 'Failed to fetch: some message'; - - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request, - error: errorMessage, - times: 5, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - await expect(promiseForResult).rejects.toThrow( - buildJsonRpcEngineEmptyResponseErrorMessage(method), - ); - }); - }); - } -} diff --git a/app/scripts/controllers/network/provider-api-tests/not-handled-by-middleware.ts b/app/scripts/controllers/network/provider-api-tests/not-handled-by-middleware.ts deleted file mode 100644 index ee92bb07f..000000000 --- a/app/scripts/controllers/network/provider-api-tests/not-handled-by-middleware.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* eslint-disable jest/require-top-level-describe, jest/no-export */ - -import { fill } from 'lodash'; -import { - ProviderType, - withMockedCommunications, - withNetworkClient, -} from './helpers'; - -type TestsForRpcMethodNotHandledByMiddlewareOptions = { - providerType: ProviderType; - numberOfParameters: number; -}; - -/** - * Defines tests which exercise the behavior exhibited by an RPC method that - * is not handled specially by the network client middleware. - * - * @param method - The name of the RPC method under test. - * @param additionalArgs - Additional arguments. - * @param additionalArgs.providerType - The type of provider being tested; - * either `infura` or `custom`. - * @param additionalArgs.numberOfParameters - The number of parameters that this - * RPC method takes. - */ -export function testsForRpcMethodNotHandledByMiddleware( - method: string, - { - providerType, - numberOfParameters, - }: TestsForRpcMethodNotHandledByMiddlewareOptions, -) { - if (providerType !== 'infura' && providerType !== 'custom') { - throw new Error( - `providerType must be either "infura" or "custom", was "${providerType}" instead`, - ); - } - - it('attempts to pass the request off to the RPC endpoint', async () => { - const request = { - method, - params: fill(Array(numberOfParameters), 'some value'), - }; - const expectedResult = 'the result'; - - await withMockedCommunications({ providerType }, async (comms) => { - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. It doesn't - // matter what this is — it's just used as a cache key. - comms.mockNextBlockTrackerRequest(); - comms.mockRpcCall({ - request, - response: { result: expectedResult }, - }); - const actualResult = await withNetworkClient( - { providerType }, - ({ makeRpcCall }) => makeRpcCall(request), - ); - - expect(actualResult).toStrictEqual(expectedResult); - }); - }); -} diff --git a/app/scripts/controllers/network/provider-api-tests/shared-tests.ts b/app/scripts/controllers/network/provider-api-tests/shared-tests.ts deleted file mode 100644 index cf34302ce..000000000 --- a/app/scripts/controllers/network/provider-api-tests/shared-tests.ts +++ /dev/null @@ -1,372 +0,0 @@ -/* eslint-disable jest/require-top-level-describe, jest/no-export, jest/no-identical-title */ - -import { testsForRpcMethodsThatCheckForBlockHashInResponse } from './block-hash-in-response'; -import { testsForRpcMethodSupportingBlockParam } from './block-param'; -import { - ProviderType, - withMockedCommunications, - withNetworkClient, -} from './helpers'; -import { testsForRpcMethodAssumingNoBlockParam } from './no-block-param'; -import { testsForRpcMethodNotHandledByMiddleware } from './not-handled-by-middleware'; - -/** - * Constructs an error message that the Infura client would produce in the event - * that it has attempted to retry the request to Infura and has failed. - * - * @param reason - The exact reason for failure. - * @returns The error message. - */ -export function buildInfuraClientRetriesExhaustedErrorMessage(reason: string) { - return new RegExp( - `^InfuraProvider - cannot complete request. All retries exhausted\\..+${reason}`, - 'us', - ); -} - -/** - * Constructs an error message that JsonRpcEngine would produce in the event - * that the response object is empty as it leaves the middleware. - * - * @param method - The RPC method. - * @returns The error message. - */ -export function buildJsonRpcEngineEmptyResponseErrorMessage(method: string) { - return new RegExp( - `^JsonRpcEngine: Response has no error or result for request:.+"method": "${method}"`, - 'us', - ); -} - -/** - * Constructs an error message that `fetch` with throw if it cannot make a - * request. - * - * @param url - The URL being fetched - * @param reason - The reason. - * @returns The error message. - */ -export function buildFetchFailedErrorMessage(url: string, reason: string) { - return new RegExp( - `^request to ${url}(/[^/ ]*)+ failed, reason: ${reason}`, - 'us', - ); -} - -/** - * Defines tests that are common to both the Infura and JSON-RPC network client. - * - * @param providerType - The type of provider being tested, which determines - * which suite of middleware is being tested. If `infura`, then the middleware - * exposed by `createInfuraClient` is tested; if `custom`, then the middleware - * exposed by `createJsonRpcClient` will be tested. - */ -export function testsForProviderType(providerType: ProviderType) { - // Ethereum JSON-RPC spec: - // Infura documentation: - - describe('methods included in the Ethereum JSON-RPC spec', () => { - describe('methods not handled by middleware', () => { - const notHandledByMiddleware = [ - // This list is presented in the same order as in the network client - // tests on the core side. - - { name: 'eth_newFilter', numberOfParameters: 1 }, - { name: 'eth_getFilterChanges', numberOfParameters: 1 }, - { name: 'eth_newBlockFilter', numberOfParameters: 0 }, - { name: 'eth_newPendingTransactionFilter', numberOfParameters: 0 }, - { name: 'eth_uninstallFilter', numberOfParameters: 1 }, - - { name: 'eth_sendRawTransaction', numberOfParameters: 1 }, - { name: 'eth_sendTransaction', numberOfParameters: 1 }, - { name: 'eth_sign', numberOfParameters: 2 }, - - { name: 'eth_createAccessList', numberOfParameters: 2 }, - { name: 'eth_getLogs', numberOfParameters: 1 }, - { name: 'eth_getProof', numberOfParameters: 3 }, - { name: 'eth_getWork', numberOfParameters: 0 }, - { name: 'eth_maxPriorityFeePerGas', numberOfParameters: 0 }, - { name: 'eth_submitHashRate', numberOfParameters: 2 }, - { name: 'eth_submitWork', numberOfParameters: 3 }, - { name: 'eth_syncing', numberOfParameters: 0 }, - { name: 'eth_feeHistory', numberOfParameters: 3 }, - { name: 'debug_getRawHeader', numberOfParameters: 1 }, - { name: 'debug_getRawBlock', numberOfParameters: 1 }, - { name: 'debug_getRawTransaction', numberOfParameters: 1 }, - { name: 'debug_getRawReceipts', numberOfParameters: 1 }, - { name: 'debug_getBadBlocks', numberOfParameters: 0 }, - - { name: 'eth_accounts', numberOfParameters: 0 }, - { name: 'eth_coinbase', numberOfParameters: 0 }, - { name: 'eth_hashrate', numberOfParameters: 0 }, - { name: 'eth_mining', numberOfParameters: 0 }, - - { name: 'eth_signTransaction', numberOfParameters: 1 }, - ]; - notHandledByMiddleware.forEach(({ name, numberOfParameters }) => { - describe(`method name: ${name}`, () => { - testsForRpcMethodNotHandledByMiddleware(name, { - providerType, - numberOfParameters, - }); - }); - }); - }); - - describe('methods with block hashes in their result', () => { - const methodsWithBlockHashInResponse = [ - { name: 'eth_getTransactionByHash', numberOfParameters: 1 }, - { name: 'eth_getTransactionReceipt', numberOfParameters: 1 }, - ]; - methodsWithBlockHashInResponse.forEach(({ name, numberOfParameters }) => { - describe(`method name: ${name}`, () => { - testsForRpcMethodsThatCheckForBlockHashInResponse(name, { - numberOfParameters, - providerType, - }); - }); - }); - }); - - describe('methods that assume there is no block param', () => { - const assumingNoBlockParam = [ - { name: 'eth_getFilterLogs', numberOfParameters: 1 }, - { name: 'eth_blockNumber', numberOfParameters: 0 }, - { name: 'eth_estimateGas', numberOfParameters: 2 }, - { name: 'eth_gasPrice', numberOfParameters: 0 }, - { name: 'eth_getBlockByHash', numberOfParameters: 2 }, - { - name: 'eth_getBlockTransactionCountByHash', - numberOfParameters: 1, - }, - { - name: 'eth_getTransactionByBlockHashAndIndex', - numberOfParameters: 2, - }, - { name: 'eth_getUncleByBlockHashAndIndex', numberOfParameters: 2 }, - { name: 'eth_getUncleCountByBlockHash', numberOfParameters: 1 }, - ]; - const blockParamIgnored = [ - { name: 'eth_getUncleCountByBlockNumber', numberOfParameters: 1 }, - { name: 'eth_getUncleByBlockNumberAndIndex', numberOfParameters: 2 }, - { - name: 'eth_getTransactionByBlockNumberAndIndex', - numberOfParameters: 2, - }, - { - name: 'eth_getBlockTransactionCountByNumber', - numberOfParameters: 1, - }, - ]; - assumingNoBlockParam - .concat(blockParamIgnored) - .forEach(({ name, numberOfParameters }) => - describe(`method name: ${name}`, () => { - testsForRpcMethodAssumingNoBlockParam(name, { - providerType, - numberOfParameters, - }); - }), - ); - }); - - describe('methods that have a param to specify the block', () => { - const supportingBlockParam = [ - { - name: 'eth_call', - blockParamIndex: 1, - numberOfParameters: 2, - }, - { - name: 'eth_getBalance', - blockParamIndex: 1, - numberOfParameters: 2, - }, - { - name: 'eth_getBlockByNumber', - blockParamIndex: 0, - numberOfParameters: 2, - }, - { name: 'eth_getCode', blockParamIndex: 1, numberOfParameters: 2 }, - { - name: 'eth_getStorageAt', - blockParamIndex: 2, - numberOfParameters: 3, - }, - { - name: 'eth_getTransactionCount', - blockParamIndex: 1, - numberOfParameters: 2, - }, - ]; - supportingBlockParam.forEach( - ({ name, blockParamIndex, numberOfParameters }) => { - describe(`method name: ${name}`, () => { - testsForRpcMethodSupportingBlockParam(name, { - providerType, - blockParamIndex, - numberOfParameters, - }); - }); - }, - ); - }); - - describe('other methods', () => { - describe('eth_getTransactionByHash', () => { - it("refreshes the block tracker's current block if it is less than the block number that comes back in the response", async () => { - const method = 'eth_getTransactionByHash'; - - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // This is our request. - comms.mockRpcCall({ - request, - response: { - result: { - blockNumber: '0x200', - }, - }, - }); - comms.mockNextBlockTrackerRequest({ blockNumber: '0x300' }); - - await withNetworkClient( - { providerType }, - async ({ makeRpcCall, blockTracker }) => { - await makeRpcCall(request); - expect(blockTracker.getCurrentBlock()).toStrictEqual('0x300'); - }, - ); - }); - }); - }); - - describe('eth_getTransactionReceipt', () => { - it("refreshes the block tracker's current block if it is less than the block number that comes back in the response", async () => { - const method = 'eth_getTransactionReceipt'; - - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // This is our request. - comms.mockRpcCall({ - request, - response: { - result: { - blockNumber: '0x200', - }, - }, - }); - comms.mockNextBlockTrackerRequest({ blockNumber: '0x300' }); - - await withNetworkClient( - { providerType }, - async ({ makeRpcCall, blockTracker }) => { - await makeRpcCall(request); - expect(blockTracker.getCurrentBlock()).toStrictEqual('0x300'); - }, - ); - }); - }); - }); - - describe('eth_chainId', () => { - it('does not hit the RPC endpoint, instead returning the configured chain id', async () => { - const networkId = await withNetworkClient( - { providerType: 'custom', customChainId: '0x1' }, - ({ makeRpcCall }) => { - return makeRpcCall({ method: 'eth_chainId' }); - }, - ); - - expect(networkId).toStrictEqual('0x1'); - }); - }); - }); - }); - - describe('methods not included in the Ethereum JSON-RPC spec', () => { - describe('methods not handled by middleware', () => { - const notHandledByMiddleware = [ - // This list is presented in the same order as in the network client - // tests on the core side. - - { name: 'net_listening', numberOfParameters: 0 }, - { name: 'eth_subscribe', numberOfParameters: 1 }, - { name: 'eth_unsubscribe', numberOfParameters: 1 }, - { name: 'custom_rpc_method', numberOfParameters: 1 }, - { name: 'net_peerCount', numberOfParameters: 0 }, - { name: 'parity_nextNonce', numberOfParameters: 1 }, - ]; - notHandledByMiddleware.forEach(({ name, numberOfParameters }) => { - describe(`method name: ${name}`, () => { - testsForRpcMethodNotHandledByMiddleware(name, { - providerType, - numberOfParameters, - }); - }); - }); - }); - - describe('methods that assume there is no block param', () => { - const assumingNoBlockParam = [ - { name: 'web3_clientVersion', numberOfParameters: 0 }, - { name: 'eth_protocolVersion', numberOfParameters: 0 }, - ]; - assumingNoBlockParam.forEach(({ name, numberOfParameters }) => - describe(`method name: ${name}`, () => { - testsForRpcMethodAssumingNoBlockParam(name, { - providerType, - numberOfParameters, - }); - }), - ); - }); - - describe('other methods', () => { - describe('net_version', () => { - // The Infura middleware includes `net_version` in its scaffold - // middleware, whereas the custom RPC middleware does not. - if (providerType === 'infura') { - it('does not hit Infura, instead returning the network ID that maps to the Infura network, as a decimal string', async () => { - const networkId = await withNetworkClient( - { providerType: 'infura', infuraNetwork: 'goerli' }, - ({ makeRpcCall }) => { - return makeRpcCall({ - method: 'net_version', - }); - }, - ); - expect(networkId).toStrictEqual('5'); - }); - } else { - it('hits the RPC endpoint', async () => { - await withMockedCommunications( - { providerType: 'custom' }, - async (comms) => { - comms.mockRpcCall({ - request: { method: 'net_version' }, - response: { result: '1' }, - }); - - const networkId = await withNetworkClient( - { providerType: 'custom' }, - ({ makeRpcCall }) => { - return makeRpcCall({ - method: 'net_version', - }); - }, - ); - - expect(networkId).toStrictEqual('1'); - }, - ); - }); - } - }); - }); - }); -} diff --git a/app/scripts/controllers/network/test/fake-block-tracker.ts b/app/scripts/controllers/network/test/fake-block-tracker.ts deleted file mode 100644 index 89f8309d1..000000000 --- a/app/scripts/controllers/network/test/fake-block-tracker.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { PollingBlockTracker } from 'eth-block-tracker'; - -/** - * Acts like a PollingBlockTracker, but doesn't start the polling loop or - * make any requests. - */ -export class FakeBlockTracker extends PollingBlockTracker { - async _start() { - // do nothing - } -} diff --git a/app/scripts/controllers/network/test/fake-provider.ts b/app/scripts/controllers/network/test/fake-provider.ts deleted file mode 100644 index a2820112d..000000000 --- a/app/scripts/controllers/network/test/fake-provider.ts +++ /dev/null @@ -1,206 +0,0 @@ -import { inspect, isDeepStrictEqual } from 'util'; -import { - JsonRpcEngine, - JsonRpcRequest, - JsonRpcResponse, -} from 'json-rpc-engine'; -import { SafeEventEmitterProvider } from '@metamask/eth-json-rpc-provider/dist/safe-event-emitter-provider'; - -// Store this in case it gets stubbed later -const originalSetTimeout = global.setTimeout; - -/** - * An object that allows specifying the behavior of a specific invocation of - * `sendAsync`. The `method` always identifies the stub, but the behavior - * may be specified multiple ways: `sendAsync` can either return a promise or - * throw an error, and if it returns a promise, that promise can either be - * resolved with a response object or reject with an error. - * - * @property request - Looks for a request matching these specifications. - * @property request.method - The RPC method to which this stub will be matched. - * @property request.params - The params to which this stub will be matched. - * @property response - Instructs `sendAsync` to return a promise that resolves - * with a response object. - * @property response.result - Specifies a successful response, with this as the - * `result`. - * @property response.error - Specifies an error response, with this as the - * `error`. - * @property error - Instructs `sendAsync` to return a promise that rejects with - * this error. - * @property implementation - Allows overriding `sendAsync` entirely. Useful if - * you want it to throw an error. - * @property delay - The amount of time that will pass after the callback is - * called with the response. - * @property discardAfterMatching - Usually after the stub matches a request, it - * is discarded, but setting this to true prevents that from happening. True by - * default. - * @property beforeCompleting - Sometimes it is useful to do something after the - * request is kicked off but before it ends (or, in terms of a `fetch` promise, - * when the promise is initiated but before it is resolved). You can pass an - * (async) function for this option to do this. - */ -export type FakeProviderStub = { - request: { - method: string; - params?: any[]; - }; - delay?: number; - discardAfterMatching?: boolean; - beforeCompleting?: () => void | Promise; -} & ( - | { - response: { result: any } | { error: string }; - } - | { - error: unknown; - } - | { - implementation: () => void; - } -); - -/** - * The set of options that the FakeProviderEngine constructor takes. - * - * @property stubs - A set of objects that allow specifying the behavior - * of specific invocations of `sendAsync` matching a `method`. - */ -interface FakeProviderEngineOptions { - stubs?: FakeProviderStub[]; -} - -/** - * FakeProviderEngine is an implementation of the provider that - * NetworkController exposes, which is actually an instance of - * Web3ProviderEngine (from the `web3-provider-engine` package). Hence it - * supports the same interface as Web3ProviderEngine, except that fake responses - * for any RPC methods that are accessed can be supplied via an API that is more - * succinct than using Jest's mocking API. - */ -// NOTE: We shouldn't need to extend from the "real" provider here, but -// we'd need a `SafeEventEmitterProvider` _interface_ and that doesn't exist (at -// least not yet). -export class FakeProvider extends SafeEventEmitterProvider { - calledStubs: FakeProviderStub[]; - - #originalStubs: FakeProviderStub[]; - - #stubs: FakeProviderStub[]; - - /** - * Makes a new instance of the fake provider. - * - * @param options - The options. - * @param options.stubs - A set of objects that allow specifying the behavior - * of specific invocations of `sendAsync` matching a `method`. - */ - constructor({ stubs = [] }: FakeProviderEngineOptions) { - super({ engine: new JsonRpcEngine() }); - this.#originalStubs = stubs; - this.#stubs = this.#originalStubs.slice(); - this.calledStubs = []; - } - - send = ( - payload: JsonRpcRequest, - callback: (error: unknown, response?: JsonRpcResponse) => void, - ) => { - return this.#handleSend(payload, callback); - }; - - sendAsync = ( - payload: JsonRpcRequest, - callback: (error: unknown, response?: JsonRpcResponse) => void, - ) => { - return this.#handleSend(payload, callback); - }; - - #handleSend( - payload: JsonRpcRequest, - callback: (error: unknown, response?: JsonRpcResponse) => void, - ) { - if (Array.isArray(payload)) { - throw new Error("Arrays aren't supported"); - } - - const index = this.#stubs.findIndex((stub) => { - return ( - stub.request.method === payload.method && - (!('params' in stub.request) || - isDeepStrictEqual(stub.request.params, payload.params)) - ); - }); - - if (index === -1) { - const matchingCalledStubs = this.calledStubs.filter((stub) => { - return ( - stub.request.method === payload.method && - (!('params' in stub.request) || - isDeepStrictEqual(stub.request.params, payload.params)) - ); - }); - let message = `Could not find any stubs matching: ${inspect(payload, { - depth: null, - })}`; - if (matchingCalledStubs.length > 0) { - message += `\n\nIt appears the following stubs were defined, but have been called already:\n\n${inspect( - matchingCalledStubs, - { depth: null }, - )}`; - } - - throw new Error(message); - } else { - const stub = this.#stubs[index]; - - if (stub.discardAfterMatching !== false) { - this.#stubs.splice(index, 1); - } - - if (stub.delay) { - originalSetTimeout(() => { - this.#handleRequest(stub, callback); - }, stub.delay); - } else { - this.#handleRequest(stub, callback); - } - - this.calledStubs.push({ ...stub }); - } - } - - async #handleRequest( - stub: FakeProviderStub, - callback: (error: unknown, response?: JsonRpcResponse) => void, - ) { - if (stub.beforeCompleting) { - await stub.beforeCompleting(); - } - - if ('implementation' in stub) { - stub.implementation(); - return; - } - - if ('response' in stub) { - if ('result' in stub.response) { - callback(null, { - jsonrpc: '2.0', - id: 1, - result: stub.response.result, - }); - } else if ('error' in stub.response) { - callback(null, { - jsonrpc: '2.0', - id: 1, - error: { - code: -999, - message: stub.response.error, - }, - }); - } - } else if ('error' in stub) { - callback(stub.error); - } - } -} diff --git a/app/scripts/controllers/swaps.js b/app/scripts/controllers/swaps.js index 505b696d7..295428813 100644 --- a/app/scripts/controllers/swaps.js +++ b/app/scripts/controllers/swaps.js @@ -147,9 +147,9 @@ export default class SwapsController { this.indexOfNewestCallInFlight = 0; this.ethersProvider = new Web3Provider(provider); - this._currentNetworkId = networkController.store.getState().networkId; + this._currentNetworkId = networkController.state.networkId; onNetworkStateChange(() => { - const { networkId, networkStatus } = networkController.store.getState(); + const { networkId, networkStatus } = networkController.state; if ( networkStatus === NetworkStatus.Available && networkId !== this._currentNetworkId diff --git a/app/scripts/controllers/swaps.test.js b/app/scripts/controllers/swaps.test.js index e3dbaafda..a17d131ca 100644 --- a/app/scripts/controllers/swaps.test.js +++ b/app/scripts/controllers/swaps.test.js @@ -100,11 +100,9 @@ const MOCK_GET_BUFFERED_GAS_LIMIT = async () => ({ function getMockNetworkController() { return { - store: { - getState: sinon.stub().returns({ - networkId: NETWORK_IDS.GOERLI, - networkStatus: NetworkStatus.Available, - }), + state: { + networkId: NETWORK_IDS.GOERLI, + networkStatus: NetworkStatus.Available, }, }; } @@ -208,7 +206,10 @@ describe('SwapsController', function () { it('should replace ethers instance when network changes', function () { const networkController = getMockNetworkController(); - const onNetworkStateChange = sinon.stub(); + let networkStateChangeListener; + const onNetworkStateChange = (listener) => { + networkStateChangeListener = listener; + }; const swapsController = new SwapsController({ getBufferedGasLimit: MOCK_GET_BUFFERED_GAS_LIMIT, networkController, @@ -220,13 +221,12 @@ describe('SwapsController', function () { getCurrentChainId: getCurrentChainIdStub, }); const currentEthersInstance = swapsController.ethersProvider; - const changeNetwork = onNetworkStateChange.getCall(0).args[0]; - networkController.store.getState.returns({ + networkController.state = { networkId: NETWORK_IDS.MAINNET, networkStatus: NetworkStatus.Available, - }); - changeNetwork(NETWORK_IDS.MAINNET); + }; + networkStateChangeListener(); const newEthersInstance = swapsController.ethersProvider; assert.notStrictEqual( @@ -238,7 +238,10 @@ describe('SwapsController', function () { it('should not replace ethers instance when network changes to loading', function () { const networkController = getMockNetworkController(); - const onNetworkStateChange = sinon.stub(); + let networkStateChangeListener; + const onNetworkStateChange = (listener) => { + networkStateChangeListener = listener; + }; const swapsController = new SwapsController({ getBufferedGasLimit: MOCK_GET_BUFFERED_GAS_LIMIT, networkController, @@ -250,13 +253,12 @@ describe('SwapsController', function () { getCurrentChainId: getCurrentChainIdStub, }); const currentEthersInstance = swapsController.ethersProvider; - const changeNetwork = onNetworkStateChange.getCall(0).args[0]; - networkController.store.getState.returns({ + networkController.state = { networkId: null, networkStatus: NetworkStatus.Unknown, - }); - changeNetwork('loading'); + }; + networkStateChangeListener(); const newEthersInstance = swapsController.ethersProvider; assert.strictEqual( @@ -268,7 +270,10 @@ describe('SwapsController', function () { it('should not replace ethers instance when network changes to the same network', function () { const networkController = getMockNetworkController(); - const onNetworkStateChange = sinon.stub(); + let networkStateChangeListener; + const onNetworkStateChange = (listener) => { + networkStateChangeListener = listener; + }; const swapsController = new SwapsController({ getBufferedGasLimit: MOCK_GET_BUFFERED_GAS_LIMIT, networkController, @@ -280,13 +285,12 @@ describe('SwapsController', function () { getCurrentChainId: getCurrentChainIdStub, }); const currentEthersInstance = swapsController.ethersProvider; - const changeNetwork = onNetworkStateChange.getCall(0).args[0]; - networkController.store.getState.returns({ + networkController.state = { networkId: NETWORK_IDS.GOERLI, networkStatus: NetworkStatus.Available, - }); - changeNetwork(NETWORK_IDS.GOERLI); + }; + networkStateChangeListener(); const newEthersInstance = swapsController.ethersProvider; assert.strictEqual( diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 9613a9aa6..0ccdb519d 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -39,6 +39,7 @@ import { } from '@metamask/assets-controllers'; import { PhishingController } from '@metamask/phishing-controller'; import { AnnouncementController } from '@metamask/announcement-controller'; +import { NetworkController } from '@metamask/network-controller'; import { GasFeeController } from '@metamask/gas-fee-controller'; import { PermissionController, @@ -105,6 +106,7 @@ import { import { CHAIN_IDS, NETWORK_TYPES, + TEST_NETWORK_TICKER_MAP, NetworkStatus, } from '../../shared/constants/network'; import { HardwareDeviceNames } from '../../shared/constants/hardware-wallets'; @@ -167,7 +169,6 @@ import createTabIdMiddleware from './lib/createTabIdMiddleware'; import createOnboardingMiddleware from './lib/createOnboardingMiddleware'; import { setupMultiplex } from './lib/stream-utils'; import EnsController from './controllers/ens'; -import { NetworkController } from './controllers/network'; import PreferencesController from './controllers/preferences'; import AppStateController from './controllers/app-state'; import CachedBalancesController from './controllers/cached-balances'; @@ -302,6 +303,7 @@ export default class MetamaskController extends EventEmitter { const networkControllerMessenger = this.controllerMessenger.getRestricted({ name: 'NetworkController', allowedEvents: [ + 'NetworkController:stateChange', 'NetworkController:networkWillChange', 'NetworkController:networkDidChange', 'NetworkController:infuraIsBlocked', @@ -309,9 +311,34 @@ export default class MetamaskController extends EventEmitter { ], }); + let initialProviderConfig; + if (process.env.IN_TEST) { + initialProviderConfig = { + type: NETWORK_TYPES.RPC, + rpcUrl: 'http://localhost:8545', + chainId: '0x539', + nickname: 'Localhost 8545', + ticker: 'ETH', + }; + } else if ( + process.env.METAMASK_DEBUG || + process.env.METAMASK_ENVIRONMENT === 'test' + ) { + initialProviderConfig = { + type: NETWORK_TYPES.GOERLI, + chainId: CHAIN_IDS.GOERLI, + ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.GOERLI], + }; + } + const initialNetworkControllerState = initialProviderConfig + ? { + providerConfig: initialProviderConfig, + ...initState.NetworkController, + } + : initState.NetworkController; this.networkController = new NetworkController({ messenger: networkControllerMessenger, - state: initState.NetworkController, + state: initialNetworkControllerState, infuraProjectId: opts.infuraProjectId, trackMetaMetricsEvent: (...args) => this.metaMetricsController.trackEvent(...args), @@ -331,13 +358,10 @@ export default class MetamaskController extends EventEmitter { }); this.tokenListController = new TokenListController({ - chainId: this.networkController.store.getState().providerConfig.chainId, + chainId: this.networkController.state.providerConfig.chainId, preventPollingOnNetworkRestart: initState.TokenListController ? initState.TokenListController.preventPollingOnNetworkRestart : true, - onNetworkStateChange: this.networkController.store.subscribe.bind( - this.networkController.store, - ), messenger: tokenListMessenger, state: initState.TokenListController, }); @@ -365,25 +389,28 @@ export default class MetamaskController extends EventEmitter { await updateCurrentLocale(currentLocale); }); + const tokensControllerMessenger = this.controllerMessenger.getRestricted({ + name: 'TokensController', + allowedActions: ['ApprovalController:addRequest'], + allowedEvents: ['NetworkController:stateChange'], + }); this.tokensController = new TokensController({ - chainId: this.networkController.store.getState().providerConfig.chainId, + messenger: tokensControllerMessenger, + chainId: this.networkController.state.providerConfig.chainId, onPreferencesStateChange: this.preferencesController.store.subscribe.bind( this.preferencesController.store, ), - onNetworkStateChange: this.networkController.store.subscribe.bind( - this.networkController.store, + onNetworkStateChange: networkControllerMessenger.subscribe.bind( + networkControllerMessenger, + 'NetworkController:stateChange', ), config: { provider: this.provider }, state: initState.TokensController, - messenger: this.controllerMessenger.getRestricted({ - name: 'TokensController', - allowedActions: [`${this.approvalController.name}:addRequest`], - }), }); this.assetsContractController = new AssetsContractController( { - chainId: this.networkController.store.getState().providerConfig.chainId, + chainId: this.networkController.state.providerConfig.chainId, onPreferencesStateChange: (listener) => this.preferencesController.store.subscribe(listener), // This handler is misnamed, and is a known issue that will be resolved @@ -398,7 +425,7 @@ export default class MetamaskController extends EventEmitter { networkControllerMessenger.subscribe( 'NetworkController:networkDidChange', () => { - const networkState = this.networkController.store.getState(); + const networkState = this.networkController.state; return cb(networkState); }, ), @@ -416,13 +443,14 @@ export default class MetamaskController extends EventEmitter { this.nftController = new NftController( { messenger: nftControllerMessenger, - chainId: this.networkController.store.getState().providerConfig.chainId, + chainId: this.networkController.state.providerConfig.chainId, onPreferencesStateChange: this.preferencesController.store.subscribe.bind( this.preferencesController.store, ), - onNetworkStateChange: this.networkController.store.subscribe.bind( - this.networkController.store, + onNetworkStateChange: networkControllerMessenger.subscribe.bind( + networkControllerMessenger, + 'NetworkController:stateChange', ), getERC721AssetName: this.assetsContractController.getERC721AssetName.bind( @@ -469,13 +497,14 @@ export default class MetamaskController extends EventEmitter { this.nftController.setApiKey(process.env.OPENSEA_KEY); this.nftDetectionController = new NftDetectionController({ - chainId: this.networkController.store.getState().providerConfig.chainId, + chainId: this.networkController.state.providerConfig.chainId, onNftsStateChange: (listener) => this.nftController.subscribe(listener), onPreferencesStateChange: this.preferencesController.store.subscribe.bind( this.preferencesController.store, ), - onNetworkStateChange: this.networkController.store.subscribe.bind( - this.networkController.store, + onNetworkStateChange: networkControllerMessenger.subscribe.bind( + networkControllerMessenger, + 'NetworkController:stateChange', ), getOpenSeaApiKey: () => this.nftController.openSeaApiKey, getBalancesInSingleCall: @@ -494,12 +523,11 @@ export default class MetamaskController extends EventEmitter { 'NetworkController:networkDidChange', ), getNetworkIdentifier: () => { - const { type, rpcUrl } = - this.networkController.store.getState().providerConfig; + const { type, rpcUrl } = this.networkController.state.providerConfig; return type === NETWORK_TYPES.RPC ? rpcUrl : type; }, getCurrentChainId: () => - this.networkController.store.getState().providerConfig.chainId, + this.networkController.state.providerConfig.chainId, version: this.platform.getVersion(), environment: process.env.METAMASK_ENVIRONMENT, extension: this.extension, @@ -531,7 +559,7 @@ export default class MetamaskController extends EventEmitter { onNetworkStateChange: (eventHandler) => { networkControllerMessenger.subscribe( 'NetworkController:networkDidChange', - () => eventHandler(this.networkController.store.getState()), + () => eventHandler(this.networkController.state), ); }, getCurrentNetworkEIP1559Compatibility: @@ -543,12 +571,10 @@ export default class MetamaskController extends EventEmitter { legacyAPIEndpoint: `${gasApiBaseUrl}/networks//gasPrices`, EIP1559APIEndpoint: `${gasApiBaseUrl}/networks//suggestedGasFees`, getCurrentNetworkLegacyGasAPICompatibility: () => { - const { chainId } = - this.networkController.store.getState().providerConfig; + const { chainId } = this.networkController.state.providerConfig; return process.env.IN_TEST || chainId === CHAIN_IDS.MAINNET; }, - getChainId: () => - this.networkController.store.getState().providerConfig.chainId, + getChainId: () => this.networkController.state.providerConfig.chainId, }); this.qrHardwareKeyring = new QRHardwareKeyring(); @@ -577,8 +603,7 @@ export default class MetamaskController extends EventEmitter { messenger: currencyRateMessenger, state: { ...initState.CurrencyController, - nativeCurrency: - this.networkController.store.getState().providerConfig.ticker, + nativeCurrency: this.networkController.state.providerConfig.ticker, }, }); @@ -606,7 +631,7 @@ export default class MetamaskController extends EventEmitter { // token exchange rate tracker this.tokenRatesController = new TokenRatesController( { - chainId: this.networkController.store.getState().providerConfig.chainId, + chainId: this.networkController.state.providerConfig.chainId, onTokensStateChange: (listener) => this.tokensController.subscribe(listener), onCurrencyRateStateChange: (listener) => @@ -614,8 +639,9 @@ export default class MetamaskController extends EventEmitter { `${this.currencyRateController.name}:stateChange`, listener, ), - onNetworkStateChange: this.networkController.store.subscribe.bind( - this.networkController.store, + onNetworkStateChange: networkControllerMessenger.subscribe.bind( + networkControllerMessenger, + 'NetworkController:stateChange', ), }, { @@ -645,7 +671,7 @@ export default class MetamaskController extends EventEmitter { this.ensController = new EnsController({ provider: this.provider, getCurrentChainId: () => - this.networkController.store.getState().providerConfig.chainId, + this.networkController.state.providerConfig.chainId, onNetworkDidChange: networkControllerMessenger.subscribe.bind( networkControllerMessenger, 'NetworkController:networkDidChange', @@ -663,7 +689,7 @@ export default class MetamaskController extends EventEmitter { 'NetworkController:networkDidChange', ), getCurrentChainId: () => - this.networkController.store.getState().providerConfig.chainId, + this.networkController.state.providerConfig.chainId, preferencesController: this.preferencesController, onboardingController: this.onboardingController, initState: initState.IncomingTransactionsController, @@ -674,10 +700,9 @@ export default class MetamaskController extends EventEmitter { provider: this.provider, blockTracker: this.blockTracker, getCurrentChainId: () => - this.networkController.store.getState().providerConfig.chainId, + this.networkController.state.providerConfig.chainId, getNetworkIdentifier: () => { - const { type, rpcUrl } = - this.networkController.store.getState().providerConfig; + const { type, rpcUrl } = this.networkController.state.providerConfig; return type === NETWORK_TYPES.RPC ? rpcUrl : type; }, preferencesController: this.preferencesController, @@ -714,7 +739,7 @@ export default class MetamaskController extends EventEmitter { this.cachedBalancesController = new CachedBalancesController({ accountTracker: this.accountTracker, getCurrentChainId: () => - this.networkController.store.getState().providerConfig.chainId, + this.networkController.state.providerConfig.chainId, initState: initState.CachedBalancesController, }); @@ -988,7 +1013,13 @@ export default class MetamaskController extends EventEmitter { }); ///: END:ONLY_INCLUDE_IN + const detectTokensControllerMessenger = + this.controllerMessenger.getRestricted({ + name: 'DetectTokensController', + allowedEvents: ['NetworkController:stateChange'], + }); this.detectTokensController = new DetectTokensController({ + messenger: detectTokensControllerMessenger, preferences: this.preferencesController, tokensController: this.tokensController, assetsContractController: this.assetsContractController, @@ -1039,29 +1070,24 @@ export default class MetamaskController extends EventEmitter { initState: initState.TransactionController || initState.TransactionManager, getPermittedAccounts: this.getPermittedAccounts.bind(this), - getProviderConfig: () => - this.networkController.store.getState().providerConfig, + getProviderConfig: () => this.networkController.state.providerConfig, getCurrentNetworkEIP1559Compatibility: this.networkController.getEIP1559Compatibility.bind( this.networkController, ), getCurrentAccountEIP1559Compatibility: this.getCurrentAccountEIP1559Compatibility.bind(this), - getNetworkId: () => this.networkController.store.getState().networkId, - getNetworkStatus: () => - this.networkController.store.getState().networkStatus, + getNetworkId: () => this.networkController.state.networkId, + getNetworkStatus: () => this.networkController.state.networkStatus, onNetworkStateChange: (listener) => { - let previousNetworkId = - this.networkController.store.getState().networkId; - this.networkController.store.subscribe((state) => { - if (previousNetworkId !== state.networkId) { - listener(); - previousNetworkId = state.networkId; - } - }); + networkControllerMessenger.subscribe( + 'NetworkController:stateChange', + () => listener(), + ({ networkId }) => networkId, + ); }, getCurrentChainId: () => - this.networkController.store.getState().providerConfig.chainId, + this.networkController.state.providerConfig.chainId, preferencesStore: this.preferencesController.store, txHistoryLimit: 60, signTransaction: this.keyringController.signTransaction.bind( @@ -1139,8 +1165,7 @@ export default class MetamaskController extends EventEmitter { const txMeta = this.txController.txStateManager.getTransaction(txId); let rpcPrefs = {}; if (txMeta.chainId) { - const { networkConfigurations } = - this.networkController.store.getState(); + const { networkConfigurations } = this.networkController.state; const matchingNetworkConfig = Object.values( networkConfigurations, ).find( @@ -1221,8 +1246,7 @@ export default class MetamaskController extends EventEmitter { networkControllerMessenger.subscribe( 'NetworkController:networkDidChange', async () => { - const { ticker } = - this.networkController.store.getState().providerConfig; + const { ticker } = this.networkController.state.providerConfig; try { await this.currencyRateController.setNativeCurrency(ticker); } catch (error) { @@ -1277,7 +1301,7 @@ export default class MetamaskController extends EventEmitter { getAllState: this.getState.bind(this), securityProviderRequest: this.securityProviderRequest.bind(this), getCurrentChainId: () => - this.networkController.store.getState().providerConfig.chainId, + this.networkController.state.providerConfig.chainId, }); this.signatureController.hub.on( @@ -1301,14 +1325,15 @@ export default class MetamaskController extends EventEmitter { this.txController.txGasUtil, ), networkController: this.networkController, - onNetworkStateChange: (listener) => - this.networkController.store.subscribe(listener), + onNetworkStateChange: networkControllerMessenger.subscribe.bind( + networkControllerMessenger, + 'NetworkController:stateChange', + ), provider: this.provider, - getProviderConfig: () => - this.networkController.store.getState().providerConfig, + getProviderConfig: () => this.networkController.state.providerConfig, getTokenRatesState: () => this.tokenRatesController.state, getCurrentChainId: () => - this.networkController.store.getState().providerConfig.chainId, + this.networkController.state.providerConfig.chainId, getEIP1559GasFeeEstimates: this.gasFeeController.fetchGasFeeEstimates.bind( this.gasFeeController, @@ -1318,19 +1343,11 @@ export default class MetamaskController extends EventEmitter { ); this.smartTransactionsController = new SmartTransactionsController( { - onNetworkStateChange: (cb) => { - this.networkController.store.subscribe((networkState) => { - const modifiedNetworkState = { - ...networkState, - providerConfig: { - ...networkState.providerConfig, - }, - }; - return cb(modifiedNetworkState); - }); - }, - getNetwork: () => - this.networkController.store.getState().networkId ?? 'loading', + onNetworkStateChange: networkControllerMessenger.subscribe.bind( + networkControllerMessenger, + 'NetworkController:stateChange', + ), + getNetwork: () => this.networkController.state.networkId ?? 'loading', getNonceLock: this.txController.nonceTracker.getNonceLock.bind( this.txController.nonceTracker, ), @@ -1482,7 +1499,7 @@ export default class MetamaskController extends EventEmitter { MetaMetricsController: this.metaMetricsController.store, AddressBookController: this.addressBookController, CurrencyController: this.currencyRateController, - NetworkController: this.networkController.store, + NetworkController: this.networkController, CachedBalancesController: this.cachedBalancesController.store, AlertController: this.alertController.store, OnboardingController: this.onboardingController.store, @@ -1520,7 +1537,7 @@ export default class MetamaskController extends EventEmitter { this.memStore = new ComposableObservableStore({ config: { AppStateController: this.appStateController.store, - NetworkController: this.networkController.store, + NetworkController: this.networkController, CachedBalancesController: this.cachedBalancesController.store, KeyringController: this.keyringController.memStore, PreferencesController: this.preferencesController.store, @@ -1906,7 +1923,7 @@ export default class MetamaskController extends EventEmitter { updatePublicConfigStore(this.getState()); function updatePublicConfigStore(memState) { - const { chainId } = networkController.store.getState().providerConfig; + const { chainId } = networkController.state.providerConfig; if (memState.networkStatus === NetworkStatus.Available) { publicConfigStore.putState(selectPublicState(chainId, memState)); } @@ -1947,7 +1964,7 @@ export default class MetamaskController extends EventEmitter { getProviderNetworkState(memState) { const { networkId } = memState || this.getState(); return { - chainId: this.networkController.store.getState().providerConfig.chainId, + chainId: this.networkController.state.providerConfig.chainId, networkVersion: networkId ?? 'loading', }; } @@ -2975,8 +2992,7 @@ export default class MetamaskController extends EventEmitter { this.appStateController.setTrezorModel(model); } - keyring.network = - this.networkController.store.getState().providerConfig.type; + keyring.network = this.networkController.state.providerConfig.type; return keyring; } @@ -3862,12 +3878,12 @@ export default class MetamaskController extends EventEmitter { ), getCurrentChainId: () => - this.networkController.store.getState().providerConfig.chainId, + this.networkController.state.providerConfig.chainId, getCurrentRpcUrl: () => - this.networkController.store.getState().providerConfig.rpcUrl, + this.networkController.state.providerConfig.rpcUrl, // network configuration-related getNetworkConfigurations: () => - this.networkController.store.getState().networkConfigurations, + this.networkController.state.networkConfigurations, upsertNetworkConfiguration: this.networkController.upsertNetworkConfiguration.bind( this.networkController, @@ -4286,7 +4302,7 @@ export default class MetamaskController extends EventEmitter { * @returns {object} rpcInfo found in the network configurations list */ findNetworkConfigurationBy(rpcInfo) { - const { networkConfigurations } = this.networkController.store.getState(); + const { networkConfigurations } = this.networkController.state; const networkConfiguration = Object.values(networkConfigurations).find( (configuration) => { return Object.keys(rpcInfo).some((key) => { @@ -4502,9 +4518,7 @@ export default class MetamaskController extends EventEmitter { if (transactionSecurityCheckEnabled) { const chainId = Number( - hexToDecimal( - this.networkController.store.getState().providerConfig.chainId, - ), + hexToDecimal(this.networkController.state.providerConfig.chainId), ); try { diff --git a/jest.config.js b/jest.config.js index 6665b9802..d4cc55266 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,9 +1,6 @@ module.exports = { collectCoverageFrom: [ '/app/scripts/constants/error-utils.js', - '/app/scripts/controllers/network/**/*.js', - '/app/scripts/controllers/network/**/*.ts', - '!/app/scripts/controllers/network/**/test/*.ts', '/app/scripts/controllers/permissions/**/*.js', '/app/scripts/controllers/sign.ts', '/app/scripts/controllers/decrypt-message.ts', @@ -40,8 +37,6 @@ module.exports = { '/app/scripts/constants/error-utils.test.js', '/app/scripts/controllers/app-state.test.js', '/app/scripts/controllers/mmi-controller.test.js', - '/app/scripts/controllers/network/**/*.test.js', - '/app/scripts/controllers/network/**/*.test.ts', '/app/scripts/controllers/permissions/**/*.test.js', '/app/scripts/controllers/sign.test.ts', '/app/scripts/controllers/decrypt-message.test.ts', diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index 929c69e1b..494ce3924 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -905,50 +905,6 @@ "fetch": true } }, - "@metamask/eth-json-rpc-infura": { - "globals": { - "setTimeout": true - }, - "packages": { - "@metamask/eth-json-rpc-infura>@metamask/utils": true, - "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true, - "eth-rpc-errors": true, - "json-rpc-engine": true, - "node-fetch": true - } - }, - "@metamask/eth-json-rpc-infura>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true - } - }, - "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": { - "globals": { - "URL": true, - "btoa": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/eth-json-rpc-infura>@metamask/utils": true, - "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true, - "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, - "@metamask/safe-event-emitter": true, - "browserify>browser-resolve": true, - "eth-rpc-errors": true, - "json-rpc-engine": true, - "lavamoat>json-stable-stringify": true, - "vinyl>clone": true - } - }, "@metamask/eth-json-rpc-middleware": { "globals": { "URL": true, @@ -977,12 +933,6 @@ "superstruct": true } }, - "@metamask/eth-json-rpc-provider": { - "packages": { - "@metamask/safe-event-emitter": true, - "json-rpc-engine": true - } - }, "@metamask/eth-keyring-controller": { "packages": { "@metamask/browser-passworder": true, @@ -1588,6 +1538,100 @@ "browserify>url": true } }, + "@metamask/network-controller": { + "globals": { + "URL": true, + "btoa": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/eth-json-rpc-middleware": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura": true, + "@metamask/network-controller>@metamask/eth-json-rpc-provider": true, + "@metamask/network-controller>@metamask/swappable-obj-proxy": true, + "@metamask/network-controller>eth-block-tracker": true, + "@metamask/utils": true, + "browserify>assert": true, + "eth-query": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "uuid": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-infura": { + "globals": { + "setTimeout": true + }, + "packages": { + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "node-fetch": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": { + "globals": { + "URL": true, + "btoa": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true, + "@metamask/safe-event-emitter": true, + "browserify>browser-resolve": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "lavamoat>json-stable-stringify": true, + "vinyl>clone": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-provider": { + "packages": { + "@metamask/safe-event-emitter": true, + "json-rpc-engine": true + } + }, + "@metamask/network-controller>eth-block-tracker": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/network-controller>eth-block-tracker>@metamask/safe-event-emitter": true, + "@metamask/network-controller>eth-block-tracker>pify": true, + "@metamask/utils": true, + "eth-query>json-rpc-random-id": true + } + }, + "@metamask/network-controller>eth-block-tracker>@metamask/safe-event-emitter": { + "globals": { + "setTimeout": true + }, + "packages": { + "browserify>events": true + } + }, "@metamask/notification-controller>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2972,27 +3016,6 @@ "postMessage": true } }, - "eth-block-tracker": { - "globals": { - "clearTimeout": true, - "console.error": true, - "setTimeout": true - }, - "packages": { - "@metamask/utils": true, - "eth-block-tracker>@metamask/safe-event-emitter": true, - "eth-block-tracker>pify": true, - "eth-query>json-rpc-random-id": true - } - }, - "eth-block-tracker>@metamask/safe-event-emitter": { - "globals": { - "setTimeout": true - }, - "packages": { - "browserify>events": true - } - }, "eth-ens-namehash": { "globals": { "name": "write" diff --git a/lavamoat/browserify/desktop/policy.json b/lavamoat/browserify/desktop/policy.json index d470098a1..fafcfcc40 100644 --- a/lavamoat/browserify/desktop/policy.json +++ b/lavamoat/browserify/desktop/policy.json @@ -976,50 +976,6 @@ "define": true } }, - "@metamask/eth-json-rpc-infura": { - "globals": { - "setTimeout": true - }, - "packages": { - "@metamask/eth-json-rpc-infura>@metamask/utils": true, - "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true, - "eth-rpc-errors": true, - "json-rpc-engine": true, - "node-fetch": true - } - }, - "@metamask/eth-json-rpc-infura>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true - } - }, - "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": { - "globals": { - "URL": true, - "btoa": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/eth-json-rpc-infura>@metamask/utils": true, - "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true, - "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, - "@metamask/safe-event-emitter": true, - "browserify>browser-resolve": true, - "eth-rpc-errors": true, - "json-rpc-engine": true, - "lavamoat>json-stable-stringify": true, - "vinyl>clone": true - } - }, "@metamask/eth-json-rpc-middleware": { "globals": { "URL": true, @@ -1048,12 +1004,6 @@ "superstruct": true } }, - "@metamask/eth-json-rpc-provider": { - "packages": { - "@metamask/safe-event-emitter": true, - "json-rpc-engine": true - } - }, "@metamask/eth-keyring-controller": { "packages": { "@metamask/browser-passworder": true, @@ -1659,6 +1609,100 @@ "browserify>url": true } }, + "@metamask/network-controller": { + "globals": { + "URL": true, + "btoa": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/eth-json-rpc-middleware": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura": true, + "@metamask/network-controller>@metamask/eth-json-rpc-provider": true, + "@metamask/network-controller>@metamask/swappable-obj-proxy": true, + "@metamask/network-controller>eth-block-tracker": true, + "@metamask/utils": true, + "browserify>assert": true, + "eth-query": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "uuid": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-infura": { + "globals": { + "setTimeout": true + }, + "packages": { + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "node-fetch": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": { + "globals": { + "URL": true, + "btoa": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true, + "@metamask/safe-event-emitter": true, + "browserify>browser-resolve": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "lavamoat>json-stable-stringify": true, + "vinyl>clone": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-provider": { + "packages": { + "@metamask/safe-event-emitter": true, + "json-rpc-engine": true + } + }, + "@metamask/network-controller>eth-block-tracker": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/network-controller>eth-block-tracker>@metamask/safe-event-emitter": true, + "@metamask/network-controller>eth-block-tracker>pify": true, + "@metamask/utils": true, + "eth-query>json-rpc-random-id": true + } + }, + "@metamask/network-controller>eth-block-tracker>@metamask/safe-event-emitter": { + "globals": { + "setTimeout": true + }, + "packages": { + "browserify>events": true + } + }, "@metamask/notification-controller": { "packages": { "@metamask/base-controller": true, @@ -3498,27 +3542,6 @@ "postMessage": true } }, - "eth-block-tracker": { - "globals": { - "clearTimeout": true, - "console.error": true, - "setTimeout": true - }, - "packages": { - "@metamask/utils": true, - "eth-block-tracker>@metamask/safe-event-emitter": true, - "eth-block-tracker>pify": true, - "eth-query>json-rpc-random-id": true - } - }, - "eth-block-tracker>@metamask/safe-event-emitter": { - "globals": { - "setTimeout": true - }, - "packages": { - "browserify>events": true - } - }, "eth-ens-namehash": { "globals": { "name": "write" diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index d470098a1..fafcfcc40 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -976,50 +976,6 @@ "define": true } }, - "@metamask/eth-json-rpc-infura": { - "globals": { - "setTimeout": true - }, - "packages": { - "@metamask/eth-json-rpc-infura>@metamask/utils": true, - "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true, - "eth-rpc-errors": true, - "json-rpc-engine": true, - "node-fetch": true - } - }, - "@metamask/eth-json-rpc-infura>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true - } - }, - "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": { - "globals": { - "URL": true, - "btoa": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/eth-json-rpc-infura>@metamask/utils": true, - "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true, - "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, - "@metamask/safe-event-emitter": true, - "browserify>browser-resolve": true, - "eth-rpc-errors": true, - "json-rpc-engine": true, - "lavamoat>json-stable-stringify": true, - "vinyl>clone": true - } - }, "@metamask/eth-json-rpc-middleware": { "globals": { "URL": true, @@ -1048,12 +1004,6 @@ "superstruct": true } }, - "@metamask/eth-json-rpc-provider": { - "packages": { - "@metamask/safe-event-emitter": true, - "json-rpc-engine": true - } - }, "@metamask/eth-keyring-controller": { "packages": { "@metamask/browser-passworder": true, @@ -1659,6 +1609,100 @@ "browserify>url": true } }, + "@metamask/network-controller": { + "globals": { + "URL": true, + "btoa": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/eth-json-rpc-middleware": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura": true, + "@metamask/network-controller>@metamask/eth-json-rpc-provider": true, + "@metamask/network-controller>@metamask/swappable-obj-proxy": true, + "@metamask/network-controller>eth-block-tracker": true, + "@metamask/utils": true, + "browserify>assert": true, + "eth-query": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "uuid": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-infura": { + "globals": { + "setTimeout": true + }, + "packages": { + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "node-fetch": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": { + "globals": { + "URL": true, + "btoa": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true, + "@metamask/safe-event-emitter": true, + "browserify>browser-resolve": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "lavamoat>json-stable-stringify": true, + "vinyl>clone": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-provider": { + "packages": { + "@metamask/safe-event-emitter": true, + "json-rpc-engine": true + } + }, + "@metamask/network-controller>eth-block-tracker": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/network-controller>eth-block-tracker>@metamask/safe-event-emitter": true, + "@metamask/network-controller>eth-block-tracker>pify": true, + "@metamask/utils": true, + "eth-query>json-rpc-random-id": true + } + }, + "@metamask/network-controller>eth-block-tracker>@metamask/safe-event-emitter": { + "globals": { + "setTimeout": true + }, + "packages": { + "browserify>events": true + } + }, "@metamask/notification-controller": { "packages": { "@metamask/base-controller": true, @@ -3498,27 +3542,6 @@ "postMessage": true } }, - "eth-block-tracker": { - "globals": { - "clearTimeout": true, - "console.error": true, - "setTimeout": true - }, - "packages": { - "@metamask/utils": true, - "eth-block-tracker>@metamask/safe-event-emitter": true, - "eth-block-tracker>pify": true, - "eth-query>json-rpc-random-id": true - } - }, - "eth-block-tracker>@metamask/safe-event-emitter": { - "globals": { - "setTimeout": true - }, - "packages": { - "browserify>events": true - } - }, "eth-ens-namehash": { "globals": { "name": "write" diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index 929c69e1b..494ce3924 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -905,50 +905,6 @@ "fetch": true } }, - "@metamask/eth-json-rpc-infura": { - "globals": { - "setTimeout": true - }, - "packages": { - "@metamask/eth-json-rpc-infura>@metamask/utils": true, - "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true, - "eth-rpc-errors": true, - "json-rpc-engine": true, - "node-fetch": true - } - }, - "@metamask/eth-json-rpc-infura>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true - } - }, - "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": { - "globals": { - "URL": true, - "btoa": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/eth-json-rpc-infura>@metamask/utils": true, - "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true, - "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, - "@metamask/safe-event-emitter": true, - "browserify>browser-resolve": true, - "eth-rpc-errors": true, - "json-rpc-engine": true, - "lavamoat>json-stable-stringify": true, - "vinyl>clone": true - } - }, "@metamask/eth-json-rpc-middleware": { "globals": { "URL": true, @@ -977,12 +933,6 @@ "superstruct": true } }, - "@metamask/eth-json-rpc-provider": { - "packages": { - "@metamask/safe-event-emitter": true, - "json-rpc-engine": true - } - }, "@metamask/eth-keyring-controller": { "packages": { "@metamask/browser-passworder": true, @@ -1588,6 +1538,100 @@ "browserify>url": true } }, + "@metamask/network-controller": { + "globals": { + "URL": true, + "btoa": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/eth-json-rpc-middleware": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura": true, + "@metamask/network-controller>@metamask/eth-json-rpc-provider": true, + "@metamask/network-controller>@metamask/swappable-obj-proxy": true, + "@metamask/network-controller>eth-block-tracker": true, + "@metamask/utils": true, + "browserify>assert": true, + "eth-query": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "uuid": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-infura": { + "globals": { + "setTimeout": true + }, + "packages": { + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "node-fetch": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": { + "globals": { + "URL": true, + "btoa": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true, + "@metamask/safe-event-emitter": true, + "browserify>browser-resolve": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "lavamoat>json-stable-stringify": true, + "vinyl>clone": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-provider": { + "packages": { + "@metamask/safe-event-emitter": true, + "json-rpc-engine": true + } + }, + "@metamask/network-controller>eth-block-tracker": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/network-controller>eth-block-tracker>@metamask/safe-event-emitter": true, + "@metamask/network-controller>eth-block-tracker>pify": true, + "@metamask/utils": true, + "eth-query>json-rpc-random-id": true + } + }, + "@metamask/network-controller>eth-block-tracker>@metamask/safe-event-emitter": { + "globals": { + "setTimeout": true + }, + "packages": { + "browserify>events": true + } + }, "@metamask/notification-controller>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2972,27 +3016,6 @@ "postMessage": true } }, - "eth-block-tracker": { - "globals": { - "clearTimeout": true, - "console.error": true, - "setTimeout": true - }, - "packages": { - "@metamask/utils": true, - "eth-block-tracker>@metamask/safe-event-emitter": true, - "eth-block-tracker>pify": true, - "eth-query>json-rpc-random-id": true - } - }, - "eth-block-tracker>@metamask/safe-event-emitter": { - "globals": { - "setTimeout": true - }, - "packages": { - "browserify>events": true - } - }, "eth-ens-namehash": { "globals": { "name": "write" diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index 22627efff..6f388ca84 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -1126,50 +1126,6 @@ "fetch": true } }, - "@metamask/eth-json-rpc-infura": { - "globals": { - "setTimeout": true - }, - "packages": { - "@metamask/eth-json-rpc-infura>@metamask/utils": true, - "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true, - "eth-rpc-errors": true, - "json-rpc-engine": true, - "node-fetch": true - } - }, - "@metamask/eth-json-rpc-infura>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true - } - }, - "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": { - "globals": { - "URL": true, - "btoa": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@metamask/eth-json-rpc-infura>@metamask/utils": true, - "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true, - "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, - "@metamask/safe-event-emitter": true, - "browserify>browser-resolve": true, - "eth-rpc-errors": true, - "json-rpc-engine": true, - "lavamoat>json-stable-stringify": true, - "vinyl>clone": true - } - }, "@metamask/eth-json-rpc-middleware": { "globals": { "URL": true, @@ -1198,12 +1154,6 @@ "superstruct": true } }, - "@metamask/eth-json-rpc-provider": { - "packages": { - "@metamask/safe-event-emitter": true, - "json-rpc-engine": true - } - }, "@metamask/eth-keyring-controller": { "packages": { "@metamask/browser-passworder": true, @@ -1809,6 +1759,100 @@ "browserify>url": true } }, + "@metamask/network-controller": { + "globals": { + "URL": true, + "btoa": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/eth-json-rpc-middleware": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura": true, + "@metamask/network-controller>@metamask/eth-json-rpc-provider": true, + "@metamask/network-controller>@metamask/swappable-obj-proxy": true, + "@metamask/network-controller>eth-block-tracker": true, + "@metamask/utils": true, + "browserify>assert": true, + "eth-query": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "uuid": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-infura": { + "globals": { + "setTimeout": true + }, + "packages": { + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "node-fetch": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": { + "globals": { + "URL": true, + "btoa": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true, + "@metamask/safe-event-emitter": true, + "browserify>browser-resolve": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "lavamoat>json-stable-stringify": true, + "vinyl>clone": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-provider": { + "packages": { + "@metamask/safe-event-emitter": true, + "json-rpc-engine": true + } + }, + "@metamask/network-controller>eth-block-tracker": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/network-controller>eth-block-tracker>@metamask/safe-event-emitter": true, + "@metamask/network-controller>eth-block-tracker>pify": true, + "@metamask/utils": true, + "eth-query>json-rpc-random-id": true + } + }, + "@metamask/network-controller>eth-block-tracker>@metamask/safe-event-emitter": { + "globals": { + "setTimeout": true + }, + "packages": { + "browserify>events": true + } + }, "@metamask/notification-controller>nanoid": { "globals": { "crypto.getRandomValues": true @@ -3193,27 +3237,6 @@ "postMessage": true } }, - "eth-block-tracker": { - "globals": { - "clearTimeout": true, - "console.error": true, - "setTimeout": true - }, - "packages": { - "@metamask/utils": true, - "eth-block-tracker>@metamask/safe-event-emitter": true, - "eth-block-tracker>pify": true, - "eth-query>json-rpc-random-id": true - } - }, - "eth-block-tracker>@metamask/safe-event-emitter": { - "globals": { - "setTimeout": true - }, - "packages": { - "browserify>events": true - } - }, "eth-ens-namehash": { "globals": { "name": "write" diff --git a/package.json b/package.json index 8ccf3b50c..f470e45d7 100644 --- a/package.json +++ b/package.json @@ -234,9 +234,7 @@ "@metamask/controller-utils": "^4.0.0", "@metamask/design-tokens": "^1.9.0", "@metamask/desktop": "^0.3.0", - "@metamask/eth-json-rpc-infura": "^8.1.0", "@metamask/eth-json-rpc-middleware": "^11.0.0", - "@metamask/eth-json-rpc-provider": "^1.0.0", "@metamask/eth-keyring-controller": "^10.0.1", "@metamask/eth-ledger-bridge-keyring": "^0.15.0", "@metamask/eth-token-tracker": "^4.0.0", @@ -248,6 +246,7 @@ "@metamask/logo": "^3.1.1", "@metamask/message-manager": "^7.0.0", "@metamask/metamask-eth-abis": "^3.0.0", + "@metamask/network-controller": "^10.1.0", "@metamask/notification-controller": "^3.0.0", "@metamask/obs-store": "^8.1.0", "@metamask/permission-controller": "^4.0.0", @@ -269,7 +268,6 @@ "@metamask/snaps-utils": "^0.32.2", "@metamask/snaps-utils-flask": "npm:@metamask/snaps-utils@0.34.0-flask.1", "@metamask/subject-metadata-controller": "^2.0.0", - "@metamask/swappable-obj-proxy": "^2.1.0", "@metamask/utils": "^5.0.0", "@ngraveio/bc-ur": "^1.1.6", "@popperjs/core": "^2.4.0", @@ -295,7 +293,6 @@ "debounce-stream": "^2.0.0", "deep-freeze-strict": "1.1.1", "end-of-stream": "^1.4.4", - "eth-block-tracker": "^7.0.0", "eth-ens-namehash": "^2.0.8", "eth-json-rpc-filters": "^6.0.0", "eth-lattice-keyring": "^0.12.4", @@ -374,7 +371,6 @@ "@babel/preset-typescript": "^7.16.7", "@babel/register": "^7.5.5", "@ethersproject/bignumber": "^5.7.0", - "@json-rpc-specification/meta-schema": "^1.0.6", "@lavamoat/allow-scripts": "^2.0.3", "@lavamoat/lavapack": "^5.0.0", "@metamask/auto-changelog": "^2.1.0", @@ -417,7 +413,6 @@ "@types/gulp-sass": "^5.0.0", "@types/gulp-sourcemaps": "^0.0.35", "@types/jest": "^29.1.2", - "@types/jest-when": "^3.5.2", "@types/madge": "^5.0.0", "@types/node": "^17.0.21", "@types/pify": "^5.0.1", @@ -496,7 +491,6 @@ "jest": "^29.1.2", "jest-canvas-mock": "^2.3.1", "jest-environment-jsdom": "^29.1.2", - "jest-when": "^3.5.2", "js-yaml": "^4.1.0", "jsdom": "^11.2.0", "junit-report-merger": "^4.0.0", diff --git a/yarn.lock b/yarn.lock index 2ac67adc8..7e19652e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3450,13 +3450,6 @@ __metadata: languageName: node linkType: hard -"@json-rpc-specification/meta-schema@npm:^1.0.6": - version: 1.0.6 - resolution: "@json-rpc-specification/meta-schema@npm:1.0.6" - checksum: 2eb9c6c6c73bb38350c7180d1ad3c5b8462406926cae753741895b457d7b1b9f0b74148daf3462bb167cef39efdd1d9090308edf4d4938956863acb643c146eb - languageName: node - linkType: hard - "@juggle/resize-observer@npm:^3.3.1": version: 3.4.0 resolution: "@juggle/resize-observer@npm:3.4.0" @@ -4142,7 +4135,7 @@ __metadata: languageName: node linkType: hard -"@metamask/eth-json-rpc-infura@npm:^8.0.0, @metamask/eth-json-rpc-infura@npm:^8.1.0": +"@metamask/eth-json-rpc-infura@npm:^8.0.0": version: 8.1.0 resolution: "@metamask/eth-json-rpc-infura@npm:8.1.0" dependencies: @@ -7740,15 +7733,6 @@ __metadata: languageName: node linkType: hard -"@types/jest-when@npm:^3.5.2": - version: 3.5.2 - resolution: "@types/jest-when@npm:3.5.2" - dependencies: - "@types/jest": "*" - checksum: 106230dd71ee266bbd7620ab339a7305054e56ba7638f0eff9f222e67a959df0ad68a7f0108156c50f7005881bae59cd5c38b3760c101d060cdb3dac9cd77ee2 - languageName: node - linkType: hard - "@types/jest@npm:*, @types/jest@npm:^29.1.2": version: 29.1.2 resolution: "@types/jest@npm:29.1.2" @@ -21963,15 +21947,6 @@ __metadata: languageName: node linkType: hard -"jest-when@npm:^3.5.2": - version: 3.5.2 - resolution: "jest-when@npm:3.5.2" - peerDependencies: - jest: ">= 25" - checksum: 9ad95552d377ef4d517c96a14c38bd8626d6856f518e7efc8a01d76a45a39d3c52281392c98da781c302114c78cd1ea17556c7f638d49d15971fcce9d58306e8 - languageName: node - linkType: hard - "jest-worker@npm:^27.4.5": version: 27.5.1 resolution: "jest-worker@npm:27.5.1" @@ -24309,7 +24284,6 @@ __metadata: "@ethersproject/providers": ^5.7.2 "@formatjs/intl-relativetimeformat": ^5.2.6 "@fortawesome/fontawesome-free": ^5.13.0 - "@json-rpc-specification/meta-schema": ^1.0.6 "@keystonehq/bc-ur-registry-eth": ^0.19.1 "@keystonehq/metamask-airgapped-keyring": ^0.13.1 "@lavamoat/allow-scripts": ^2.0.3 @@ -24340,9 +24314,7 @@ __metadata: "@metamask/eslint-config-mocha": ^9.0.0 "@metamask/eslint-config-nodejs": ^9.0.0 "@metamask/eslint-config-typescript": ^9.0.1 - "@metamask/eth-json-rpc-infura": ^8.1.0 "@metamask/eth-json-rpc-middleware": ^11.0.0 - "@metamask/eth-json-rpc-provider": ^1.0.0 "@metamask/eth-keyring-controller": ^10.0.1 "@metamask/eth-ledger-bridge-keyring": ^0.15.0 "@metamask/eth-token-tracker": ^4.0.0 @@ -24355,6 +24327,7 @@ __metadata: "@metamask/logo": ^3.1.1 "@metamask/message-manager": ^7.0.0 "@metamask/metamask-eth-abis": ^3.0.0 + "@metamask/network-controller": ^10.1.0 "@metamask/notification-controller": ^3.0.0 "@metamask/obs-store": ^8.1.0 "@metamask/permission-controller": ^4.0.0 @@ -24377,7 +24350,6 @@ __metadata: "@metamask/snaps-utils": ^0.32.2 "@metamask/snaps-utils-flask": "npm:@metamask/snaps-utils@0.34.0-flask.1" "@metamask/subject-metadata-controller": ^2.0.0 - "@metamask/swappable-obj-proxy": ^2.1.0 "@metamask/test-dapp": ^7.0.0 "@metamask/utils": ^5.0.0 "@ngraveio/bc-ur": ^1.1.6 @@ -24421,7 +24393,6 @@ __metadata: "@types/gulp-sass": ^5.0.0 "@types/gulp-sourcemaps": ^0.0.35 "@types/jest": ^29.1.2 - "@types/jest-when": ^3.5.2 "@types/madge": ^5.0.0 "@types/node": ^17.0.21 "@types/pify": ^5.0.1 @@ -24486,7 +24457,6 @@ __metadata: eslint-plugin-react: ^7.23.1 eslint-plugin-react-hooks: ^4.2.0 eslint-plugin-storybook: ^0.6.12 - eth-block-tracker: ^7.0.0 eth-ens-namehash: ^2.0.8 eth-json-rpc-filters: ^6.0.0 eth-lattice-keyring: ^0.12.4 @@ -24537,7 +24507,6 @@ __metadata: jest-canvas-mock: ^2.3.1 jest-environment-jsdom: ^29.1.2 jest-junit: ^14.0.1 - jest-when: ^3.5.2 js-yaml: ^4.1.0 jsdom: ^11.2.0 json-rpc-engine: ^6.1.0