diff --git a/app/scripts/controllers/network/network-controller.test.ts b/app/scripts/controllers/network/network-controller.test.ts index fd8e04bc1..9a8a2f933 100644 --- a/app/scripts/controllers/network/network-controller.test.ts +++ b/app/scripts/controllers/network/network-controller.test.ts @@ -992,99 +992,38 @@ describe('NetworkController', () => { }); describe('lookupNetwork', () => { - describe('if the provider has not been initialized', () => { - it('does not update state in any way', async () => { - const providerConfig = { - type: NETWORK_TYPES.RPC, - rpcUrl: 'http://example-custom-rpc.metamask.io', - chainId: '0x9999' as const, - nickname: 'Test initial state', - }; - const initialState = { - providerConfig, - networkDetails: { - EIPS: { - 1559: true, - }, - }, - }; - - await withController( - { - state: initialState, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - const stateAfterConstruction = controller.store.getState(); - - await controller.lookupNetwork(); - - expect(controller.store.getState()).toStrictEqual( - stateAfterConstruction, - ); - }, - ); - }); - - it('does not emit infuraIsUnblocked', async () => { - await withController(async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - const promiseForNoInfuraIsUnblockedEvents = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - count: 0, - operation: async () => { - await controller.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'], }); - expect(await promiseForNoInfuraIsUnblockedEvents).toBeTruthy(); - }); - }); + await controller.lookupNetwork(); - it('does not emit infuraIsBlocked', async () => { - await withController(async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - const promiseForNoInfuraIsBlockedEvents = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - count: 0, - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(await promiseForNoInfuraIsBlockedEvents).toBeTruthy(); + await expect(promiseForNetworkChanges).toNeverResolve(); }); }); }); - INFURA_NETWORKS.forEach(({ networkType }) => { - describe(`when the type in the provider configuration is "${networkType}"`, () => { - describe('if the network was switched after the eth_getBlockByNumber request started but before it completed', () => { + [ + NETWORK_TYPES.MAINNET, + NETWORK_TYPES.GOERLI, + NETWORK_TYPES.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: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', - }, + providerConfig: buildProviderConfig({ type: networkType }), networkConfigurations: { testNetworkConfigurationId: { id: 'testNetworkConfigurationId', rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', + chainId: toHex(1337), ticker: 'ABC', }, }, @@ -1094,13 +1033,12 @@ describe('NetworkController', () => { async ({ controller }) => { const fakeProviders = [ buildFakeProvider([ + // Called during provider initialization { request: { method: 'net_version', }, - response: { - result: '1', - }, + response: SUCCESSFUL_NET_VERSION_RESPONSE, }, { request: { @@ -1108,27 +1046,29 @@ describe('NetworkController', () => { }, 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 () => { - await controller.setActiveNetwork( + 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', @@ -1149,19 +1089,12 @@ describe('NetworkController', () => { }) .mockReturnValue(fakeNetworkClients[0]) .calledWith({ - chainId: '0x1337', + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[1]); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.initializeProvider(); - }, - }); + await controller.initializeProvider(); expect(controller.store.getState().networkStatus).toBe( 'available', ); @@ -1173,6 +1106,7 @@ describe('NetworkController', () => { await controller.lookupNetwork(); }, }); + expect(controller.store.getState().networkStatus).toBe( 'unknown', ); @@ -1184,17 +1118,12 @@ describe('NetworkController', () => { await withController( { state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', - }, + providerConfig: buildProviderConfig({ type: networkType }), networkConfigurations: { testNetworkConfigurationId: { id: 'testNetworkConfigurationId', rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', + chainId: toHex(1337), ticker: 'ABC', }, }, @@ -1204,12 +1133,13 @@ describe('NetworkController', () => { async ({ controller }) => { const fakeProviders = [ buildFakeProvider([ + // Called during provider initialization { request: { method: 'net_version', }, response: { - result: '111', + result: '1', }, }, { @@ -1217,20 +1147,38 @@ describe('NetworkController', () => { method: 'eth_getBlockByNumber', }, response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - beforeCompleting: async () => { - await controller.setActiveNetwork( - 'testNetworkConfigurationId', - ); - }, }, - ]), - buildFakeProvider([ + // Called via `lookupNetwork` directly { request: { method: 'net_version', }, response: { - result: '222', + 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', }, }, ]), @@ -1247,17 +1195,19 @@ describe('NetworkController', () => { }) .mockReturnValue(fakeNetworkClients[0]) .calledWith({ - chainId: '0x1337', + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[1]); - await withoutCallingLookupNetwork({ + await waitForStateChanges({ controller, + propertyPath: ['networkId'], operation: async () => { await controller.initializeProvider(); }, }); + expect(controller.store.getState().networkId).toBe('1'); await waitForStateChanges({ controller, @@ -1267,7 +1217,7 @@ describe('NetworkController', () => { }, }); - expect(controller.store.getState().networkId).toBe('222'); + expect(controller.store.getState().networkId).toBe('2'); }, ); }); @@ -1276,17 +1226,12 @@ describe('NetworkController', () => { await withController( { state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', - }, + providerConfig: buildProviderConfig({ type: networkType }), networkConfigurations: { testNetworkConfigurationId: { id: 'testNetworkConfigurationId', rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', + chainId: toHex(1337), ticker: 'ABC', }, }, @@ -1296,6 +1241,15 @@ describe('NetworkController', () => { async ({ controller }) => { const fakeProviders = [ buildFakeProvider([ + // Called during provider initialization + { + request: { + method: 'net_version', + }, + response: { + result: '1', + }, + }, { request: { method: 'eth_getBlockByNumber', @@ -1303,14 +1257,42 @@ describe('NetworkController', () => { response: { result: POST_1559_BLOCK, }, - beforeCompleting: async () => { - await controller.setActiveNetwork( + }, + // 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', @@ -1333,15 +1315,17 @@ describe('NetworkController', () => { }) .mockReturnValue(fakeNetworkClients[0]) .calledWith({ - chainId: '0x1337', + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[1]); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); + await controller.initializeProvider(); + expect( + controller.store.getState().networkDetails, + ).toStrictEqual({ + EIPS: { + 1559: true, }, }); @@ -1364,25 +1348,18 @@ describe('NetworkController', () => { ); }); - it('emits infuraIsBlocked, not infuraIsUnblocked, if the second network is blocked, even if the first one is not', async () => { - const anotherNetwork = INFURA_NETWORKS.find( - (network) => network.networkType !== networkType, - ); - /* eslint-disable-next-line jest/no-if */ - if (!anotherNetwork) { - throw new Error( - "Could not find another network to use. You've probably commented out all INFURA_NETWORKS but one. Please uncomment another one.", - ); - } - + it('emits infuraIsUnblocked, not infuraIsBlocked, assuming that the first network was blocked', async () => { await withController( { state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID - // of the network selected, it just needs to exist - chainId: '0x9999999', + providerConfig: buildProviderConfig({ type: networkType }), + networkConfigurations: { + testNetworkConfigurationId: { + id: 'testNetworkConfigurationId', + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + ticker: 'ABC', + }, }, }, infuraProjectId: 'some-infura-project-id', @@ -1390,19 +1367,33 @@ describe('NetworkController', () => { 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, - beforeCompleting: async () => { - await controller.setProviderType( - anotherNetwork.networkType, + }, + // 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', ); }, }, - ]), - buildFakeProvider([ { request: { method: 'eth_getBlockByNumber', @@ -1410,6 +1401,21 @@ describe('NetworkController', () => { 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]), @@ -1421,34 +1427,485 @@ describe('NetworkController', () => { infuraProjectId: 'some-infura-project-id', type: NetworkClientType.Infura, }) - .mockReturnValueOnce(fakeNetworkClients[0]) + .mockReturnValue(fakeNetworkClients[0]) .calledWith({ - network: anotherNetwork.networkType, + 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, }) - .mockReturnValueOnce(fakeNetworkClients[1]); - await withoutCallingLookupNetwork({ + .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(); }, }); - const promiseForNoInfuraIsUnblockedEvents = + 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, }); - const promiseForInfuraIsBlocked = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', + + await waitForStateChanges({ + controller, + propertyPath: ['networkStatus'], + operation: async () => { + await controller.lookupNetwork(); + }, }); - await controller.lookupNetwork(); - - expect(await promiseForNoInfuraIsUnblockedEvents).toBeTruthy(); - expect(await promiseForInfuraIsBlocked).toBeTruthy(); + await expect(promiseForInfuraIsUnblockedEvents).toBeFulfilled(); + await expect(promiseForNoInfuraIsBlockedEvents).toBeFulfilled(); }, ); }); @@ -1466,23 +1923,24 @@ describe('NetworkController', () => { }); }); - describe('when the type in the provider configuration is "rpc"', () => { + 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: { - type: 'rpc', + providerConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, + }), }, infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { const fakeProviders = [ buildFakeProvider([ + // Called during provider initialization { request: { method: 'net_version', @@ -1495,13 +1953,16 @@ describe('NetworkController', () => { }, response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, }, + // Called via `lookupNetwork` directly { request: { method: 'net_version', }, response: SUCCESSFUL_NET_VERSION_RESPONSE, - beforeCompleting: async () => { - await controller.setProviderType('goerli'); + beforeCompleting: () => { + // Intentionally not awaited because don't want this to + // block the `net_version` request + controller.setProviderType(NETWORK_TYPES.GOERLI); }, }, { @@ -1512,9 +1973,10 @@ describe('NetworkController', () => { }, ]), buildFakeProvider([ + // Called when switching networks { request: { - method: 'eth_getBlockByNumber', + method: 'net_version', }, error: GENERIC_JSON_RPC_ERROR, }, @@ -1526,24 +1988,18 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ - chainId: '0x1337', + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', type: NetworkClientType.Custom, }) - .mockReturnValueOnce(fakeNetworkClients[0]) + .mockReturnValue(fakeNetworkClients[0]) .calledWith({ network: NETWORK_TYPES.GOERLI, infuraProjectId: 'some-infura-project-id', type: NetworkClientType.Infura, }) - .mockReturnValueOnce(fakeNetworkClients[1]); - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.initializeProvider(); - }, - }); + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); expect(controller.store.getState().networkStatus).toBe( 'available', ); @@ -1555,6 +2011,7 @@ describe('NetworkController', () => { await controller.lookupNetwork(); }, }); + expect(controller.store.getState().networkStatus).toBe('unknown'); }, ); @@ -1564,18 +2021,18 @@ describe('NetworkController', () => { await withController( { state: { - providerConfig: { - type: 'rpc', + providerConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'RPC', - }, + }), }, infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { const fakeProviders = [ buildFakeProvider([ + // Called during provider initialization { request: { method: 'net_version', @@ -1590,6 +2047,7 @@ describe('NetworkController', () => { }, response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, }, + // Called via `lookupNetwork` directly { request: { method: 'net_version', @@ -1598,7 +2056,9 @@ describe('NetworkController', () => { result: '1', }, beforeCompleting: async () => { - await controller.setProviderType('goerli'); + // Intentionally not awaited because don't want this to + // block the `net_version` request + controller.setProviderType(NETWORK_TYPES.GOERLI); }, }, { @@ -1609,6 +2069,7 @@ describe('NetworkController', () => { }, ]), buildFakeProvider([ + // Called when switching networks { request: { method: 'net_version', @@ -1617,12 +2078,6 @@ describe('NetworkController', () => { result: '2', }, }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, ]), ]; const fakeNetworkClients = [ @@ -1631,17 +2086,17 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ - chainId: '0x1337', + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', type: NetworkClientType.Custom, }) - .mockReturnValueOnce(fakeNetworkClients[0]) + .mockReturnValue(fakeNetworkClients[0]) .calledWith({ network: NETWORK_TYPES.GOERLI, infuraProjectId: 'some-infura-project-id', type: NetworkClientType.Infura, }) - .mockReturnValueOnce(fakeNetworkClients[1]); + .mockReturnValue(fakeNetworkClients[1]); await waitForStateChanges({ controller, propertyPath: ['networkId'], @@ -1668,27 +2123,25 @@ describe('NetworkController', () => { await withController( { state: { - providerConfig: { - type: 'rpc', + providerConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'RPC', - }, - networkDetails: { - EIPS: {}, - other: 'details', - }, + }), }, infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { const fakeProviders = [ buildFakeProvider([ + // Called during provider initialization { request: { method: 'net_version', }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, + response: { + result: '1', + }, }, { request: { @@ -1698,13 +2151,18 @@ describe('NetworkController', () => { result: POST_1559_BLOCK, }, }, + // Called via `lookupNetwork` directly { request: { method: 'net_version', }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - beforeCompleting: async () => { - await controller.setProviderType('goerli'); + response: { + result: '1', + }, + beforeCompleting: () => { + // Intentionally not awaited because don't want this to + // block the `net_version` request + controller.setProviderType(NETWORK_TYPES.GOERLI); }, }, { @@ -1717,11 +2175,14 @@ describe('NetworkController', () => { }, ]), buildFakeProvider([ + // Called when switching networks { request: { method: 'net_version', }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, + response: { + result: '2', + }, }, { request: { @@ -1739,37 +2200,27 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ - chainId: '0x1337', + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', type: NetworkClientType.Custom, }) - .mockReturnValueOnce(fakeNetworkClients[0]) + .mockReturnValue(fakeNetworkClients[0]) .calledWith({ network: NETWORK_TYPES.GOERLI, infuraProjectId: 'some-infura-project-id', type: NetworkClientType.Infura, }) - .mockReturnValueOnce(fakeNetworkClients[1]); - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation: async () => { - await controller.initializeProvider(); - }, - }); + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); expect(controller.store.getState().networkDetails).toStrictEqual({ EIPS: { 1559: true, }, - other: 'details', }); await waitForStateChanges({ controller, propertyPath: ['networkDetails'], - // setProviderType clears networkDetails first, and then updates - // it to what we expect it to be - count: 2, operation: async () => { await controller.lookupNetwork(); }, @@ -1784,29 +2235,44 @@ describe('NetworkController', () => { ); }); - it('emits infuraIsBlocked, not infuraIsUnblocked, if the second network is blocked, even if the first one is not', async () => { + it('emits infuraIsBlocked, not infuraIsUnblocked, if the second network was blocked and the first network was not', async () => { await withController( { state: { - providerConfig: { - type: 'rpc', + providerConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'RPC', - }, + }), }, infuraProjectId: 'some-infura-project-id', }, async ({ controller, messenger }) => { const fakeProviders = [ buildFakeProvider([ + // Called during provider initialization { request: { method: 'net_version', }, response: SUCCESSFUL_NET_VERSION_RESPONSE, - beforeCompleting: async () => { - await controller.setProviderType('goerli'); + }, + { + 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(NETWORK_TYPES.GOERLI); }, }, { @@ -1817,6 +2283,7 @@ describe('NetworkController', () => { }, ]), buildFakeProvider([ + // Called when switching networks { request: { method: 'net_version', @@ -1837,38 +2304,39 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ - chainId: '0x1337', + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', type: NetworkClientType.Custom, }) - .mockReturnValueOnce(fakeNetworkClients[0]) + .mockReturnValue(fakeNetworkClients[0]) .calledWith({ network: NETWORK_TYPES.GOERLI, infuraProjectId: 'some-infura-project-id', type: NetworkClientType.Infura, }) - .mockReturnValueOnce(fakeNetworkClients[1]); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); const promiseForNoInfuraIsUnblockedEvents = waitForPublishedEvents({ messenger, eventType: 'NetworkController:infuraIsUnblocked', count: 0, }); - const promiseForInfuraIsBlocked = waitForPublishedEvents({ + const promiseForInfuraIsBlockedEvents = waitForPublishedEvents({ messenger, eventType: 'NetworkController:infuraIsBlocked', }); - await controller.lookupNetwork(); + await waitForStateChanges({ + controller, + propertyPath: ['networkStatus'], + operation: async () => { + await controller.lookupNetwork(); + }, + }); - expect(await promiseForNoInfuraIsUnblockedEvents).toBeTruthy(); - expect(await promiseForInfuraIsBlocked).toBeTruthy(); + await expect(promiseForNoInfuraIsUnblockedEvents).toBeFulfilled(); + await expect(promiseForInfuraIsBlockedEvents).toBeFulfilled(); }, ); }); @@ -1879,21 +2347,18 @@ describe('NetworkController', () => { await withController( { state: { - providerConfig: { - type: 'rpc', + providerConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'RPC', - }, - networkDetails: { - EIPS: {}, - }, + }), }, infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { const fakeProviders = [ buildFakeProvider([ + // Called during provider initialization { request: { method: 'net_version', @@ -1906,6 +2371,7 @@ describe('NetworkController', () => { }, response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, }, + // Called via `lookupNetwork` directly { request: { method: 'net_version', @@ -1917,15 +2383,18 @@ describe('NetworkController', () => { method: 'eth_getBlockByNumber', }, response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - beforeCompleting: async () => { - await controller.setProviderType('goerli'); + beforeCompleting: () => { + // Intentionally not awaited because don't want this to + // block the `net_version` request + controller.setProviderType(NETWORK_TYPES.GOERLI); }, }, ]), buildFakeProvider([ + // Called when switching networks { request: { - method: 'eth_getBlockByNumber', + method: 'net_version', }, error: GENERIC_JSON_RPC_ERROR, }, @@ -1937,24 +2406,18 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ - chainId: '0x1337', + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', type: NetworkClientType.Custom, }) - .mockReturnValueOnce(fakeNetworkClients[0]) + .mockReturnValue(fakeNetworkClients[0]) .calledWith({ network: NETWORK_TYPES.GOERLI, infuraProjectId: 'some-infura-project-id', type: NetworkClientType.Infura, }) - .mockReturnValueOnce(fakeNetworkClients[1]); - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.initializeProvider(); - }, - }); + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); expect(controller.store.getState().networkStatus).toBe( 'available', ); @@ -1966,27 +2429,28 @@ describe('NetworkController', () => { await controller.lookupNetwork(); }, }); + expect(controller.store.getState().networkStatus).toBe('unknown'); }, ); }); - it('stores the network ID of the second network, not the first', async () => { + it('stores the ID of the second network, not the first', async () => { await withController( { state: { - providerConfig: { - type: 'rpc', + providerConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'RPC', - }, + }), }, infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { const fakeProviders = [ buildFakeProvider([ + // Called during provider initialization { request: { method: 'net_version', @@ -2001,6 +2465,7 @@ describe('NetworkController', () => { }, response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, }, + // Called via `lookupNetwork` directly { request: { method: 'net_version', @@ -2015,11 +2480,14 @@ describe('NetworkController', () => { }, response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, beforeCompleting: async () => { - await controller.setProviderType('goerli'); + // Intentionally not awaited because don't want this to + // block the `net_version` request + controller.setProviderType(NETWORK_TYPES.GOERLI); }, }, ]), buildFakeProvider([ + // Called when switching networks { request: { method: 'net_version', @@ -2028,12 +2496,6 @@ describe('NetworkController', () => { result: '2', }, }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, ]), ]; const fakeNetworkClients = [ @@ -2042,17 +2504,17 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ - chainId: '0x1337', + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', type: NetworkClientType.Custom, }) - .mockReturnValueOnce(fakeNetworkClients[0]) + .mockReturnValue(fakeNetworkClients[0]) .calledWith({ network: NETWORK_TYPES.GOERLI, infuraProjectId: 'some-infura-project-id', type: NetworkClientType.Infura, }) - .mockReturnValueOnce(fakeNetworkClients[1]); + .mockReturnValue(fakeNetworkClients[1]); await waitForStateChanges({ controller, propertyPath: ['networkId'], @@ -2069,6 +2531,7 @@ describe('NetworkController', () => { await controller.lookupNetwork(); }, }); + expect(controller.store.getState().networkId).toBe('2'); }, ); @@ -2078,27 +2541,25 @@ describe('NetworkController', () => { await withController( { state: { - providerConfig: { - type: 'rpc', + providerConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'RPC', - }, - networkDetails: { - EIPS: {}, - other: 'details', - }, + }), }, infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { const fakeProviders = [ buildFakeProvider([ + // Called during provider initialization { request: { method: 'net_version', }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, + response: { + result: '1', + }, }, { request: { @@ -2108,13 +2569,13 @@ describe('NetworkController', () => { result: POST_1559_BLOCK, }, }, + // Called via `lookupNetwork` directly { request: { method: 'net_version', }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - beforeCompleting: async () => { - await controller.setProviderType('goerli'); + response: { + result: '1', }, }, { @@ -2124,14 +2585,22 @@ describe('NetworkController', () => { response: { result: POST_1559_BLOCK, }, + beforeCompleting: () => { + // Intentionally not awaited because don't want this to + // block the `net_version` request + controller.setProviderType(NETWORK_TYPES.GOERLI); + }, }, ]), buildFakeProvider([ + // Called when switching networks { request: { method: 'net_version', }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, + response: { + result: '2', + }, }, { request: { @@ -2149,37 +2618,27 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ - chainId: '0x1337', + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', type: NetworkClientType.Custom, }) - .mockReturnValueOnce(fakeNetworkClients[0]) + .mockReturnValue(fakeNetworkClients[0]) .calledWith({ network: NETWORK_TYPES.GOERLI, infuraProjectId: 'some-infura-project-id', type: NetworkClientType.Infura, }) - .mockReturnValueOnce(fakeNetworkClients[1]); - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation: async () => { - await controller.initializeProvider(); - }, - }); + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); expect(controller.store.getState().networkDetails).toStrictEqual({ EIPS: { 1559: true, }, - other: 'details', }); await waitForStateChanges({ controller, propertyPath: ['networkDetails'], - // setProviderType clears networkDetails first, and then updates - // it to what we expect it to be - count: 2, operation: async () => { await controller.lookupNetwork(); }, @@ -2194,30 +2653,27 @@ describe('NetworkController', () => { ); }); - it('emits infuraIsBlocked, not infuraIsUnblocked, if the second network is blocked, even if the first one is not', async () => { + it('emits infuraIsBlocked, not infuraIsUnblocked, if the second network was blocked and the first network was not', async () => { await withController( { state: { - providerConfig: { - type: 'rpc', + providerConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'RPC', - }, + }), }, infuraProjectId: 'some-infura-project-id', }, async ({ controller, messenger }) => { const fakeProviders = [ buildFakeProvider([ + // Called during provider initialization { request: { method: 'net_version', }, response: SUCCESSFUL_NET_VERSION_RESPONSE, - beforeCompleting: async () => { - await controller.setProviderType('goerli'); - }, }, { request: { @@ -2225,8 +2681,27 @@ describe('NetworkController', () => { }, 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(NETWORK_TYPES.GOERLI); + }, + }, ]), buildFakeProvider([ + // Called when switching networks { request: { method: 'net_version', @@ -2247,38 +2722,39 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ - chainId: '0x1337', + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', type: NetworkClientType.Custom, }) - .mockReturnValueOnce(fakeNetworkClients[0]) + .mockReturnValue(fakeNetworkClients[0]) .calledWith({ network: NETWORK_TYPES.GOERLI, infuraProjectId: 'some-infura-project-id', type: NetworkClientType.Infura, }) - .mockReturnValueOnce(fakeNetworkClients[1]); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); const promiseForNoInfuraIsUnblockedEvents = waitForPublishedEvents({ messenger, eventType: 'NetworkController:infuraIsUnblocked', count: 0, }); - const promiseForInfuraIsBlocked = waitForPublishedEvents({ + const promiseForInfuraIsBlockedEvents = waitForPublishedEvents({ messenger, eventType: 'NetworkController:infuraIsBlocked', }); - await controller.lookupNetwork(); + await waitForStateChanges({ + controller, + propertyPath: ['networkStatus'], + operation: async () => { + await controller.lookupNetwork(); + }, + }); - expect(await promiseForNoInfuraIsUnblockedEvents).toBeTruthy(); - expect(await promiseForInfuraIsBlocked).toBeTruthy(); + await expect(promiseForNoInfuraIsUnblockedEvents).toBeFulfilled(); + await expect(promiseForInfuraIsBlockedEvents).toBeFulfilled(); }, ); }); @@ -6503,33 +6979,6 @@ async function setFakeProvider( } } -/** - * For each kind of way that the provider can be set, `lookupNetwork` is always - * called. This can cause difficulty when testing the behavior of - * `lookupNetwork` itself, as extra requests then have to be mocked. - * This function takes a function that presumably sets the provider, - * stubbing `lookupNetwork` before the function and releasing the stub - * afterward. - * - * @param args - The arguments. - * @param args.controller - The network controller. - * @param args.operation - The function that presumably involves - * `lookupNetwork`. - */ -async function withoutCallingLookupNetwork({ - controller, - operation, -}: { - controller: NetworkController; - operation: () => void | Promise; -}) { - const spy = jest - .spyOn(controller, 'lookupNetwork') - .mockResolvedValue(undefined); - await operation(); - spy.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