From 48d4797b1916e8c245c015a3c8cda0074b1cdf8c Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 16 Mar 2023 17:26:01 -0500 Subject: [PATCH 1/8] Migrate network configurations from PreferencesController to NetworkController --- .../src/AssetsContractController.test.ts | 1 + .../src/TokenBalancesController.test.ts | 1 + .../src/TokenListController.test.ts | 4 +- .../src/TokenRatesController.test.ts | 2 +- packages/controller-utils/src/constants.ts | 1 + packages/controller-utils/src/util.test.ts | 7 + packages/controller-utils/src/util.ts | 16 +- .../src/GasFeeController.test.ts | 1 + packages/network-controller/package.json | 2 + .../src/NetworkController.ts | 242 ++++- .../tests/NetworkController.test.ts | 943 +++++++++--------- .../tests/provider-api-tests/helpers.ts | 18 +- .../tests/provider-api-tests/shared-tests.ts | 2 +- .../rpc-methods/requestPermissions.test.ts | 2 + .../preferences-controller/jest.config.js | 6 +- .../src/PreferencesController.test.ts | 51 - .../src/PreferencesController.ts | 79 -- .../src/TransactionController.test.ts | 10 +- yarn.lock | 144 ++- 19 files changed, 861 insertions(+), 671 deletions(-) diff --git a/packages/assets-controllers/src/AssetsContractController.test.ts b/packages/assets-controllers/src/AssetsContractController.test.ts index 86e9c24d41..01f3e017d6 100644 --- a/packages/assets-controllers/src/AssetsContractController.test.ts +++ b/packages/assets-controllers/src/AssetsContractController.test.ts @@ -35,6 +35,7 @@ const setupControllers = () => { }); const network = new NetworkController({ messenger, + trackMetaMetricsEvent: jest.fn(), }); const preferences = new PreferencesController(); const assetsContract = new AssetsContractController({ diff --git a/packages/assets-controllers/src/TokenBalancesController.test.ts b/packages/assets-controllers/src/TokenBalancesController.test.ts index 054608c112..bc1e0dc2ad 100644 --- a/packages/assets-controllers/src/TokenBalancesController.test.ts +++ b/packages/assets-controllers/src/TokenBalancesController.test.ts @@ -130,6 +130,7 @@ describe('TokenBalancesController', () => { new NetworkController({ messenger, infuraProjectId: 'potato', + trackMetaMetricsEvent: jest.fn(), }); const preferences = new PreferencesController(); return { messenger, preferences }; diff --git a/packages/assets-controllers/src/TokenListController.test.ts b/packages/assets-controllers/src/TokenListController.test.ts index f8fa20aa0d..248d6ea9fa 100644 --- a/packages/assets-controllers/src/TokenListController.test.ts +++ b/packages/assets-controllers/src/TokenListController.test.ts @@ -1036,7 +1036,7 @@ describe('TokenListController', () => { controllerMessenger.publish('NetworkController:providerConfigChange', { type: 'rpc', chainId: '56', - rpcTarget: 'http://localhost:8545', + rpcUrl: 'http://localhost:8545', }); await new Promise((resolve) => setTimeout(() => resolve(), 500)); @@ -1138,7 +1138,7 @@ describe('TokenListController', () => { controllerMessenger.publish('NetworkController:providerConfigChange', { type: 'rpc', chainId: '56', - rpcTarget: 'http://localhost:8545', + rpcUrl: 'http://localhost:8545', }); }); }); diff --git a/packages/assets-controllers/src/TokenRatesController.test.ts b/packages/assets-controllers/src/TokenRatesController.test.ts index 577efc9f5e..e24db98d9a 100644 --- a/packages/assets-controllers/src/TokenRatesController.test.ts +++ b/packages/assets-controllers/src/TokenRatesController.test.ts @@ -213,7 +213,7 @@ describe('TokenRatesController', () => { }); it('should update all rates', async () => { - new NetworkController({ messenger }); + new NetworkController({ messenger, trackMetaMetricsEvent: jest.fn() }); const preferences = new PreferencesController(); const tokensController = new TokensController({ onPreferencesStateChange: (listener) => preferences.subscribe(listener), diff --git a/packages/controller-utils/src/constants.ts b/packages/controller-utils/src/constants.ts index a104bf75b5..90df5981f3 100644 --- a/packages/controller-utils/src/constants.ts +++ b/packages/controller-utils/src/constants.ts @@ -7,6 +7,7 @@ export const IPFS_DEFAULT_GATEWAY_URL = 'https://cloudflare-ipfs.com/ipfs/'; // NETWORKS ID export const GANACHE_CHAIN_ID = '1337'; +export const MAX_SAFE_CHAIN_ID = 4503599627370476; // TOKEN STANDARDS export const ERC721 = 'ERC721'; diff --git a/packages/controller-utils/src/util.test.ts b/packages/controller-utils/src/util.test.ts index 540d3868e1..62edbfd43f 100644 --- a/packages/controller-utils/src/util.test.ts +++ b/packages/controller-utils/src/util.test.ts @@ -1,6 +1,7 @@ import { BN } from 'ethereumjs-util'; import nock from 'nock'; import * as util from './util'; +import { MAX_SAFE_CHAIN_ID } from './constants'; const VALID = '4e1fF7229BDdAf0A73DF183a88d9c3a04cc975e0'; const SOME_API = 'https://someapi.com'; @@ -11,6 +12,12 @@ describe('util', () => { nock.cleanAll(); }); + it('isSafeChainId', () => { + expect(util.isSafeChainId(MAX_SAFE_CHAIN_ID + 1)).toBe(false); + expect(util.isSafeChainId(MAX_SAFE_CHAIN_ID)).toBe(true); + expect(util.isSafeChainId(-1)).toBe(false); + }); + it('bNToHex', () => { expect(util.BNToHex(new BN('1337'))).toBe('0x539'); }); diff --git a/packages/controller-utils/src/util.ts b/packages/controller-utils/src/util.ts index d16fbf8adb..a3d7f16b3a 100644 --- a/packages/controller-utils/src/util.ts +++ b/packages/controller-utils/src/util.ts @@ -10,9 +10,23 @@ import { fromWei, toWei } from 'ethjs-unit'; import ensNamehash from 'eth-ens-namehash'; import deepEqual from 'fast-deep-equal'; import type { Json } from './types'; +import { MAX_SAFE_CHAIN_ID } from './constants'; const TIMEOUT_ERROR = new Error('timeout'); +/** + * Checks whether the given number primitive chain ID is safe. + * Because some cryptographic libraries we use expect the chain ID to be a + * number primitive, it must not exceed a certain size. + * + * @param chainId - The chain ID to check for safety. + * @returns Whether the given chain ID is safe. + */ +export function isSafeChainId(chainId: number): boolean { + return ( + Number.isSafeInteger(chainId) && chainId > 0 && chainId <= MAX_SAFE_CHAIN_ID + ); +} /** * Converts a BN object to a hex string with a '0x' prefix. * @@ -120,7 +134,7 @@ export function getBuyURL( * @returns A BN instance. */ export function hexToBN(inputHex: string) { - return new BN(stripHexPrefix(inputHex), 16); + return new BN(inputHex ? stripHexPrefix(inputHex) : inputHex, 16); } /** diff --git a/packages/gas-fee-controller/src/GasFeeController.test.ts b/packages/gas-fee-controller/src/GasFeeController.test.ts index 26bff8cd36..a569fadb5f 100644 --- a/packages/gas-fee-controller/src/GasFeeController.test.ts +++ b/packages/gas-fee-controller/src/GasFeeController.test.ts @@ -54,6 +54,7 @@ const setupNetworkController = ( const network = new NetworkController({ messenger: networkMessenger, infuraProjectId: '123', + trackMetaMetricsEvent: jest.fn(), }); return { network, networkMessenger }; diff --git a/packages/network-controller/package.json b/packages/network-controller/package.json index bf58c9ed74..fd4e1e4864 100644 --- a/packages/network-controller/package.json +++ b/packages/network-controller/package.json @@ -32,11 +32,13 @@ "@metamask/base-controller": "workspace:^", "@metamask/controller-utils": "workspace:^", "@metamask/swappable-obj-proxy": "^2.1.0", + "@metamask/utils": "^5.0.0", "async-mutex": "^0.2.6", "babel-runtime": "^6.26.0", "eth-json-rpc-infura": "^5.1.0", "eth-query": "^2.1.2", "immer": "^9.0.6", + "uuid": "^9.0.0", "web3-provider-engine": "^16.0.3" }, "devDependencies": { diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 7a24e649d7..13ee2ee366 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -5,6 +5,7 @@ import createMetamaskProvider from 'web3-provider-engine/zero'; import { createEventEmitterProxy } from '@metamask/swappable-obj-proxy'; import type { SwappableProxy } from '@metamask/swappable-obj-proxy'; import { Mutex } from 'async-mutex'; +import { v4 as random } from 'uuid'; import type { Patch } from 'immer'; import { BaseControllerV2, @@ -16,24 +17,29 @@ import { TESTNET_NETWORK_TYPE_TO_TICKER_SYMBOL, NetworksChainId, NetworkType, + isSafeChainId, } from '@metamask/controller-utils'; +import { assertIsStrictHexString } from '@metamask/utils'; + /** * @type ProviderConfig * * Configuration passed to web3-provider-engine - * @property rpcTarget - RPC target URL. + * @property rpcUrl - RPC target URL. * @property type - Human-readable network name. * @property chainId - Network ID as per EIP-155. * @property ticker - Currency ticker. * @property nickname - Personalized network name. + * @property id - Network Configuration Id. */ export type ProviderConfig = { - rpcTarget?: string; + rpcUrl?: string; type: NetworkType; chainId: string; ticker?: string; nickname?: string; + id?: string; }; export type Block = { @@ -44,19 +50,41 @@ export type NetworkDetails = { isEIP1559Compatible?: boolean; }; +/** + * Custom RPC network information + * + * @property rpcUrl - RPC target URL. + * @property chainId - Network ID as per EIP-155 + * @property nickname - Personalized network name. + * @property ticker - Currency ticker. + * @property rpcPrefs - Personalized preferences. + */ +export type NetworkConfiguration = { + rpcUrl: string; + chainId: string; + ticker: string; + nickname?: string; + rpcPrefs?: { + blockExplorerUrl: string; + }; +}; + /** * @type NetworkState * * Network controller state - * @property network - Network ID as per net_version - * @property isCustomNetwork - Identifies if the network is a custom network - * @property provider - RPC URL and network name provider settings + * @property network - Network ID as per net_version of the currently connected network + * @property isCustomNetwork - Identifies if the currently connected network is a custom network + * @property providerConfig - RPC URL and network name provider settings of the currently connected network + * @property properties - an additional set of network properties for the currently connected network + * @property networkConfigurations - the full list of configured networks either preloaded or added by the user. */ export type NetworkState = { network: string; isCustomNetwork: boolean; providerConfig: ProviderConfig; networkDetails: NetworkDetails; + networkConfigurations: Record; }; const LOCALHOST_RPC_URL = 'http://localhost:8545'; @@ -112,6 +140,7 @@ export type NetworkControllerMessenger = RestrictedControllerMessenger< export type NetworkControllerOptions = { messenger: NetworkControllerMessenger; + trackMetaMetricsEvent: () => void; infuraProjectId?: string; state?: Partial; }; @@ -121,6 +150,20 @@ export const defaultState: NetworkState = { isCustomNetwork: false, providerConfig: { type: MAINNET, chainId: NetworksChainId.mainnet }, networkDetails: { isEIP1559Compatible: false }, + networkConfigurations: {}, +}; + +type MetaMetricsEventPayload = { + event: string; + category: string; + referrer?: { url: string }; + actionId?: number; + environmentType?: string; + properties?: unknown; + sensitiveProperties?: unknown; + revenue?: number; + currency?: string; + value?: number; }; /** @@ -137,6 +180,8 @@ export class NetworkController extends BaseControllerV2< private infuraProjectId: string | undefined; + private trackMetaMetricsEvent: (event: MetaMetricsEventPayload) => void; + private mutex = new Mutex(); #provider: Provider | undefined; @@ -145,7 +190,12 @@ export class NetworkController extends BaseControllerV2< #blockTrackerProxy: BlockTrackerProxy | undefined; - constructor({ messenger, state, infuraProjectId }: NetworkControllerOptions) { + constructor({ + messenger, + state, + infuraProjectId, + trackMetaMetricsEvent, + }: NetworkControllerOptions) { super({ name, metadata: { @@ -165,11 +215,16 @@ export class NetworkController extends BaseControllerV2< persist: true, anonymous: false, }, + networkConfigurations: { + persist: true, + anonymous: false, + }, }, messenger, state: { ...defaultState, ...state }, }); this.infuraProjectId = infuraProjectId; + this.trackMetaMetricsEvent = trackMetaMetricsEvent; this.messagingSystem.registerActionHandler( `${this.name}:getProviderConfig`, () => { @@ -187,7 +242,7 @@ export class NetworkController extends BaseControllerV2< private initializeProvider( type: NetworkType, - rpcTarget?: string, + rpcUrl?: string, chainId?: string, ticker?: string, nickname?: string, @@ -206,8 +261,7 @@ export class NetworkController extends BaseControllerV2< this.setupStandardProvider(LOCALHOST_RPC_URL); break; case RPC: - rpcTarget && - this.setupStandardProvider(rpcTarget, chainId, ticker, nickname); + rpcUrl && this.setupStandardProvider(rpcUrl, chainId, ticker, nickname); break; default: throw new Error(`Unrecognized network type: '${type}'`); @@ -230,8 +284,8 @@ export class NetworkController extends BaseControllerV2< state.network = 'loading'; state.networkDetails = {}; }); - const { rpcTarget, type, chainId, ticker } = this.state.providerConfig; - this.initializeProvider(type, rpcTarget, chainId, ticker); + const { rpcUrl, type, chainId, ticker } = this.state.providerConfig; + this.initializeProvider(type, rpcUrl, chainId, ticker); this.lookupNetwork(); } @@ -273,7 +327,7 @@ export class NetworkController extends BaseControllerV2< } private setupStandardProvider( - rpcTarget: string, + rpcUrl: string, chainId?: string, ticker?: string, nickname?: string, @@ -284,7 +338,7 @@ export class NetworkController extends BaseControllerV2< chainId, engineParams: { pollingInterval: 12000 }, nickname, - rpcUrl: rpcTarget, + rpcUrl, ticker, }, }; @@ -319,9 +373,9 @@ export class NetworkController extends BaseControllerV2< */ set providerConfig(providerConfig: ProviderConfig) { this.internalProviderConfig = providerConfig; - const { type, rpcTarget, chainId, ticker, nickname } = + const { type, rpcUrl, chainId, ticker, nickname } = this.state.providerConfig; - this.initializeProvider(type, rpcTarget, chainId, ticker, nickname); + this.initializeProvider(type, rpcUrl, chainId, ticker, nickname); this.registerProvider(); this.lookupNetwork(); } @@ -396,7 +450,7 @@ export class NetworkController extends BaseControllerV2< state.providerConfig.type = type; state.providerConfig.ticker = ticker; state.providerConfig.chainId = NetworksChainId[type]; - state.providerConfig.rpcTarget = undefined; + state.providerConfig.rpcUrl = undefined; state.providerConfig.nickname = undefined; }); this.refreshNetwork(); @@ -405,24 +459,27 @@ export class NetworkController extends BaseControllerV2< /** * Convenience method to update provider RPC settings. * - * @param rpcTarget - The RPC endpoint URL. - * @param chainId - The chain ID as per EIP-155. - * @param ticker - The currency ticker. - * @param nickname - Personalized network name. + * @param networkConfigurationId - The unique id for the network configuration to set as the active provider. */ - setRpcTarget( - rpcTarget: string, - chainId: string, - ticker?: string, - nickname?: string, - ) { + setActiveNetwork(networkConfigurationId: string) { + const targetNetwork = + this.state.networkConfigurations[networkConfigurationId]; + + if (!targetNetwork) { + throw new Error( + `networkConfigurationId ${networkConfigurationId} does not match a configured networkConfiguration`, + ); + } + this.update((state) => { state.providerConfig.type = RPC; - state.providerConfig.rpcTarget = rpcTarget; - state.providerConfig.chainId = chainId; - state.providerConfig.ticker = ticker; - state.providerConfig.nickname = nickname; + state.providerConfig.rpcUrl = targetNetwork.rpcUrl; + state.providerConfig.chainId = targetNetwork.chainId; + state.providerConfig.ticker = targetNetwork.ticker; + state.providerConfig.nickname = targetNetwork.nickname; + state.providerConfig.id = targetNetwork.id; }); + this.refreshNetwork(); } @@ -481,6 +538,131 @@ export class NetworkController extends BaseControllerV2< }); } } + + /** + * Adds a network configuration if the rpcUrl is not already present on an + * existing network configuration. Otherwise updates the entry with the matching rpcUrl. + * + * @param networkConfiguration - The network configuration to add or, if rpcUrl matches an existing entry, to modify. + * @param networkConfiguration.rpcUrl - RPC provider url. + * @param networkConfiguration.chainId - Network ID as per EIP-155. + * @param networkConfiguration.ticker - Currency ticker. + * @param networkConfiguration.nickname - Personalized network name. + * @param networkConfiguration.rpcPrefs - Personalized preferences (i.e. preferred blockExplorer) + * @param options - additional configuration options. + * @param options.setActive - An option to set the newly added networkConfiguration as the active provider. + * @param options.referrer - The site from which the call originated, or 'metamask' for internal calls - used for event metrics. + * @param options.source - Where the upsertNetwork event originated (i.e. from a dapp or from the network form)- used for event metrics. + * @returns id for the added or updated network configuration + */ + upsertNetworkConfiguration( + { rpcUrl, chainId, ticker, nickname, rpcPrefs }: NetworkConfiguration, + { + setActive = false, + referrer, + source, + }: { setActive?: boolean; referrer: string; source: string }, + ) { + assertIsStrictHexString(chainId); + + if (!isSafeChainId(parseInt(chainId, 16))) { + throw new Error( + `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: any) { + if (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 newNetworkConfiguration = { + rpcUrl, + chainId, + ticker, + nickname, + rpcPrefs, + }; + + const oldNetworkConfigurations = this.state.networkConfigurations; + + const oldNetworkConfigurationId = Object.values( + oldNetworkConfigurations, + ).find( + (networkConfiguration) => + networkConfiguration.rpcUrl?.toLowerCase() === rpcUrl?.toLowerCase(), + )?.id; + + const newNetworkConfigurationId = oldNetworkConfigurationId || random(); + this.update((state) => { + state.networkConfigurations = { + ...oldNetworkConfigurations, + [newNetworkConfigurationId]: { + ...newNetworkConfiguration, + id: newNetworkConfigurationId, + }, + }; + }); + + if (!oldNetworkConfigurationId) { + this.trackMetaMetricsEvent({ + event: 'Custom Network Added', + category: 'Network', + referrer: { + url: referrer, + }, + properties: { + chain_id: chainId, + symbol: ticker, + source, + }, + }); + } + + if (setActive) { + this.setActiveNetwork(newNetworkConfigurationId); + } + + return newNetworkConfigurationId; + } + + /** + * Removes network configuration from state. + * + * @param networkConfigurationId - The networkConfigurationId of an existing network configuration + */ + removeNetworkConfiguration(networkConfigurationId: string) { + if (!this.state.networkConfigurations[networkConfigurationId]) { + throw new Error( + `networkConfigurationId ${networkConfigurationId} does not match a configured networkConfiguration`, + ); + } + this.update((state) => { + delete state.networkConfigurations[networkConfigurationId]; + }); + } } export default NetworkController; diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index 6af6ed8b48..bac19c6d8c 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -9,6 +9,7 @@ import createInfuraProvider from 'eth-json-rpc-infura/src/createProvider'; import type { ProviderEngine } from 'web3-provider-engine'; import createMetamaskProvider from 'web3-provider-engine/zero'; import { Patch } from 'immer'; +import { v4 } from 'uuid'; import { waitForResult } from '../../../tests/helpers'; import { NetworkController, @@ -32,6 +33,15 @@ jest.mock('web3-provider-engine/subproviders/provider'); jest.mock('eth-json-rpc-infura/src/createProvider'); jest.mock('web3-provider-engine/zero'); +jest.mock('uuid', () => { + const actual = jest.requireActual('uuid'); + + return { + ...actual, + v4: jest.fn(), + }; +}); + // Store this up front so it doesn't get lost when it is stubbed const originalSetTimeout = global.setTimeout; @@ -39,7 +49,7 @@ const SubproviderMock = mocked(Subprovider); const createInfuraProviderMock = mocked(createInfuraProvider); const createMetamaskProviderMock = mocked(createMetamaskProvider); -// setProviderType setRpcTarget +// setProviderType setActiveNetwork // └───────────┬────────────┘ // set providerConfig refreshNetwork // │ │ └────────────────────────────────────────────┬──────────────────────────────────────────────┘ │ @@ -72,6 +82,7 @@ describe('NetworkController', () => { it('initializes the state with some defaults', async () => { await withController(({ controller }) => { expect(controller.state).toStrictEqual({ + networkConfigurations: {}, network: 'loading', isCustomNetwork: false, providerConfig: { type: 'mainnet' as const, chainId: '1' }, @@ -90,6 +101,7 @@ describe('NetworkController', () => { }, ({ controller }) => { expect(controller.state).toStrictEqual({ + networkConfigurations: {}, network: 'loading', isCustomNetwork: true, providerConfig: { type: 'mainnet', chainId: '1' }, @@ -434,7 +446,7 @@ describe('NetworkController', () => { type: 'localhost', chainId: '66666', nickname: "doesn't matter", - rpcTarget: 'http://doesntmatter.com', + rpcUrl: 'http://doesntmatter.com', ticker: 'ABC', }), }, @@ -653,7 +665,7 @@ describe('NetworkController', () => { type: 'rpc', chainId: '123', nickname: 'some cool network', - rpcTarget: 'http://example.com', + rpcUrl: 'http://example.com', ticker: 'ABC', }, }, @@ -704,7 +716,7 @@ describe('NetworkController', () => { state: { providerConfig: buildProviderConfig({ type: 'rpc', - rpcTarget: 'http://example.com', + rpcUrl: 'http://example.com', }), }, }, @@ -738,7 +750,7 @@ describe('NetworkController', () => { state: { providerConfig: buildProviderConfig({ type: 'rpc', - rpcTarget: 'http://example.com', + rpcUrl: 'http://example.com', }), }, }, @@ -810,7 +822,7 @@ describe('NetworkController', () => { state: { providerConfig: buildProviderConfig({ type: 'rpc', - rpcTarget: 'http://example.com', + rpcUrl: 'http://example.com', }), }, }, @@ -1232,7 +1244,7 @@ describe('NetworkController', () => { state: { providerConfig: { type: 'localhost', - rpcTarget: 'http://somethingexisting.com', + rpcUrl: 'http://somethingexisting.com', chainId: '99999', ticker: 'something existing', nickname: 'something existing', @@ -1258,7 +1270,7 @@ describe('NetworkController', () => { type: 'mainnet', ticker: 'ETH', chainId: '1', - rpcTarget: undefined, + rpcUrl: undefined, nickname: undefined, }); }, @@ -1570,7 +1582,7 @@ describe('NetworkController', () => { state: { providerConfig: { type: 'localhost', - rpcTarget: 'http://somethingexisting.com', + rpcUrl: 'http://somethingexisting.com', chainId: '99999', ticker: 'something existing', nickname: 'something existing', @@ -1596,7 +1608,7 @@ describe('NetworkController', () => { type: networkType, ticker, chainId, - rpcTarget: undefined, + rpcUrl: undefined, nickname: undefined, }); }, @@ -1896,7 +1908,7 @@ describe('NetworkController', () => { state: { providerConfig: { type: 'localhost', - rpcTarget: 'http://somethingexisting.com', + rpcUrl: 'http://somethingexisting.com', chainId: '99999', ticker: 'something existing', nickname: 'something existing', @@ -1918,7 +1930,7 @@ describe('NetworkController', () => { type: 'rpc', ticker: 'ETH', chainId: '', - rpcTarget: undefined, + rpcUrl: undefined, nickname: undefined, }); }, @@ -2010,7 +2022,7 @@ describe('NetworkController', () => { state: { providerConfig: { type: 'localhost', - rpcTarget: 'http://somethingexisting.com', + rpcUrl: 'http://somethingexisting.com', chainId: '99999', ticker: 'something existing', nickname: 'something existing', @@ -2032,7 +2044,7 @@ describe('NetworkController', () => { type: 'localhost', ticker: 'ETH', chainId: '', - rpcTarget: undefined, + rpcUrl: undefined, nickname: undefined, }); }, @@ -2277,9 +2289,9 @@ describe('NetworkController', () => { }); }); - describe('setRpcTarget', () => { - describe('given only an RPC target and chain ID', () => { - it('updates the provider config in state with the RPC target and chain ID, clearing any existing ticker and nickname', async () => { + describe('setActiveNetwork', () => { + describe('given only a networkConfigurationId', () => { + it('updates the provider config in state with the rpcUrl and chainId, clearing the previous provider details', async () => { const messenger = buildMessenger(); await withController( { @@ -2287,11 +2299,26 @@ describe('NetworkController', () => { state: { providerConfig: { type: 'localhost', - rpcTarget: 'http://somethingexisting.com', + rpcUrl: 'http://somethingexisting.com', chainId: '99999', ticker: 'something existing', nickname: 'something existing', }, + networkConfigurations: { + testNetworkConfigurationId: { + rpcUrl: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', + }, + testNetworkConfigurationId2: { + rpcUrl: 'http://somethingexisting.com', + chainId: '99999', + ticker: 'something existing', + nickname: 'something existing', + id: 'testNetworkConfigurationId2', + }, + }, }, }, async ({ controller }) => { @@ -2301,15 +2328,16 @@ describe('NetworkController', () => { await waitForStateChanges(messenger, { propertyPath: ['network'], produceStateChanges: () => { - controller.setRpcTarget('http://example.com', '123'); + controller.setActiveNetwork('testNetworkConfigurationId'); }, }); expect(controller.state.providerConfig).toStrictEqual({ type: 'rpc', - rpcTarget: 'http://example.com', - chainId: '123', - ticker: undefined, + rpcUrl: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', nickname: undefined, }); }, @@ -2323,6 +2351,14 @@ describe('NetworkController', () => { messenger, state: { isCustomNetwork: false, + networkConfigurations: { + testNetworkConfigurationId: { + rpcUrl: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', + }, + }, }, }, async ({ controller }) => { @@ -2332,7 +2368,7 @@ describe('NetworkController', () => { await waitForStateChanges(messenger, { propertyPath: ['isCustomNetwork'], produceStateChanges: () => { - controller.setRpcTarget('http://example.com', '123'); + controller.setActiveNetwork('testNetworkConfigurationId'); }, }); @@ -2342,532 +2378,309 @@ describe('NetworkController', () => { }); it('sets the provider to a custom RPC provider initialized with the RPC target and chain ID, leaving nickname and ticker undefined', async () => { - await withController(async ({ controller }) => { - const fakeMetamaskProvider = buildFakeMetamaskProvider([ - { - request: { - method: 'eth_chainId', - }, - response: { - result: '0x1337', + const messenger = buildMessenger(); + await withController( + { + messenger, + state: { + networkConfigurations: { + testNetworkConfigurationId: { + rpcUrl: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', + }, }, }, - ]); - createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); + }, + async ({ controller }) => { + const fakeMetamaskProvider = buildFakeMetamaskProvider([ + { + request: { + method: 'eth_chainId', + }, + response: { + result: '0x1337', + }, + }, + ]); + createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); - controller.setRpcTarget('http://example.com', '123'); + controller.setActiveNetwork('testNetworkConfigurationId'); - expect(createMetamaskProviderMock).toHaveBeenCalledWith({ - chainId: '123', - engineParams: { pollingInterval: 12000 }, - nickname: undefined, - rpcUrl: 'http://example.com', - ticker: undefined, - }); - const { provider } = controller.getProviderAndBlockTracker(); - const promisifiedSendAsync = promisify(provider.sendAsync).bind( - provider, - ); - const chainIdResult = await promisifiedSendAsync({ - method: 'eth_chainId', - }); - expect(chainIdResult.result).toBe('0x1337'); - }); + expect(createMetamaskProviderMock).toHaveBeenCalledWith({ + rpcUrl: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + nickname: undefined, + engineParams: { pollingInterval: 12000 }, + }); + const { provider } = controller.getProviderAndBlockTracker(); + const promisifiedSendAsync = promisify(provider.sendAsync).bind( + provider, + ); + const chainIdResult = await promisifiedSendAsync({ + method: 'eth_chainId', + }); + expect(chainIdResult.result).toBe('0x1337'); + }, + ); }); it('updates networkDetails.isEIP1559Compatible in state based on the latest block (assuming that the request for eth_getBlockByNumber is made successfully)', async () => { const messenger = buildMessenger(); - await withController({ messenger }, async ({ controller }) => { - const fakeMetamaskProvider = buildFakeMetamaskProvider([ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: { - baseFeePerGas: '0x1', + await withController( + { + messenger, + state: { + isCustomNetwork: false, + networkConfigurations: { + testNetworkConfigurationId: { + rpcUrl: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', }, }, }, - ]); - createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); + }, + async ({ controller }) => { + const fakeMetamaskProvider = buildFakeMetamaskProvider([ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + response: { + result: { + baseFeePerGas: '0x1', + }, + }, + }, + ]); + createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); - await waitForStateChanges(messenger, { - propertyPath: ['networkDetails', 'isEIP1559Compatible'], - produceStateChanges: () => { - controller.setRpcTarget('http://example.com', '123'); - }, - }); + await waitForStateChanges(messenger, { + propertyPath: ['networkDetails', 'isEIP1559Compatible'], + produceStateChanges: () => { + controller.setActiveNetwork('testNetworkConfigurationId'); + }, + }); - expect(controller.state.networkDetails.isEIP1559Compatible).toBe( - true, - ); - }); + expect(controller.state.networkDetails.isEIP1559Compatible).toBe( + true, + ); + }, + ); }); it('ensures that the existing provider is stopped while replacing it', async () => { - await withController(({ controller }) => { - const fakeMetamaskProviders = [ - buildFakeMetamaskProvider(), - buildFakeMetamaskProvider(), - ]; - jest.spyOn(fakeMetamaskProviders[0], 'stop'); - createMetamaskProviderMock - .mockImplementationOnce(() => fakeMetamaskProviders[0]) - .mockImplementationOnce(() => fakeMetamaskProviders[1]); + const messenger = buildMessenger(); + await withController( + { + messenger, + state: { + networkConfigurations: { + testNetworkConfigurationId: { + rpcUrl: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', + }, + }, + }, + }, + ({ controller }) => { + const fakeMetamaskProviders = [ + buildFakeMetamaskProvider(), + buildFakeMetamaskProvider(), + ]; + jest.spyOn(fakeMetamaskProviders[0], 'stop'); + createMetamaskProviderMock + .mockImplementationOnce(() => fakeMetamaskProviders[0]) + .mockImplementationOnce(() => fakeMetamaskProviders[1]); - controller.setRpcTarget('http://example.com', '123'); - controller.setRpcTarget('http://example.com', '123'); - assert(controller.getProviderAndBlockTracker().provider); - jest.runAllTimers(); + controller.setActiveNetwork('testNetworkConfigurationId'); + controller.setActiveNetwork('testNetworkConfigurationId'); + assert(controller.getProviderAndBlockTracker().provider); + jest.runAllTimers(); - expect(fakeMetamaskProviders[0].stop).toHaveBeenCalled(); - }); + expect(fakeMetamaskProviders[0].stop).toHaveBeenCalled(); + }, + ); }); it('updates the version of the current network in state (assuming that the request for net_version is made successfully)', async () => { const messenger = buildMessenger(); - await withController({ messenger }, async ({ controller }) => { - const fakeMetamaskProvider = buildFakeMetamaskProvider([ - { - request: { - method: 'net_version', - params: [], - }, - response: { - result: '42', + await withController( + { + messenger, + state: { + networkConfigurations: { + testNetworkConfigurationId: { + rpcUrl: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', + }, }, }, - ]); - createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); + }, + async ({ controller }) => { + const fakeMetamaskProvider = buildFakeMetamaskProvider([ + { + request: { + method: 'net_version', + params: [], + }, + response: { + result: '42', + }, + }, + ]); + createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); - await waitForStateChanges(messenger, { - propertyPath: ['network'], - produceStateChanges: () => { - controller.setRpcTarget('http://example.com', '123'); - }, - }); + await waitForStateChanges(messenger, { + propertyPath: ['network'], + produceStateChanges: () => { + controller.setActiveNetwork('testNetworkConfigurationId'); + }, + }); - expect(controller.state.network).toBe('42'); - }); + expect(controller.state.network).toBe('42'); + }, + ); }); describe('when an "error" event occurs on the new provider', () => { - describe('if the network version could not be retrieved during the call to setRpcTarget', () => { + describe('if the network version could not be retrieved during the call to setActiveNetwork', () => { it('retrieves the network version again and, assuming success, persists it to state', async () => { const messenger = buildMessenger(); - await withController({ messenger }, async ({ controller }) => { - const fakeMetamaskProvider = buildFakeMetamaskProvider([ - { - request: { - method: 'net_version', - }, - response: { - error: 'oops', + await withController( + { + messenger, + state: { + networkConfigurations: { + testNetworkConfigurationId: { + rpcUrl: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', + }, }, }, - { - request: { - method: 'net_version', + }, + async ({ controller }) => { + const fakeMetamaskProvider = buildFakeMetamaskProvider([ + { + request: { + method: 'net_version', + }, + response: { + error: 'oops', + }, }, - response: { - result: '42', + { + request: { + method: 'net_version', + }, + response: { + result: '42', + }, }, - }, - ]); - createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); + ]); + createMetamaskProviderMock.mockReturnValue( + fakeMetamaskProvider, + ); - await waitForPublishedEvents( - messenger, - 'NetworkController:providerConfigChange', - { - produceEvents: () => { - controller.setRpcTarget('http://example.com', '123'); - assert(controller.getProviderAndBlockTracker().provider); + await waitForPublishedEvents( + messenger, + 'NetworkController:providerConfigChange', + { + produceEvents: () => { + controller.setActiveNetwork('testNetworkConfigurationId'); + assert(controller.getProviderAndBlockTracker().provider); + }, }, - }, - ); + ); - await waitForStateChanges(messenger, { - propertyPath: ['network'], - produceStateChanges: () => { - controller - .getProviderAndBlockTracker() - .provider.emit('error', { some: 'error' }); - }, - }); - expect(controller.state.network).toBe('42'); - }); + await waitForStateChanges(messenger, { + propertyPath: ['network'], + produceStateChanges: () => { + controller + .getProviderAndBlockTracker() + .provider.emit('error', { some: 'error' }); + }, + }); + expect(controller.state.network).toBe('42'); + }, + ); }); }); - describe('if the network version could be retrieved during the call to setRpcTarget', () => { + describe('if the network version could be retrieved during the call to setActiveNetwork', () => { it('does not retrieve the network version again', async () => { const messenger = buildMessenger(); - await withController({ messenger }, async ({ controller }) => { - const fakeMetamaskProvider = buildFakeMetamaskProvider([ - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - { - request: { - method: 'net_version', - }, - response: { - result: '2', - }, - }, - ]); - createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); - - await waitForPublishedEvents( + await withController( + { messenger, - 'NetworkController:providerConfigChange', - { - produceEvents: () => { - controller.setRpcTarget('http://example.com', '123'); - assert(controller.getProviderAndBlockTracker().provider); + state: { + networkConfigurations: { + testNetworkConfigurationId: { + rpcUrl: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', + }, }, }, - ); - - await waitForStateChanges(messenger, { - propertyPath: ['network'], - count: 0, - produceStateChanges: () => { - controller - .getProviderAndBlockTracker() - .provider.emit('error', { some: 'error' }); - }, - }); - expect(controller.state.network).toBe('1'); - }); - }); - }); - }); - }); - - describe('given an RPC target, chain ID, ticker, and nickname', () => { - it('updates the provider config in state with the RPC target, chain ID, ticker, and nickname', async () => { - const messenger = buildMessenger(); - await withController( - { - messenger, - state: { - providerConfig: { - type: 'localhost', - rpcTarget: 'http://somethingexisting.com', - chainId: '99999', - ticker: 'something existing', - nickname: 'something existing', - }, - }, - }, - async ({ controller }) => { - const fakeMetamaskProvider = buildFakeMetamaskProvider(); - createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); - - await waitForStateChanges(messenger, { - propertyPath: ['network'], - produceStateChanges: () => { - controller.setRpcTarget( - 'http://example.com', - '123', - 'ABC', - 'cool network', - ); - }, - }); - - expect(controller.state.providerConfig).toStrictEqual({ - type: 'rpc', - rpcTarget: 'http://example.com', - chainId: '123', - ticker: 'ABC', - nickname: 'cool network', - }); - }, - ); - }); - - it('sets isCustomNetwork in state to true', async () => { - const messenger = buildMessenger(); - await withController( - { - messenger, - state: { - isCustomNetwork: false, - }, - }, - async ({ controller }) => { - const fakeMetamaskProvider = buildFakeMetamaskProvider(); - createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); - - await waitForStateChanges(messenger, { - propertyPath: ['isCustomNetwork'], - produceStateChanges: () => { - controller.setRpcTarget( - 'http://example.com', - '123', - 'ABC', - 'cool network', - ); - }, - }); - - expect(controller.state.isCustomNetwork).toBe(true); - }, - ); - }); - - it('sets the provider to a custom RPC provider initialized with the RPC target, chain ID, and ticker, ignoring the nickname', async () => { - await withController(async ({ controller }) => { - const fakeMetamaskProvider = buildFakeMetamaskProvider([ - { - request: { - method: 'eth_chainId', - }, - response: { - result: '0x1337', - }, - }, - ]); - createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); - - controller.setRpcTarget( - 'http://example.com', - '123', - 'ABC', - 'cool network', - ); - - expect(createMetamaskProviderMock).toHaveBeenCalledWith({ - chainId: '123', - engineParams: { pollingInterval: 12000 }, - nickname: undefined, - rpcUrl: 'http://example.com', - ticker: 'ABC', - }); - const { provider } = controller.getProviderAndBlockTracker(); - const promisifiedSendAsync = promisify(provider.sendAsync).bind( - provider, - ); - const chainIdResult = await promisifiedSendAsync({ - method: 'eth_chainId', - }); - expect(chainIdResult.result).toBe('0x1337'); - }); - }); - - it('updates networkDetails.isEIP1559Compatible in state based on the latest block (assuming that the request for eth_getBlockByNumber is made successfully)', async () => { - const messenger = buildMessenger(); - await withController({ messenger }, async ({ controller }) => { - const fakeMetamaskProvider = buildFakeMetamaskProvider([ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: { - baseFeePerGas: '0x1', - }, - }, - }, - ]); - createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); - - await waitForStateChanges(messenger, { - propertyPath: ['networkDetails', 'isEIP1559Compatible'], - produceStateChanges: () => { - controller.setRpcTarget( - 'http://example.com', - '123', - 'ABC', - 'cool network', - ); - }, - }); - - expect(controller.state.networkDetails.isEIP1559Compatible).toBe( - true, - ); - }); - }); - - it('ensures that the existing provider is stopped while replacing it', async () => { - await withController(({ controller }) => { - const fakeMetamaskProviders = [ - buildFakeMetamaskProvider(), - buildFakeMetamaskProvider(), - ]; - jest.spyOn(fakeMetamaskProviders[0], 'stop'); - createMetamaskProviderMock - .mockImplementationOnce(() => fakeMetamaskProviders[0]) - .mockImplementationOnce(() => fakeMetamaskProviders[1]); - - controller.setRpcTarget( - 'http://example.com', - '123', - 'ABC', - 'cool network', - ); - controller.setRpcTarget( - 'http://example.com', - '123', - 'ABC', - 'cool network', - ); - assert(controller.getProviderAndBlockTracker().provider); - jest.runAllTimers(); - - expect(fakeMetamaskProviders[0].stop).toHaveBeenCalled(); - }); - }); - - it('updates the version of the current network in state (assuming that the request for net_version is made successfully)', async () => { - const messenger = buildMessenger(); - await withController({ messenger }, async ({ controller }) => { - const fakeMetamaskProvider = buildFakeMetamaskProvider([ - { - request: { - method: 'net_version', - params: [], - }, - response: { - result: '42', }, - }, - ]); - createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); - - await waitForStateChanges(messenger, { - propertyPath: ['network'], - produceStateChanges: () => { - controller.setRpcTarget( - 'http://example.com', - '123', - 'ABC', - 'cool network', - ); - }, - }); - - expect(controller.state.network).toBe('42'); - }); - }); - - describe('when an "error" event occurs on the new provider', () => { - describe('if the network version could not be retrieved during the call to setRpcTarget', () => { - it('retrieves the network version again and, assuming success, persists it to state', async () => { - const messenger = buildMessenger(); - await withController({ messenger }, async ({ controller }) => { - const fakeMetamaskProvider = buildFakeMetamaskProvider([ - { - request: { - method: 'net_version', - }, - response: { - error: 'oops', - }, - }, - { - request: { - method: 'net_version', - }, - response: { - result: '42', + async ({ controller }) => { + const fakeMetamaskProvider = buildFakeMetamaskProvider([ + { + request: { + method: 'net_version', + }, + response: { + result: '1', + }, }, - }, - ]); - createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); - - await waitForPublishedEvents( - messenger, - 'NetworkController:providerConfigChange', - { - produceEvents: () => { - controller.setRpcTarget( - 'http://example.com', - '123', - 'ABC', - 'cool network', - ); - assert(controller.getProviderAndBlockTracker().provider); + { + request: { + method: 'net_version', + }, + response: { + result: '2', + }, }, - }, - ); - - await waitForStateChanges(messenger, { - propertyPath: ['network'], - produceStateChanges: () => { - controller - .getProviderAndBlockTracker() - .provider.emit('error', { some: 'error' }); - }, - }); - expect(controller.state.network).toBe('42'); - }); - }); - }); + ]); + createMetamaskProviderMock.mockReturnValue( + fakeMetamaskProvider, + ); - describe('if the network version could be retrieved during the call to setRpcTarget', () => { - it('does not retrieve the network version again', async () => { - const messenger = buildMessenger(); - await withController({ messenger }, async ({ controller }) => { - const fakeMetamaskProvider = buildFakeMetamaskProvider([ - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - { - request: { - method: 'net_version', - }, - response: { - result: '2', + await waitForPublishedEvents( + messenger, + 'NetworkController:providerConfigChange', + { + produceEvents: () => { + controller.setActiveNetwork('testNetworkConfigurationId'); + assert(controller.getProviderAndBlockTracker().provider); + }, }, - }, - ]); - createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); + ); - await waitForPublishedEvents( - messenger, - 'NetworkController:providerConfigChange', - { - produceEvents: () => { - controller.setRpcTarget( - 'http://example.com', - '123', - 'ABC', - 'cool network', - ); - assert(controller.getProviderAndBlockTracker().provider); + await waitForStateChanges(messenger, { + propertyPath: ['network'], + count: 0, + produceStateChanges: () => { + controller + .getProviderAndBlockTracker() + .provider.emit('error', { some: 'error' }); }, - }, - ); - - await waitForStateChanges(messenger, { - propertyPath: ['network'], - count: 0, - produceStateChanges: () => { - controller - .getProviderAndBlockTracker() - .provider.emit('error', { some: 'error' }); - }, - }); - expect(controller.state.network).toBe('1'); - }); + }); + expect(controller.state.network).toBe('1'); + }, + ); }); }); }); @@ -3696,6 +3509,143 @@ describe('NetworkController', () => { }); }); }); + + describe('upsertNetworkConfiguration', () => { + it('adds the given network configuration when its rpcURL does not match an existing configuration', async () => { + (v4 as jest.Mock).mockImplementationOnce( + () => 'network-configuration-id-1', + ); + const messenger = buildMessenger(); + await withController({ messenger }, async ({ controller }) => { + const rpcUrlNetwork = { + chainId: '0x9999', + rpcUrl: 'https://test-rpc.com', + ticker: 'RPC', + }; + + expect(controller.state.networkConfigurations).toStrictEqual({}); + + controller.upsertNetworkConfiguration(rpcUrlNetwork, { + referrer: 'https://test-dapp.com', + source: 'dapp', + }); + + expect( + Object.values(controller.state.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: '0x1', + id: 'testNetworkConfigurationId', + }, + }, + }, + }, + async ({ controller }) => { + controller.upsertNetworkConfiguration( + { + rpcUrl: 'https://rpc-url.com', + ticker: 'new_rpc_ticker', + nickname: 'new_rpc_nickname', + rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, + chainId: '0x1', + }, + { referrer: 'https://test-dapp.com', source: 'dapp' }, + ); + expect( + Object.values(controller.state.networkConfigurations), + ).toStrictEqual( + expect.arrayContaining([ + { + rpcUrl: 'https://rpc-url.com', + nickname: 'new_rpc_nickname', + ticker: 'new_rpc_ticker', + rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, + chainId: '0x1', + id: 'testNetworkConfigurationId', + }, + ]), + ); + }, + ); + }); + + 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: '1', + id: testNetworkConfigurationId, + }, + }, + }, + }, + async ({ controller }) => { + controller.removeNetworkConfiguration(testNetworkConfigurationId); + expect(controller.state.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: '1', + id: testNetworkConfigurationId, + }, + }, + }, + }, + async ({ controller }) => { + expect(() => + controller.removeNetworkConfiguration( + invalidNetworkConfigurationId, + ), + ).toThrow( + `networkConfigurationId ${invalidNetworkConfigurationId} does not match a configured networkConfiguration`, + ); + }, + ); + }); + }); + }); }); /** @@ -3750,6 +3700,7 @@ async function withController( args.length === 2 ? args : [{}, args[0]]; const controller = new NetworkController({ messenger, + trackMetaMetricsEvent: jest.fn(), ...rest, }); try { diff --git a/packages/network-controller/tests/provider-api-tests/helpers.ts b/packages/network-controller/tests/provider-api-tests/helpers.ts index acd8887f76..779dda64f4 100644 --- a/packages/network-controller/tests/provider-api-tests/helpers.ts +++ b/packages/network-controller/tests/provider-api-tests/helpers.ts @@ -268,10 +268,9 @@ const makeRpcCall = ( export type ProviderType = 'infura' | 'custom'; export type MockOptions = { - infuraNetwork?: NetworkType; providerType: ProviderType; + infuraNetwork?: NetworkType; customRpcUrl?: string; - customChainId?: string; }; export type MockCommunications = { @@ -370,7 +369,6 @@ export const withNetworkClient = async ( providerType, infuraNetwork = 'mainnet', customRpcUrl = MOCK_RPC_URL, - customChainId = '0x1', }: MockOptions, fn: (client: MockNetworkClient) => Promise, ): Promise => { @@ -386,6 +384,7 @@ export const withNetworkClient = async ( const controller = new NetworkController({ messenger, infuraProjectId: MOCK_INFURA_PROJECT_ID, + trackMetaMetricsEvent: jest.fn(), }); const getEIP1559CompatibilityMock = jest @@ -400,10 +399,19 @@ export const withNetworkClient = async ( return Promise.resolve(); }); + const networkConfigurationId = controller.upsertNetworkConfiguration( + { + rpcUrl: customRpcUrl, + chainId: '0x9999', + ticker: 'TEST', + }, + { referrer: 'https://test-dapp.com', source: 'dapp' }, + ); + if (providerType === 'infura') { controller.setProviderType(infuraNetwork); - } else { - controller.setRpcTarget(customRpcUrl, customChainId); + } else if (networkConfigurationId) { + controller.setActiveNetwork(networkConfigurationId); } const ethQuery = messenger.call('NetworkController:getEthQuery'); diff --git a/packages/network-controller/tests/provider-api-tests/shared-tests.ts b/packages/network-controller/tests/provider-api-tests/shared-tests.ts index bf8946d2e7..1ea74a8bb3 100644 --- a/packages/network-controller/tests/provider-api-tests/shared-tests.ts +++ b/packages/network-controller/tests/provider-api-tests/shared-tests.ts @@ -367,7 +367,7 @@ export const testsForProviderType = (providerType: ProviderType) => { describe('net_version', () => { it('does hit RPC endpoint to get net_version', async () => { await withMockedCommunications( - { providerType, infuraNetwork: 'goerli', customChainId: '5' }, + { providerType, infuraNetwork: 'goerli' }, async (comms) => { comms.mockRpcCall({ request: { method: 'net_version' }, diff --git a/packages/permission-controller/src/rpc-methods/requestPermissions.test.ts b/packages/permission-controller/src/rpc-methods/requestPermissions.test.ts index d1144eb195..e1eff902cf 100644 --- a/packages/permission-controller/src/rpc-methods/requestPermissions.test.ts +++ b/packages/permission-controller/src/rpc-methods/requestPermissions.test.ts @@ -99,6 +99,7 @@ describe('requestPermissions RPC method', () => { delete expectedError.stack; const response: any = await engine.handle(req as any); + delete response.error.stack; expect(response.error).toStrictEqual(expectedError); expect(mockRequestPermissionsForOrigin).not.toHaveBeenCalled(); } @@ -131,6 +132,7 @@ describe('requestPermissions RPC method', () => { delete expectedError.stack; const response: any = await engine.handle(req as any); + delete response.error.stack; expect(response.error).toStrictEqual(expectedError); expect(mockRequestPermissionsForOrigin).not.toHaveBeenCalled(); } diff --git a/packages/preferences-controller/jest.config.js b/packages/preferences-controller/jest.config.js index 5fd4fda362..6a84cceb23 100644 --- a/packages/preferences-controller/jest.config.js +++ b/packages/preferences-controller/jest.config.js @@ -17,9 +17,9 @@ module.exports = merge(baseConfig, { coverageThreshold: { global: { branches: 88.23, - functions: 95, - lines: 93.82, - statements: 93.82, + functions: 93.75, + lines: 92.54, + statements: 92.54, }, }, }); diff --git a/packages/preferences-controller/src/PreferencesController.test.ts b/packages/preferences-controller/src/PreferencesController.test.ts index 66c7c39d1f..f64ca48589 100644 --- a/packages/preferences-controller/src/PreferencesController.test.ts +++ b/packages/preferences-controller/src/PreferencesController.test.ts @@ -5,7 +5,6 @@ describe('PreferencesController', () => { const controller = new PreferencesController(); expect(controller.state).toStrictEqual({ featureFlags: {}, - frequentRpcList: [], identities: {}, ipfsGateway: 'https://ipfs.io/ipfs/', lostIdentities: {}, @@ -150,56 +149,6 @@ describe('PreferencesController', () => { expect(controller.state.selectedAddress).toStrictEqual('0x00'); }); - it('should add custom rpc url', () => { - const controller = new PreferencesController(); - const rpcUrlNetwork = { - chainId: undefined, - nickname: 'RPC', - rpcPrefs: undefined, - rpcUrl: 'rpc_url', - ticker: 'RPC', - }; - const localhostNetwork = { - chainId: undefined, - nickname: undefined, - rpcPrefs: undefined, - rpcUrl: 'http://localhost:8545', - ticker: 'LOCAL', - }; - controller.addToFrequentRpcList('rpc_url', undefined, 'RPC', 'RPC'); - controller.addToFrequentRpcList( - 'http://localhost:8545', - undefined, - 'LOCAL', - ); - - expect(controller.state.frequentRpcList).toStrictEqual([ - rpcUrlNetwork, - localhostNetwork, - ]); - controller.addToFrequentRpcList('rpc_url'); - expect(controller.state.frequentRpcList).toStrictEqual([ - localhostNetwork, - { ...rpcUrlNetwork, nickname: undefined, ticker: undefined }, - ]); - }); - - it('should remove custom rpc url', () => { - const controller = new PreferencesController(); - const rpcUrlNetwork = { - chainId: undefined, - nickname: undefined, - rpcPrefs: undefined, - rpcUrl: 'rpc_url', - ticker: undefined, - }; - controller.addToFrequentRpcList('rpc_url'); - expect(controller.state.frequentRpcList).toStrictEqual([rpcUrlNetwork]); - controller.removeFromFrequentRpcList('other_rpc_url'); - controller.removeFromFrequentRpcList('rpc_url'); - expect(controller.state.frequentRpcList).toStrictEqual([]); - }); - it('should set IPFS gateway', () => { const controller = new PreferencesController(); controller.setIpfsGateway('https://ipfs.infura.io/ipfs/'); diff --git a/packages/preferences-controller/src/PreferencesController.ts b/packages/preferences-controller/src/PreferencesController.ts index f89e24dffd..6fd100d9cc 100644 --- a/packages/preferences-controller/src/PreferencesController.ts +++ b/packages/preferences-controller/src/PreferencesController.ts @@ -18,45 +18,17 @@ export interface ContactEntry { importTime?: number; } -/** - * Custom RPC network information - * - * @property rpcUrl - RPC target URL. - * @property chainId - Network ID as per EIP-155 - * @property nickname - Personalized network name. - * @property ticker - Currency ticker. - * @property rpcPrefs - Personalized preferences. - */ -export interface FrequentRpc { - rpcUrl: string; - chainId?: number; - nickname?: string; - ticker?: string; - rpcPrefs?: RpcPreferences; -} - -/** - * Custom RPC network preferences - * - * @param blockExplorerUrl - Block explorer URL. - */ -export interface RpcPreferences { - blockExplorerUrl: string; -} - /** * @type PreferencesState * * Preferences controller state * @property featureFlags - Map of specific features to enable or disable - * @property frequentRpcList - A list of custom RPCs to provide the user * @property identities - Map of addresses to ContactEntry objects * @property lostIdentities - Map of lost addresses to ContactEntry objects * @property selectedAddress - Current coinbase account */ export interface PreferencesState extends BaseState { featureFlags: { [feature: string]: boolean }; - frequentRpcList: FrequentRpc[]; ipfsGateway: string; identities: { [address: string]: ContactEntry }; lostIdentities: { [address: string]: ContactEntry }; @@ -91,7 +63,6 @@ export class PreferencesController extends BaseController< super(config, state); this.defaultState = { featureFlags: {}, - frequentRpcList: [], identities: {}, ipfsGateway: 'https://ipfs.io/ipfs/', lostIdentities: {}, @@ -242,56 +213,6 @@ export class PreferencesController extends BaseController< this.update({ identities: { ...identities }, selectedAddress }); } - /** - * Adds custom RPC URL to state. - * - * @param url - The custom RPC URL. - * @param chainId - The chain ID of the network, as per EIP-155. - * @param ticker - Currency ticker. - * @param nickname - Personalized network name. - * @param rpcPrefs - Personalized preferences. - */ - addToFrequentRpcList( - url: string, - chainId?: number, - ticker?: string, - nickname?: string, - rpcPrefs?: RpcPreferences, - ) { - const { frequentRpcList } = this.state; - const index = frequentRpcList.findIndex(({ rpcUrl }) => { - return rpcUrl === url; - }); - if (index !== -1) { - frequentRpcList.splice(index, 1); - } - const newFrequestRpc: FrequentRpc = { - rpcUrl: url, - chainId, - ticker, - nickname, - rpcPrefs, - }; - frequentRpcList.push(newFrequestRpc); - this.update({ frequentRpcList: [...frequentRpcList] }); - } - - /** - * Removes custom RPC URL from state. - * - * @param url - Custom RPC URL. - */ - removeFromFrequentRpcList(url: string) { - const { frequentRpcList } = this.state; - const index = frequentRpcList.findIndex(({ rpcUrl }) => { - return rpcUrl === url; - }); - if (index !== -1) { - frequentRpcList.splice(index, 1); - } - this.update({ frequentRpcList: [...frequentRpcList] }); - } - /** * Sets selected address. * diff --git a/packages/transaction-controller/src/TransactionController.test.ts b/packages/transaction-controller/src/TransactionController.test.ts index 85bb0df17e..86168e062e 100644 --- a/packages/transaction-controller/src/TransactionController.test.ts +++ b/packages/transaction-controller/src/TransactionController.test.ts @@ -138,6 +138,7 @@ const MOCK_NETWORK = { type: 'goerli' as NetworkType, chainId: NetworksChainId.goerli, }, + networkConfigurations: {}, }, subscribe: () => undefined, }; @@ -151,13 +152,18 @@ const MOCK_NETWORK_CUSTOM = { type: 'rpc' as NetworkType, chainId: '10', }, + networkConfigurations: {}, }, subscribe: () => undefined, }; const MOCK_NETWORK_WITHOUT_CHAIN_ID = { getProvider: () => PROVIDER, isCustomNetwork: false, - state: { network: '5', providerConfig: { type: 'goerli' as NetworkType } }, + state: { + network: '5', + providerConfig: { type: 'goerli' as NetworkType }, + networkConfigurations: {}, + }, subscribe: () => undefined, }; const MOCK_MAINNET_NETWORK = { @@ -170,6 +176,7 @@ const MOCK_MAINNET_NETWORK = { type: 'mainnet' as NetworkType, chainId: NetworksChainId.mainnet, }, + networkConfigurations: {}, }, subscribe: () => undefined, }; @@ -183,6 +190,7 @@ const MOCK_CUSTOM_NETWORK = { type: 'rpc' as NetworkType, chainId: '80001', }, + networkConfigurations: {}, }, subscribe: () => undefined, }; diff --git a/yarn.lock b/yarn.lock index 9eda144468..cb3eee4962 100644 --- a/yarn.lock +++ b/yarn.lock @@ -546,6 +546,33 @@ __metadata: languageName: node linkType: hard +"@chainsafe/as-sha256@npm:^0.3.1": + version: 0.3.1 + resolution: "@chainsafe/as-sha256@npm:0.3.1" + checksum: 58ea733be1657b0e31dbf48b0dba862da0833df34a81c1460c7352f04ce90874f70003cbf34d0afb9e5e53a33ee2d63a261a8b12462be85b2ba0a6f7f13d6150 + languageName: node + linkType: hard + +"@chainsafe/persistent-merkle-tree@npm:^0.4.2": + version: 0.4.2 + resolution: "@chainsafe/persistent-merkle-tree@npm:0.4.2" + dependencies: + "@chainsafe/as-sha256": ^0.3.1 + checksum: f9cfcb2132a243992709715dbd28186ab48c7c0c696f29d30857693cca5526bf753974a505ef68ffd5623bbdbcaa10f9083f4dd40bf99eb6408e451cc26a1a9e + languageName: node + linkType: hard + +"@chainsafe/ssz@npm:0.9.4": + version: 0.9.4 + resolution: "@chainsafe/ssz@npm:0.9.4" + dependencies: + "@chainsafe/as-sha256": ^0.3.1 + "@chainsafe/persistent-merkle-tree": ^0.4.2 + case: ^1.6.3 + checksum: c6eaedeae9e5618b3c666ff4507a27647f665a8dcf17d5ca86da4ed4788c5a93868f256d0005467d184fdf35ec03f323517ec2e55ec42492d769540a2ec396bc + languageName: node + linkType: hard + "@cnakazawa/watch@npm:^1.0.3": version: 1.0.3 resolution: "@cnakazawa/watch@npm:1.0.3" @@ -615,6 +642,16 @@ __metadata: languageName: node linkType: hard +"@ethereumjs/common@npm:^3.1.1": + version: 3.1.1 + resolution: "@ethereumjs/common@npm:3.1.1" + dependencies: + "@ethereumjs/util": ^8.0.5 + crc-32: ^1.2.0 + checksum: 58602dee9fbcf691dca111b4fd7fd5770f5e86d68012ce48fba396c7038afdca4fca273a9cf39f88cf6ea7b256603a4bd214e94e9d01361efbcd060460b78952 + languageName: node + linkType: hard + "@ethereumjs/rlp@npm:^4.0.0-beta.2": version: 4.0.0 resolution: "@ethereumjs/rlp@npm:4.0.0" @@ -624,6 +661,15 @@ __metadata: languageName: node linkType: hard +"@ethereumjs/rlp@npm:^4.0.1": + version: 4.0.1 + resolution: "@ethereumjs/rlp@npm:4.0.1" + bin: + rlp: bin/rlp + checksum: 30db19c78faa2b6ff27275ab767646929207bb207f903f09eb3e4c273ce2738b45f3c82169ddacd67468b4f063d8d96035f2bf36f02b6b7e4d928eefe2e3ecbc + languageName: node + linkType: hard + "@ethereumjs/tx@npm:3.5.1": version: 3.5.1 resolution: "@ethereumjs/tx@npm:3.5.1" @@ -654,6 +700,25 @@ __metadata: languageName: node linkType: hard +"@ethereumjs/tx@npm:^4.1.1": + version: 4.1.1 + resolution: "@ethereumjs/tx@npm:4.1.1" + dependencies: + "@chainsafe/ssz": 0.9.4 + "@ethereumjs/common": ^3.1.1 + "@ethereumjs/rlp": ^4.0.1 + "@ethereumjs/util": ^8.0.5 + "@ethersproject/providers": ^5.7.2 + ethereum-cryptography: ^1.1.2 + peerDependencies: + c-kzg: ^1.0.8 + peerDependenciesMeta: + c-kzg: + optional: true + checksum: 98897e79adf03ee90ed98c6a543e15e0b4e127bc5bc381d70cdcc76b111574205b94869c29d925ea9e30a98e5ef8b0f5597914359deb9db552017b2e78ef17a8 + languageName: node + linkType: hard + "@ethereumjs/util@npm:^8.0.0": version: 8.0.0 resolution: "@ethereumjs/util@npm:8.0.0" @@ -675,6 +740,17 @@ __metadata: languageName: node linkType: hard +"@ethereumjs/util@npm:^8.0.5": + version: 8.0.5 + resolution: "@ethereumjs/util@npm:8.0.5" + dependencies: + "@chainsafe/ssz": 0.9.4 + "@ethereumjs/rlp": ^4.0.1 + ethereum-cryptography: ^1.1.2 + checksum: 318386785295b4584289b1aa576d2621392b3a918d127890db62d3f74184f3377694dd9e951e19bfb9ab80e8dc9e38e180236cac2651dead26097d10963731f9 + languageName: node + linkType: hard + "@ethersproject/abi@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/abi@npm:5.7.0" @@ -879,6 +955,34 @@ __metadata: languageName: node linkType: hard +"@ethersproject/providers@npm:^5.7.2": + version: 5.7.2 + resolution: "@ethersproject/providers@npm:5.7.2" + dependencies: + "@ethersproject/abstract-provider": ^5.7.0 + "@ethersproject/abstract-signer": ^5.7.0 + "@ethersproject/address": ^5.7.0 + "@ethersproject/base64": ^5.7.0 + "@ethersproject/basex": ^5.7.0 + "@ethersproject/bignumber": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/constants": ^5.7.0 + "@ethersproject/hash": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/networks": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + "@ethersproject/random": ^5.7.0 + "@ethersproject/rlp": ^5.7.0 + "@ethersproject/sha2": ^5.7.0 + "@ethersproject/strings": ^5.7.0 + "@ethersproject/transactions": ^5.7.0 + "@ethersproject/web": ^5.7.0 + bech32: 1.1.4 + ws: 7.4.6 + checksum: 1754c731a5ca6782ae9677f4a9cd8b6246c4ef21a966c9a01b133750f3c578431ec43ec254e699969c4a0f87e84463ded50f96b415600aabd37d2056aee58c19 + languageName: node + linkType: hard + "@ethersproject/random@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/random@npm:5.7.0" @@ -1843,6 +1947,7 @@ __metadata: "@metamask/base-controller": "workspace:^" "@metamask/controller-utils": "workspace:^" "@metamask/swappable-obj-proxy": ^2.1.0 + "@metamask/utils": ^5.0.0 "@types/jest": ^26.0.22 "@types/lodash": ^4.14.191 async-mutex: ^0.2.6 @@ -1859,6 +1964,7 @@ __metadata: typedoc: ^0.22.15 typedoc-plugin-missing-exports: ^0.22.6 typescript: ~4.6.3 + uuid: ^9.0.0 web3-provider-engine: ^16.0.3 languageName: unknown linkType: soft @@ -2068,6 +2174,19 @@ __metadata: languageName: node linkType: hard +"@metamask/utils@npm:^5.0.0": + version: 5.0.0 + resolution: "@metamask/utils@npm:5.0.0" + dependencies: + "@ethereumjs/tx": ^4.1.1 + "@types/debug": ^4.1.7 + debug: ^4.3.4 + semver: ^7.3.8 + superstruct: ^1.0.3 + checksum: 34e39fc0bf28db5fe92676753de3291b05a517f8c81dbe332a4b6002739a58450a89fb2bddd85922a4f420affb1674604e6ad4627cdf052459e5371361ef7dd2 + languageName: node + linkType: hard + "@ngraveio/bc-ur@npm:^1.1.5": version: 1.1.6 resolution: "@ngraveio/bc-ur@npm:1.1.6" @@ -3776,6 +3895,13 @@ __metadata: languageName: node linkType: hard +"case@npm:^1.6.3": + version: 1.6.3 + resolution: "case@npm:1.6.3" + checksum: febe73278f910b0d28aab7efd6f51c235f9aa9e296148edb56dfb83fd58faa88308c30ce9a0122b6e53e0362c44f4407105bd5ef89c46860fc2b184e540fd68d + languageName: node + linkType: hard + "caseless@npm:~0.12.0": version: 0.12.0 resolution: "caseless@npm:0.12.0" @@ -10533,7 +10659,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.3.7": +"semver@npm:^7.3.7, semver@npm:^7.3.8": version: 7.3.8 resolution: "semver@npm:7.3.8" dependencies: @@ -11205,6 +11331,13 @@ __metadata: languageName: node linkType: hard +"superstruct@npm:^1.0.3": + version: 1.0.3 + resolution: "superstruct@npm:1.0.3" + checksum: 761790bb111e6e21ddd608299c252f3be35df543263a7ebbc004e840d01fcf8046794c274bcb351bdf3eae4600f79d317d085cdbb19ca05803a4361840cc9bb1 + languageName: node + linkType: hard + "supports-color@npm:^5.3.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0" @@ -11847,6 +11980,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:^9.0.0": + version: 9.0.0 + resolution: "uuid@npm:9.0.0" + bin: + uuid: dist/bin/uuid + checksum: 8dd2c83c43ddc7e1c71e36b60aea40030a6505139af6bee0f382ebcd1a56f6cd3028f7f06ffb07f8cf6ced320b76aea275284b224b002b289f89fe89c389b028 + languageName: node + linkType: hard + "v8-compile-cache-lib@npm:^3.0.1": version: 3.0.1 resolution: "v8-compile-cache-lib@npm:3.0.1" From 479e349d7097b55b6e4839ed354d3d3a121174ac Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 16 Mar 2023 17:31:22 -0500 Subject: [PATCH 2/8] remove unnecessary changes with yarn deduping fixed --- packages/controller-utils/src/util.ts | 2 +- packages/network-controller/package.json | 4 +- .../rpc-methods/requestPermissions.test.ts | 2 - yarn.lock | 146 +----------------- 4 files changed, 6 insertions(+), 148 deletions(-) diff --git a/packages/controller-utils/src/util.ts b/packages/controller-utils/src/util.ts index a3d7f16b3a..19afd9be88 100644 --- a/packages/controller-utils/src/util.ts +++ b/packages/controller-utils/src/util.ts @@ -134,7 +134,7 @@ export function getBuyURL( * @returns A BN instance. */ export function hexToBN(inputHex: string) { - return new BN(inputHex ? stripHexPrefix(inputHex) : inputHex, 16); + return new BN(stripHexPrefix(inputHex), 16); } /** diff --git a/packages/network-controller/package.json b/packages/network-controller/package.json index fd4e1e4864..84ea5e14c1 100644 --- a/packages/network-controller/package.json +++ b/packages/network-controller/package.json @@ -32,13 +32,13 @@ "@metamask/base-controller": "workspace:^", "@metamask/controller-utils": "workspace:^", "@metamask/swappable-obj-proxy": "^2.1.0", - "@metamask/utils": "^5.0.0", + "@metamask/utils": "^3.3.1", "async-mutex": "^0.2.6", "babel-runtime": "^6.26.0", "eth-json-rpc-infura": "^5.1.0", "eth-query": "^2.1.2", "immer": "^9.0.6", - "uuid": "^9.0.0", + "uuid": "^8.3.2", "web3-provider-engine": "^16.0.3" }, "devDependencies": { diff --git a/packages/permission-controller/src/rpc-methods/requestPermissions.test.ts b/packages/permission-controller/src/rpc-methods/requestPermissions.test.ts index e1eff902cf..d1144eb195 100644 --- a/packages/permission-controller/src/rpc-methods/requestPermissions.test.ts +++ b/packages/permission-controller/src/rpc-methods/requestPermissions.test.ts @@ -99,7 +99,6 @@ describe('requestPermissions RPC method', () => { delete expectedError.stack; const response: any = await engine.handle(req as any); - delete response.error.stack; expect(response.error).toStrictEqual(expectedError); expect(mockRequestPermissionsForOrigin).not.toHaveBeenCalled(); } @@ -132,7 +131,6 @@ describe('requestPermissions RPC method', () => { delete expectedError.stack; const response: any = await engine.handle(req as any); - delete response.error.stack; expect(response.error).toStrictEqual(expectedError); expect(mockRequestPermissionsForOrigin).not.toHaveBeenCalled(); } diff --git a/yarn.lock b/yarn.lock index cb3eee4962..1ae87318ab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -546,33 +546,6 @@ __metadata: languageName: node linkType: hard -"@chainsafe/as-sha256@npm:^0.3.1": - version: 0.3.1 - resolution: "@chainsafe/as-sha256@npm:0.3.1" - checksum: 58ea733be1657b0e31dbf48b0dba862da0833df34a81c1460c7352f04ce90874f70003cbf34d0afb9e5e53a33ee2d63a261a8b12462be85b2ba0a6f7f13d6150 - languageName: node - linkType: hard - -"@chainsafe/persistent-merkle-tree@npm:^0.4.2": - version: 0.4.2 - resolution: "@chainsafe/persistent-merkle-tree@npm:0.4.2" - dependencies: - "@chainsafe/as-sha256": ^0.3.1 - checksum: f9cfcb2132a243992709715dbd28186ab48c7c0c696f29d30857693cca5526bf753974a505ef68ffd5623bbdbcaa10f9083f4dd40bf99eb6408e451cc26a1a9e - languageName: node - linkType: hard - -"@chainsafe/ssz@npm:0.9.4": - version: 0.9.4 - resolution: "@chainsafe/ssz@npm:0.9.4" - dependencies: - "@chainsafe/as-sha256": ^0.3.1 - "@chainsafe/persistent-merkle-tree": ^0.4.2 - case: ^1.6.3 - checksum: c6eaedeae9e5618b3c666ff4507a27647f665a8dcf17d5ca86da4ed4788c5a93868f256d0005467d184fdf35ec03f323517ec2e55ec42492d769540a2ec396bc - languageName: node - linkType: hard - "@cnakazawa/watch@npm:^1.0.3": version: 1.0.3 resolution: "@cnakazawa/watch@npm:1.0.3" @@ -642,16 +615,6 @@ __metadata: languageName: node linkType: hard -"@ethereumjs/common@npm:^3.1.1": - version: 3.1.1 - resolution: "@ethereumjs/common@npm:3.1.1" - dependencies: - "@ethereumjs/util": ^8.0.5 - crc-32: ^1.2.0 - checksum: 58602dee9fbcf691dca111b4fd7fd5770f5e86d68012ce48fba396c7038afdca4fca273a9cf39f88cf6ea7b256603a4bd214e94e9d01361efbcd060460b78952 - languageName: node - linkType: hard - "@ethereumjs/rlp@npm:^4.0.0-beta.2": version: 4.0.0 resolution: "@ethereumjs/rlp@npm:4.0.0" @@ -661,15 +624,6 @@ __metadata: languageName: node linkType: hard -"@ethereumjs/rlp@npm:^4.0.1": - version: 4.0.1 - resolution: "@ethereumjs/rlp@npm:4.0.1" - bin: - rlp: bin/rlp - checksum: 30db19c78faa2b6ff27275ab767646929207bb207f903f09eb3e4c273ce2738b45f3c82169ddacd67468b4f063d8d96035f2bf36f02b6b7e4d928eefe2e3ecbc - languageName: node - linkType: hard - "@ethereumjs/tx@npm:3.5.1": version: 3.5.1 resolution: "@ethereumjs/tx@npm:3.5.1" @@ -700,25 +654,6 @@ __metadata: languageName: node linkType: hard -"@ethereumjs/tx@npm:^4.1.1": - version: 4.1.1 - resolution: "@ethereumjs/tx@npm:4.1.1" - dependencies: - "@chainsafe/ssz": 0.9.4 - "@ethereumjs/common": ^3.1.1 - "@ethereumjs/rlp": ^4.0.1 - "@ethereumjs/util": ^8.0.5 - "@ethersproject/providers": ^5.7.2 - ethereum-cryptography: ^1.1.2 - peerDependencies: - c-kzg: ^1.0.8 - peerDependenciesMeta: - c-kzg: - optional: true - checksum: 98897e79adf03ee90ed98c6a543e15e0b4e127bc5bc381d70cdcc76b111574205b94869c29d925ea9e30a98e5ef8b0f5597914359deb9db552017b2e78ef17a8 - languageName: node - linkType: hard - "@ethereumjs/util@npm:^8.0.0": version: 8.0.0 resolution: "@ethereumjs/util@npm:8.0.0" @@ -740,17 +675,6 @@ __metadata: languageName: node linkType: hard -"@ethereumjs/util@npm:^8.0.5": - version: 8.0.5 - resolution: "@ethereumjs/util@npm:8.0.5" - dependencies: - "@chainsafe/ssz": 0.9.4 - "@ethereumjs/rlp": ^4.0.1 - ethereum-cryptography: ^1.1.2 - checksum: 318386785295b4584289b1aa576d2621392b3a918d127890db62d3f74184f3377694dd9e951e19bfb9ab80e8dc9e38e180236cac2651dead26097d10963731f9 - languageName: node - linkType: hard - "@ethersproject/abi@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/abi@npm:5.7.0" @@ -955,34 +879,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/providers@npm:^5.7.2": - version: 5.7.2 - resolution: "@ethersproject/providers@npm:5.7.2" - dependencies: - "@ethersproject/abstract-provider": ^5.7.0 - "@ethersproject/abstract-signer": ^5.7.0 - "@ethersproject/address": ^5.7.0 - "@ethersproject/base64": ^5.7.0 - "@ethersproject/basex": ^5.7.0 - "@ethersproject/bignumber": ^5.7.0 - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/constants": ^5.7.0 - "@ethersproject/hash": ^5.7.0 - "@ethersproject/logger": ^5.7.0 - "@ethersproject/networks": ^5.7.0 - "@ethersproject/properties": ^5.7.0 - "@ethersproject/random": ^5.7.0 - "@ethersproject/rlp": ^5.7.0 - "@ethersproject/sha2": ^5.7.0 - "@ethersproject/strings": ^5.7.0 - "@ethersproject/transactions": ^5.7.0 - "@ethersproject/web": ^5.7.0 - bech32: 1.1.4 - ws: 7.4.6 - checksum: 1754c731a5ca6782ae9677f4a9cd8b6246c4ef21a966c9a01b133750f3c578431ec43ec254e699969c4a0f87e84463ded50f96b415600aabd37d2056aee58c19 - languageName: node - linkType: hard - "@ethersproject/random@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/random@npm:5.7.0" @@ -1947,7 +1843,7 @@ __metadata: "@metamask/base-controller": "workspace:^" "@metamask/controller-utils": "workspace:^" "@metamask/swappable-obj-proxy": ^2.1.0 - "@metamask/utils": ^5.0.0 + "@metamask/utils": ^3.3.1 "@types/jest": ^26.0.22 "@types/lodash": ^4.14.191 async-mutex: ^0.2.6 @@ -1964,7 +1860,7 @@ __metadata: typedoc: ^0.22.15 typedoc-plugin-missing-exports: ^0.22.6 typescript: ~4.6.3 - uuid: ^9.0.0 + uuid: ^8.3.2 web3-provider-engine: ^16.0.3 languageName: unknown linkType: soft @@ -2174,19 +2070,6 @@ __metadata: languageName: node linkType: hard -"@metamask/utils@npm:^5.0.0": - version: 5.0.0 - resolution: "@metamask/utils@npm:5.0.0" - dependencies: - "@ethereumjs/tx": ^4.1.1 - "@types/debug": ^4.1.7 - debug: ^4.3.4 - semver: ^7.3.8 - superstruct: ^1.0.3 - checksum: 34e39fc0bf28db5fe92676753de3291b05a517f8c81dbe332a4b6002739a58450a89fb2bddd85922a4f420affb1674604e6ad4627cdf052459e5371361ef7dd2 - languageName: node - linkType: hard - "@ngraveio/bc-ur@npm:^1.1.5": version: 1.1.6 resolution: "@ngraveio/bc-ur@npm:1.1.6" @@ -3895,13 +3778,6 @@ __metadata: languageName: node linkType: hard -"case@npm:^1.6.3": - version: 1.6.3 - resolution: "case@npm:1.6.3" - checksum: febe73278f910b0d28aab7efd6f51c235f9aa9e296148edb56dfb83fd58faa88308c30ce9a0122b6e53e0362c44f4407105bd5ef89c46860fc2b184e540fd68d - languageName: node - linkType: hard - "caseless@npm:~0.12.0": version: 0.12.0 resolution: "caseless@npm:0.12.0" @@ -10659,7 +10535,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.3.7, semver@npm:^7.3.8": +"semver@npm:^7.3.7": version: 7.3.8 resolution: "semver@npm:7.3.8" dependencies: @@ -11331,13 +11207,6 @@ __metadata: languageName: node linkType: hard -"superstruct@npm:^1.0.3": - version: 1.0.3 - resolution: "superstruct@npm:1.0.3" - checksum: 761790bb111e6e21ddd608299c252f3be35df543263a7ebbc004e840d01fcf8046794c274bcb351bdf3eae4600f79d317d085cdbb19ca05803a4361840cc9bb1 - languageName: node - linkType: hard - "supports-color@npm:^5.3.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0" @@ -11980,15 +11849,6 @@ __metadata: languageName: node linkType: hard -"uuid@npm:^9.0.0": - version: 9.0.0 - resolution: "uuid@npm:9.0.0" - bin: - uuid: dist/bin/uuid - checksum: 8dd2c83c43ddc7e1c71e36b60aea40030a6505139af6bee0f382ebcd1a56f6cd3028f7f06ffb07f8cf6ced320b76aea275284b224b002b289f89fe89c389b028 - languageName: node - linkType: hard - "v8-compile-cache-lib@npm:^3.0.1": version: 3.0.1 resolution: "v8-compile-cache-lib@npm:3.0.1" From 11b619597e8ea0632c132eb416920944af80d943 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 21 Mar 2023 17:11:50 -0500 Subject: [PATCH 3/8] add test condition --- packages/controller-utils/src/util.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/controller-utils/src/util.test.ts b/packages/controller-utils/src/util.test.ts index 62edbfd43f..64122ff5fd 100644 --- a/packages/controller-utils/src/util.test.ts +++ b/packages/controller-utils/src/util.test.ts @@ -16,6 +16,8 @@ describe('util', () => { expect(util.isSafeChainId(MAX_SAFE_CHAIN_ID + 1)).toBe(false); expect(util.isSafeChainId(MAX_SAFE_CHAIN_ID)).toBe(true); expect(util.isSafeChainId(-1)).toBe(false); + // @ts-expect-error - ensure that string args return false. + expect(util.isSafeChainId('test')).toBe(false); }); it('bNToHex', () => { From 611cbde7c55ea76cf8ad7c0610b91fbb053f0b51 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 22 Mar 2023 11:51:05 -0500 Subject: [PATCH 4/8] rollback rpcTarget -> rpcUrl on providerConfig --- .../src/TokenListController.test.ts | 4 +-- .../src/NetworkController.ts | 27 ++++++++-------- .../tests/NetworkController.test.ts | 32 +++++++++---------- 3 files changed, 32 insertions(+), 31 deletions(-) diff --git a/packages/assets-controllers/src/TokenListController.test.ts b/packages/assets-controllers/src/TokenListController.test.ts index 248d6ea9fa..f8fa20aa0d 100644 --- a/packages/assets-controllers/src/TokenListController.test.ts +++ b/packages/assets-controllers/src/TokenListController.test.ts @@ -1036,7 +1036,7 @@ describe('TokenListController', () => { controllerMessenger.publish('NetworkController:providerConfigChange', { type: 'rpc', chainId: '56', - rpcUrl: 'http://localhost:8545', + rpcTarget: 'http://localhost:8545', }); await new Promise((resolve) => setTimeout(() => resolve(), 500)); @@ -1138,7 +1138,7 @@ describe('TokenListController', () => { controllerMessenger.publish('NetworkController:providerConfigChange', { type: 'rpc', chainId: '56', - rpcUrl: 'http://localhost:8545', + rpcTarget: 'http://localhost:8545', }); }); }); diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 13ee2ee366..14c451cbda 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -26,7 +26,7 @@ import { assertIsStrictHexString } from '@metamask/utils'; * @type ProviderConfig * * Configuration passed to web3-provider-engine - * @property rpcUrl - RPC target URL. + * @property rpcTarget - RPC target URL. * @property type - Human-readable network name. * @property chainId - Network ID as per EIP-155. * @property ticker - Currency ticker. @@ -34,7 +34,7 @@ import { assertIsStrictHexString } from '@metamask/utils'; * @property id - Network Configuration Id. */ export type ProviderConfig = { - rpcUrl?: string; + rpcTarget?: string; type: NetworkType; chainId: string; ticker?: string; @@ -53,7 +53,7 @@ export type NetworkDetails = { /** * Custom RPC network information * - * @property rpcUrl - RPC target URL. + * @property rpcTarget - RPC target URL. * @property chainId - Network ID as per EIP-155 * @property nickname - Personalized network name. * @property ticker - Currency ticker. @@ -242,7 +242,7 @@ export class NetworkController extends BaseControllerV2< private initializeProvider( type: NetworkType, - rpcUrl?: string, + rpcTarget?: string, chainId?: string, ticker?: string, nickname?: string, @@ -261,7 +261,8 @@ export class NetworkController extends BaseControllerV2< this.setupStandardProvider(LOCALHOST_RPC_URL); break; case RPC: - rpcUrl && this.setupStandardProvider(rpcUrl, chainId, ticker, nickname); + rpcTarget && + this.setupStandardProvider(rpcTarget, chainId, ticker, nickname); break; default: throw new Error(`Unrecognized network type: '${type}'`); @@ -284,8 +285,8 @@ export class NetworkController extends BaseControllerV2< state.network = 'loading'; state.networkDetails = {}; }); - const { rpcUrl, type, chainId, ticker } = this.state.providerConfig; - this.initializeProvider(type, rpcUrl, chainId, ticker); + const { rpcTarget, type, chainId, ticker } = this.state.providerConfig; + this.initializeProvider(type, rpcTarget, chainId, ticker); this.lookupNetwork(); } @@ -327,7 +328,7 @@ export class NetworkController extends BaseControllerV2< } private setupStandardProvider( - rpcUrl: string, + rpcTarget: string, chainId?: string, ticker?: string, nickname?: string, @@ -338,7 +339,7 @@ export class NetworkController extends BaseControllerV2< chainId, engineParams: { pollingInterval: 12000 }, nickname, - rpcUrl, + rpcUrl: rpcTarget, ticker, }, }; @@ -373,9 +374,9 @@ export class NetworkController extends BaseControllerV2< */ set providerConfig(providerConfig: ProviderConfig) { this.internalProviderConfig = providerConfig; - const { type, rpcUrl, chainId, ticker, nickname } = + const { type, rpcTarget, chainId, ticker, nickname } = this.state.providerConfig; - this.initializeProvider(type, rpcUrl, chainId, ticker, nickname); + this.initializeProvider(type, rpcTarget, chainId, ticker, nickname); this.registerProvider(); this.lookupNetwork(); } @@ -450,7 +451,7 @@ export class NetworkController extends BaseControllerV2< state.providerConfig.type = type; state.providerConfig.ticker = ticker; state.providerConfig.chainId = NetworksChainId[type]; - state.providerConfig.rpcUrl = undefined; + state.providerConfig.rpcTarget = undefined; state.providerConfig.nickname = undefined; }); this.refreshNetwork(); @@ -473,7 +474,7 @@ export class NetworkController extends BaseControllerV2< this.update((state) => { state.providerConfig.type = RPC; - state.providerConfig.rpcUrl = targetNetwork.rpcUrl; + state.providerConfig.rpcTarget = targetNetwork.rpcUrl; state.providerConfig.chainId = targetNetwork.chainId; state.providerConfig.ticker = targetNetwork.ticker; state.providerConfig.nickname = targetNetwork.nickname; diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index bac19c6d8c..360c52bf28 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -446,7 +446,7 @@ describe('NetworkController', () => { type: 'localhost', chainId: '66666', nickname: "doesn't matter", - rpcUrl: 'http://doesntmatter.com', + rpcTarget: 'http://doesntmatter.com', ticker: 'ABC', }), }, @@ -665,7 +665,7 @@ describe('NetworkController', () => { type: 'rpc', chainId: '123', nickname: 'some cool network', - rpcUrl: 'http://example.com', + rpcTarget: 'http://example.com', ticker: 'ABC', }, }, @@ -716,7 +716,7 @@ describe('NetworkController', () => { state: { providerConfig: buildProviderConfig({ type: 'rpc', - rpcUrl: 'http://example.com', + rpcTarget: 'http://example.com', }), }, }, @@ -750,7 +750,7 @@ describe('NetworkController', () => { state: { providerConfig: buildProviderConfig({ type: 'rpc', - rpcUrl: 'http://example.com', + rpcTarget: 'http://example.com', }), }, }, @@ -822,7 +822,7 @@ describe('NetworkController', () => { state: { providerConfig: buildProviderConfig({ type: 'rpc', - rpcUrl: 'http://example.com', + rpcTarget: 'http://example.com', }), }, }, @@ -1244,7 +1244,7 @@ describe('NetworkController', () => { state: { providerConfig: { type: 'localhost', - rpcUrl: 'http://somethingexisting.com', + rpcTarget: 'http://somethingexisting.com', chainId: '99999', ticker: 'something existing', nickname: 'something existing', @@ -1270,7 +1270,7 @@ describe('NetworkController', () => { type: 'mainnet', ticker: 'ETH', chainId: '1', - rpcUrl: undefined, + rpcTarget: undefined, nickname: undefined, }); }, @@ -1582,7 +1582,7 @@ describe('NetworkController', () => { state: { providerConfig: { type: 'localhost', - rpcUrl: 'http://somethingexisting.com', + rpcTarget: 'http://somethingexisting.com', chainId: '99999', ticker: 'something existing', nickname: 'something existing', @@ -1608,7 +1608,7 @@ describe('NetworkController', () => { type: networkType, ticker, chainId, - rpcUrl: undefined, + rpcTarget: undefined, nickname: undefined, }); }, @@ -1908,7 +1908,7 @@ describe('NetworkController', () => { state: { providerConfig: { type: 'localhost', - rpcUrl: 'http://somethingexisting.com', + rpcTarget: 'http://somethingexisting.com', chainId: '99999', ticker: 'something existing', nickname: 'something existing', @@ -1930,7 +1930,7 @@ describe('NetworkController', () => { type: 'rpc', ticker: 'ETH', chainId: '', - rpcUrl: undefined, + rpcTarget: undefined, nickname: undefined, }); }, @@ -2022,7 +2022,7 @@ describe('NetworkController', () => { state: { providerConfig: { type: 'localhost', - rpcUrl: 'http://somethingexisting.com', + rpcTarget: 'http://somethingexisting.com', chainId: '99999', ticker: 'something existing', nickname: 'something existing', @@ -2044,7 +2044,7 @@ describe('NetworkController', () => { type: 'localhost', ticker: 'ETH', chainId: '', - rpcUrl: undefined, + rpcTarget: undefined, nickname: undefined, }); }, @@ -2291,7 +2291,7 @@ describe('NetworkController', () => { describe('setActiveNetwork', () => { describe('given only a networkConfigurationId', () => { - it('updates the provider config in state with the rpcUrl and chainId, clearing the previous provider details', async () => { + it('updates the provider config in state with the rpcTarget and chainId, clearing the previous provider details', async () => { const messenger = buildMessenger(); await withController( { @@ -2299,7 +2299,7 @@ describe('NetworkController', () => { state: { providerConfig: { type: 'localhost', - rpcUrl: 'http://somethingexisting.com', + rpcTarget: 'http://somethingexisting.com', chainId: '99999', ticker: 'something existing', nickname: 'something existing', @@ -2334,7 +2334,7 @@ describe('NetworkController', () => { expect(controller.state.providerConfig).toStrictEqual({ type: 'rpc', - rpcUrl: 'https://mock-rpc-url', + rpcTarget: 'https://mock-rpc-url', chainId: '0xtest', ticker: 'TEST', id: 'testNetworkConfigurationId', From e7b76af01c1ca3549db96775da414ebf6c0fe3ff Mon Sep 17 00:00:00 2001 From: Alex Donesky Date: Wed, 22 Mar 2023 12:36:56 -0500 Subject: [PATCH 5/8] Update packages/controller-utils/src/constants.ts Co-authored-by: Mark Stacey --- packages/controller-utils/src/constants.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/controller-utils/src/constants.ts b/packages/controller-utils/src/constants.ts index 90df5981f3..b09b8664e6 100644 --- a/packages/controller-utils/src/constants.ts +++ b/packages/controller-utils/src/constants.ts @@ -7,6 +7,10 @@ export const IPFS_DEFAULT_GATEWAY_URL = 'https://cloudflare-ipfs.com/ipfs/'; // NETWORKS ID export const GANACHE_CHAIN_ID = '1337'; +/** + * The largest possible chain ID we can handle. + * Explanation: https://gist.github.com/rekmarks/a47bd5f2525936c4b8eee31a16345553 + */ export const MAX_SAFE_CHAIN_ID = 4503599627370476; // TOKEN STANDARDS From ceef493dd0932764945d95460478a060b0e08ea5 Mon Sep 17 00:00:00 2001 From: Alex Donesky Date: Wed, 22 Mar 2023 12:37:07 -0500 Subject: [PATCH 6/8] Update packages/controller-utils/src/util.test.ts Co-authored-by: Mark Stacey --- packages/controller-utils/src/util.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/controller-utils/src/util.test.ts b/packages/controller-utils/src/util.test.ts index 64122ff5fd..ec5b3ad935 100644 --- a/packages/controller-utils/src/util.test.ts +++ b/packages/controller-utils/src/util.test.ts @@ -15,7 +15,7 @@ describe('util', () => { it('isSafeChainId', () => { expect(util.isSafeChainId(MAX_SAFE_CHAIN_ID + 1)).toBe(false); expect(util.isSafeChainId(MAX_SAFE_CHAIN_ID)).toBe(true); - expect(util.isSafeChainId(-1)).toBe(false); + expect(util.isSafeChainId(0)).toBe(false); // @ts-expect-error - ensure that string args return false. expect(util.isSafeChainId('test')).toBe(false); }); From 0124a625a57d81ae00c1dcaa03e880c62f6ff14e Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 22 Mar 2023 13:42:14 -0500 Subject: [PATCH 7/8] address feedback --- .../src/NetworkController.ts | 4 +- .../tests/NetworkController.test.ts | 1458 +++++++++++------ .../tests/provider-api-tests/helpers.ts | 21 +- 3 files changed, 1010 insertions(+), 473 deletions(-) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 14c451cbda..710cd34a48 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -553,7 +553,7 @@ export class NetworkController extends BaseControllerV2< * @param options - additional configuration options. * @param options.setActive - An option to set the newly added networkConfiguration as the active provider. * @param options.referrer - The site from which the call originated, or 'metamask' for internal calls - used for event metrics. - * @param options.source - Where the upsertNetwork event originated (i.e. from a dapp or from the network form)- used for event metrics. + * @param options.source - Where the upsertNetwork event originated (i.e. from a dapp or from the network form) - used for event metrics. * @returns id for the added or updated network configuration */ upsertNetworkConfiguration( @@ -563,7 +563,7 @@ export class NetworkController extends BaseControllerV2< referrer, source, }: { setActive?: boolean; referrer: string; source: string }, - ) { + ): string { assertIsStrictHexString(chainId); if (!isSafeChainId(parseInt(chainId, 16))) { diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index 360c52bf28..faafdd7fd8 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -10,6 +10,7 @@ import type { ProviderEngine } from 'web3-provider-engine'; import createMetamaskProvider from 'web3-provider-engine/zero'; import { Patch } from 'immer'; import { v4 } from 'uuid'; +import { NetworkType } from '@metamask/controller-utils'; import { waitForResult } from '../../../tests/helpers'; import { NetworkController, @@ -2290,398 +2291,392 @@ describe('NetworkController', () => { }); describe('setActiveNetwork', () => { - describe('given only a networkConfigurationId', () => { - it('updates the provider config in state with the rpcTarget and chainId, clearing the previous provider details', async () => { - const messenger = buildMessenger(); - await withController( - { - messenger, - state: { - providerConfig: { - type: 'localhost', - rpcTarget: 'http://somethingexisting.com', + it('updates the provider config in state with the rpcTarget and chainId, clearing the previous provider details', async () => { + const messenger = buildMessenger(); + await withController( + { + messenger, + state: { + providerConfig: { + type: 'localhost', + rpcTarget: 'http://somethingexisting.com', + chainId: '99999', + ticker: 'something existing', + nickname: 'something existing', + }, + networkConfigurations: { + testNetworkConfigurationId: { + rpcUrl: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', + }, + testNetworkConfigurationId2: { + rpcUrl: 'http://somethingexisting.com', chainId: '99999', ticker: 'something existing', nickname: 'something existing', - }, - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: '0xtest', - ticker: 'TEST', - id: 'testNetworkConfigurationId', - }, - testNetworkConfigurationId2: { - rpcUrl: 'http://somethingexisting.com', - chainId: '99999', - ticker: 'something existing', - nickname: 'something existing', - id: 'testNetworkConfigurationId2', - }, + id: 'testNetworkConfigurationId2', }, }, }, - async ({ controller }) => { - const fakeMetamaskProvider = buildFakeMetamaskProvider(); - createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); + }, + async ({ controller }) => { + const fakeMetamaskProvider = buildFakeMetamaskProvider(); + createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); - await waitForStateChanges(messenger, { - propertyPath: ['network'], - produceStateChanges: () => { - controller.setActiveNetwork('testNetworkConfigurationId'); - }, - }); + await waitForStateChanges(messenger, { + propertyPath: ['network'], + produceStateChanges: () => { + controller.setActiveNetwork('testNetworkConfigurationId'); + }, + }); - expect(controller.state.providerConfig).toStrictEqual({ - type: 'rpc', - rpcTarget: 'https://mock-rpc-url', - chainId: '0xtest', - ticker: 'TEST', - id: 'testNetworkConfigurationId', - nickname: undefined, - }); - }, - ); - }); + expect(controller.state.providerConfig).toStrictEqual({ + type: 'rpc', + rpcTarget: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', + nickname: undefined, + }); + }, + ); + }); - it('sets isCustomNetwork in state to true', async () => { - const messenger = buildMessenger(); - await withController( - { - messenger, - state: { - isCustomNetwork: false, - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: '0xtest', - ticker: 'TEST', - id: 'testNetworkConfigurationId', - }, + it('sets isCustomNetwork in state to true', async () => { + const messenger = buildMessenger(); + await withController( + { + messenger, + state: { + isCustomNetwork: false, + networkConfigurations: { + testNetworkConfigurationId: { + rpcUrl: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', }, }, }, - async ({ controller }) => { - const fakeMetamaskProvider = buildFakeMetamaskProvider(); - createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); + }, + async ({ controller }) => { + const fakeMetamaskProvider = buildFakeMetamaskProvider(); + createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); - await waitForStateChanges(messenger, { - propertyPath: ['isCustomNetwork'], - produceStateChanges: () => { - controller.setActiveNetwork('testNetworkConfigurationId'); - }, - }); + await waitForStateChanges(messenger, { + propertyPath: ['isCustomNetwork'], + produceStateChanges: () => { + controller.setActiveNetwork('testNetworkConfigurationId'); + }, + }); - expect(controller.state.isCustomNetwork).toBe(true); - }, - ); - }); + expect(controller.state.isCustomNetwork).toBe(true); + }, + ); + }); - it('sets the provider to a custom RPC provider initialized with the RPC target and chain ID, leaving nickname and ticker undefined', async () => { - const messenger = buildMessenger(); - await withController( - { - messenger, - state: { - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: '0xtest', - ticker: 'TEST', - id: 'testNetworkConfigurationId', - }, + it('sets the provider to a custom RPC provider initialized with the RPC target and chain ID, leaving nickname and ticker undefined', async () => { + const messenger = buildMessenger(); + await withController( + { + messenger, + state: { + networkConfigurations: { + testNetworkConfigurationId: { + rpcUrl: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', }, }, }, - async ({ controller }) => { - const fakeMetamaskProvider = buildFakeMetamaskProvider([ - { - request: { - method: 'eth_chainId', - }, - response: { - result: '0x1337', - }, + }, + async ({ controller }) => { + const fakeMetamaskProvider = buildFakeMetamaskProvider([ + { + request: { + method: 'eth_chainId', }, - ]); - createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); + response: { + result: '0x1337', + }, + }, + ]); + createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); - controller.setActiveNetwork('testNetworkConfigurationId'); + controller.setActiveNetwork('testNetworkConfigurationId'); - expect(createMetamaskProviderMock).toHaveBeenCalledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: '0xtest', - ticker: 'TEST', - nickname: undefined, - engineParams: { pollingInterval: 12000 }, - }); - const { provider } = controller.getProviderAndBlockTracker(); - const promisifiedSendAsync = promisify(provider.sendAsync).bind( - provider, - ); - const chainIdResult = await promisifiedSendAsync({ - method: 'eth_chainId', - }); - expect(chainIdResult.result).toBe('0x1337'); - }, - ); - }); + expect(createMetamaskProviderMock).toHaveBeenCalledWith({ + rpcUrl: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + nickname: undefined, + engineParams: { pollingInterval: 12000 }, + }); + const { provider } = controller.getProviderAndBlockTracker(); + const promisifiedSendAsync = promisify(provider.sendAsync).bind( + provider, + ); + const chainIdResult = await promisifiedSendAsync({ + method: 'eth_chainId', + }); + expect(chainIdResult.result).toBe('0x1337'); + }, + ); + }); - it('updates networkDetails.isEIP1559Compatible in state based on the latest block (assuming that the request for eth_getBlockByNumber is made successfully)', async () => { - const messenger = buildMessenger(); - await withController( - { - messenger, - state: { - isCustomNetwork: false, - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: '0xtest', - ticker: 'TEST', - id: 'testNetworkConfigurationId', - }, + it('updates networkDetails.isEIP1559Compatible in state based on the latest block (assuming that the request for eth_getBlockByNumber is made successfully)', async () => { + const messenger = buildMessenger(); + await withController( + { + messenger, + state: { + isCustomNetwork: false, + networkConfigurations: { + testNetworkConfigurationId: { + rpcUrl: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', }, }, }, - async ({ controller }) => { - const fakeMetamaskProvider = buildFakeMetamaskProvider([ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: { - baseFeePerGas: '0x1', - }, + }, + async ({ controller }) => { + const fakeMetamaskProvider = buildFakeMetamaskProvider([ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + response: { + result: { + baseFeePerGas: '0x1', }, }, - ]); - createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); + }, + ]); + createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); - await waitForStateChanges(messenger, { - propertyPath: ['networkDetails', 'isEIP1559Compatible'], - produceStateChanges: () => { - controller.setActiveNetwork('testNetworkConfigurationId'); - }, - }); + await waitForStateChanges(messenger, { + propertyPath: ['networkDetails', 'isEIP1559Compatible'], + produceStateChanges: () => { + controller.setActiveNetwork('testNetworkConfigurationId'); + }, + }); - expect(controller.state.networkDetails.isEIP1559Compatible).toBe( - true, - ); - }, - ); - }); + expect(controller.state.networkDetails.isEIP1559Compatible).toBe( + true, + ); + }, + ); + }); - it('ensures that the existing provider is stopped while replacing it', async () => { - const messenger = buildMessenger(); - await withController( - { - messenger, - state: { - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: '0xtest', - ticker: 'TEST', - id: 'testNetworkConfigurationId', - }, + it('ensures that the existing provider is stopped while replacing it', async () => { + const messenger = buildMessenger(); + await withController( + { + messenger, + state: { + networkConfigurations: { + testNetworkConfigurationId: { + rpcUrl: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', }, }, }, - ({ controller }) => { - const fakeMetamaskProviders = [ - buildFakeMetamaskProvider(), - buildFakeMetamaskProvider(), - ]; - jest.spyOn(fakeMetamaskProviders[0], 'stop'); - createMetamaskProviderMock - .mockImplementationOnce(() => fakeMetamaskProviders[0]) - .mockImplementationOnce(() => fakeMetamaskProviders[1]); + }, + ({ controller }) => { + const fakeMetamaskProviders = [ + buildFakeMetamaskProvider(), + buildFakeMetamaskProvider(), + ]; + jest.spyOn(fakeMetamaskProviders[0], 'stop'); + createMetamaskProviderMock + .mockImplementationOnce(() => fakeMetamaskProviders[0]) + .mockImplementationOnce(() => fakeMetamaskProviders[1]); - controller.setActiveNetwork('testNetworkConfigurationId'); - controller.setActiveNetwork('testNetworkConfigurationId'); - assert(controller.getProviderAndBlockTracker().provider); - jest.runAllTimers(); + controller.setActiveNetwork('testNetworkConfigurationId'); + controller.setActiveNetwork('testNetworkConfigurationId'); + assert(controller.getProviderAndBlockTracker().provider); + jest.runAllTimers(); - expect(fakeMetamaskProviders[0].stop).toHaveBeenCalled(); - }, - ); - }); + expect(fakeMetamaskProviders[0].stop).toHaveBeenCalled(); + }, + ); + }); - it('updates the version of the current network in state (assuming that the request for net_version is made successfully)', async () => { - const messenger = buildMessenger(); - await withController( - { - messenger, - state: { - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: '0xtest', - ticker: 'TEST', - id: 'testNetworkConfigurationId', - }, + it('updates the version of the current network in state (assuming that the request for net_version is made successfully)', async () => { + const messenger = buildMessenger(); + await withController( + { + messenger, + state: { + networkConfigurations: { + testNetworkConfigurationId: { + rpcUrl: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', }, }, }, - async ({ controller }) => { - const fakeMetamaskProvider = buildFakeMetamaskProvider([ - { - request: { - method: 'net_version', - params: [], - }, - response: { - result: '42', - }, + }, + async ({ controller }) => { + const fakeMetamaskProvider = buildFakeMetamaskProvider([ + { + request: { + method: 'net_version', + params: [], }, - ]); - createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); - - await waitForStateChanges(messenger, { - propertyPath: ['network'], - produceStateChanges: () => { - controller.setActiveNetwork('testNetworkConfigurationId'); + response: { + result: '42', }, - }); + }, + ]); + createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); - expect(controller.state.network).toBe('42'); - }, - ); - }); + await waitForStateChanges(messenger, { + propertyPath: ['network'], + produceStateChanges: () => { + controller.setActiveNetwork('testNetworkConfigurationId'); + }, + }); - describe('when an "error" event occurs on the new provider', () => { - describe('if the network version could not be retrieved during the call to setActiveNetwork', () => { - it('retrieves the network version again and, assuming success, persists it to state', async () => { - const messenger = buildMessenger(); - await withController( - { - messenger, - state: { - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: '0xtest', - ticker: 'TEST', - id: 'testNetworkConfigurationId', - }, + expect(controller.state.network).toBe('42'); + }, + ); + }); + + describe('when an "error" event occurs on the new provider', () => { + describe('if the network version could not be retrieved during the call to setActiveNetwork', () => { + it('retrieves the network version again and, assuming success, persists it to state', async () => { + const messenger = buildMessenger(); + await withController( + { + messenger, + state: { + networkConfigurations: { + testNetworkConfigurationId: { + rpcUrl: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', }, }, }, - async ({ controller }) => { - const fakeMetamaskProvider = buildFakeMetamaskProvider([ - { - request: { - method: 'net_version', - }, - response: { - error: 'oops', - }, + }, + async ({ controller }) => { + const fakeMetamaskProvider = buildFakeMetamaskProvider([ + { + request: { + method: 'net_version', }, - { - request: { - method: 'net_version', - }, - response: { - result: '42', - }, + response: { + error: 'oops', }, - ]); - createMetamaskProviderMock.mockReturnValue( - fakeMetamaskProvider, - ); - - await waitForPublishedEvents( - messenger, - 'NetworkController:providerConfigChange', - { - produceEvents: () => { - controller.setActiveNetwork('testNetworkConfigurationId'); - assert(controller.getProviderAndBlockTracker().provider); - }, + }, + { + request: { + method: 'net_version', }, - ); + response: { + result: '42', + }, + }, + ]); + createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); - await waitForStateChanges(messenger, { - propertyPath: ['network'], - produceStateChanges: () => { - controller - .getProviderAndBlockTracker() - .provider.emit('error', { some: 'error' }); + await waitForPublishedEvents( + messenger, + 'NetworkController:providerConfigChange', + { + produceEvents: () => { + controller.setActiveNetwork('testNetworkConfigurationId'); + assert(controller.getProviderAndBlockTracker().provider); }, - }); - expect(controller.state.network).toBe('42'); - }, - ); - }); + }, + ); + + await waitForStateChanges(messenger, { + propertyPath: ['network'], + produceStateChanges: () => { + controller + .getProviderAndBlockTracker() + .provider.emit('error', { some: 'error' }); + }, + }); + expect(controller.state.network).toBe('42'); + }, + ); }); + }); - describe('if the network version could be retrieved during the call to setActiveNetwork', () => { - it('does not retrieve the network version again', async () => { - const messenger = buildMessenger(); - await withController( - { - messenger, - state: { - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: '0xtest', - ticker: 'TEST', - id: 'testNetworkConfigurationId', - }, + describe('if the network version could be retrieved during the call to setActiveNetwork', () => { + it('does not retrieve the network version again', async () => { + const messenger = buildMessenger(); + await withController( + { + messenger, + state: { + networkConfigurations: { + testNetworkConfigurationId: { + rpcUrl: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', }, }, }, - async ({ controller }) => { - const fakeMetamaskProvider = buildFakeMetamaskProvider([ - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, + }, + async ({ controller }) => { + const fakeMetamaskProvider = buildFakeMetamaskProvider([ + { + request: { + method: 'net_version', }, - { - request: { - method: 'net_version', - }, - response: { - result: '2', - }, + response: { + result: '1', }, - ]); - createMetamaskProviderMock.mockReturnValue( - fakeMetamaskProvider, - ); - - await waitForPublishedEvents( - messenger, - 'NetworkController:providerConfigChange', - { - produceEvents: () => { - controller.setActiveNetwork('testNetworkConfigurationId'); - assert(controller.getProviderAndBlockTracker().provider); - }, + }, + { + request: { + method: 'net_version', }, - ); + response: { + result: '2', + }, + }, + ]); + createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); - await waitForStateChanges(messenger, { - propertyPath: ['network'], - count: 0, - produceStateChanges: () => { - controller - .getProviderAndBlockTracker() - .provider.emit('error', { some: 'error' }); + await waitForPublishedEvents( + messenger, + 'NetworkController:providerConfigChange', + { + produceEvents: () => { + controller.setActiveNetwork('testNetworkConfigurationId'); + assert(controller.getProviderAndBlockTracker().provider); }, - }); - expect(controller.state.network).toBe('1'); - }, - ); - }); + }, + ); + + await waitForStateChanges(messenger, { + propertyPath: ['network'], + count: 0, + produceStateChanges: () => { + controller + .getProviderAndBlockTracker() + .provider.emit('error', { some: 'error' }); + }, + }); + expect(controller.state.network).toBe('1'); + }, + ); }); }); }); @@ -3464,99 +3459,475 @@ describe('NetworkController', () => { ); }); }); - }); + }); + + describe('NetworkController:getProviderConfig action', () => { + it('returns the provider config in state', async () => { + const messenger = buildMessenger(); + await withController( + { + messenger, + state: { + providerConfig: { + type: 'mainnet', + chainId: '1', + }, + }, + }, + async () => { + const providerConfig = await messenger.call( + 'NetworkController:getProviderConfig', + ); + + expect(providerConfig).toStrictEqual({ + type: 'mainnet', + chainId: '1', + }); + }, + ); + }); + }); + + describe('NetworkController:getEthQuery action', () => { + it('returns the EthQuery object set after the provider is set', async () => { + const messenger = buildMessenger(); + await withController({ messenger }, async ({ controller }) => { + const fakeEthQuery = { + sendAsync: jest.fn(), + }; + jest.spyOn(ethQueryModule, 'default').mockReturnValue(fakeEthQuery); + setFakeProvider(controller); + + const ethQuery = await messenger.call('NetworkController:getEthQuery'); + + expect(ethQuery).toBe(fakeEthQuery); + }); + }); + }); + + describe('upsertNetworkConfiguration', () => { + it('adds the given network configuration when its rpcURL does not match an existing configuration', async () => { + (v4 as jest.Mock).mockImplementationOnce( + () => 'network-configuration-id-1', + ); + const messenger = buildMessenger(); + await withController({ messenger }, async ({ controller }) => { + const rpcUrlNetwork = { + chainId: '0x9999', + rpcUrl: 'https://test-rpc.com', + ticker: 'RPC', + }; + + expect(controller.state.networkConfigurations).toStrictEqual({}); + + controller.upsertNetworkConfiguration(rpcUrlNetwork, { + referrer: 'https://test-dapp.com', + source: 'dapp', + }); + + expect( + Object.values(controller.state.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: '0x1', + id: 'testNetworkConfigurationId', + }, + }, + }, + }, + async ({ controller }) => { + controller.upsertNetworkConfiguration( + { + rpcUrl: 'https://rpc-url.com', + ticker: 'new_rpc_ticker', + nickname: 'new_rpc_nickname', + rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, + chainId: '0x1', + }, + { referrer: 'https://test-dapp.com', source: 'dapp' }, + ); + expect( + Object.values(controller.state.networkConfigurations), + ).toStrictEqual( + expect.arrayContaining([ + { + rpcUrl: 'https://rpc-url.com', + nickname: 'new_rpc_nickname', + ticker: 'new_rpc_ticker', + rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, + chainId: '0x1', + id: 'testNetworkConfigurationId', + }, + ]), + ); + }, + ); + }); + + it('throws if the given chain ID is not a 0x-prefixed hex number', async () => { + const invalidChainId = '1'; + await withController(async ({ controller }) => { + expect(() => + controller.upsertNetworkConfiguration( + { + chainId: invalidChainId, + nickname: 'RPC', + rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, + rpcUrl: 'rpc_url', + ticker: 'RPC', + }, + { + referrer: 'https://test-dapp.com', + source: 'dapp', + }, + ), + ).toThrow( + new Error('Value must be a hexadecimal string, starting with "0x".'), + ); + }); + }); + + it('throws if the given chain ID is greater than the maximum allowed ID', async () => { + await withController(async ({ controller }) => { + expect(() => + controller.upsertNetworkConfiguration( + { + chainId: '0xFFFFFFFFFFFFFFFF', + nickname: 'RPC', + rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, + rpcUrl: 'rpc_url', + ticker: 'RPC', + }, + { + referrer: 'https://test-dapp.com', + source: 'dapp', + }, + ), + ).toThrow( + new Error( + 'Invalid chain ID "0xFFFFFFFFFFFFFFFF": numerical value greater than max safe value.', + ), + ); + }); + }); + + it('throws if rpcUrl passed is not a valid Url', async () => { + await withController(async ({ controller }) => { + expect(() => + controller.upsertNetworkConfiguration( + { + chainId: '0x9999', + nickname: 'RPC', + rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, + ticker: 'RPC', + rpcUrl: 'test', + }, + { + referrer: 'https://test-dapp.com', + source: 'dapp', + }, + ), + ).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 }) => { + expect(() => + controller.upsertNetworkConfiguration( + // @ts-expect-error - we want to test the case where no ticker is present. + { + chainId: '0x5', + nickname: 'RPC', + rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, + rpcUrl: 'https://mock-rpc-url', + }, + { + referrer: 'https://test-dapp.com', + source: 'dapp', + }, + ), + ).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 }) => { + expect(() => + // @ts-expect-error - we want to test the case where no second arg is passed. + controller.upsertNetworkConfiguration({ + chainId: '0x5', + nickname: 'RPC', + rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, + rpcUrl: 'https://mock-rpc-url', + }), + ).toThrow( + new Error( + "Cannot read properties of undefined (reading 'setActive')", + ), + ); + }); + }); + + it('throws if referrer and source arguments are not passed', async () => { + (v4 as jest.Mock).mockImplementationOnce(() => 'networkConfigurationId'); + const trackEventSpy = jest.fn(); + await withController( + { + state: { + providerConfig: { + type: 'rpc', + rpcTarget: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', + }, + networkConfigurations: { + testNetworkConfigurationId: { + rpcUrl: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', + }, + }, + }, + trackMetaMetricsEvent: trackEventSpy, + }, + async ({ controller }) => { + const newNetworkConfiguration = { + rpcUrl: 'https://new-chain-rpc-url', + chainId: '0x9999', + ticker: 'NEW', + nickname: 'new-chain', + rpcPrefs: { blockExplorerUrl: 'https://block-explorer' }, + }; + + expect(() => + // @ts-expect-error - we want to test the case where the options object is empty. + controller.upsertNetworkConfiguration(newNetworkConfiguration, {}), + ).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 () => { + (v4 as jest.Mock).mockImplementationOnce(() => 'networkConfigurationId'); + await withController( + { + state: { + networkConfigurations: {}, + }, + }, + async ({ controller }) => { + const rpcUrlNetwork = { + chainId: '0x1', + rpcUrl: 'https://test-rpc-url', + ticker: 'test_ticker', + }; + + controller.upsertNetworkConfiguration(rpcUrlNetwork, { + referrer: 'https://test-dapp.com', + source: 'dapp', + }); + + expect( + Object.values(controller.state.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 () { + (v4 as jest.Mock).mockImplementationOnce(() => 'networkConfigurationId'); + await withController( + { + state: { + networkConfigurations: {}, + }, + }, + async ({ controller }) => { + const rpcUrlNetwork = { + chainId: '0x1', + rpcUrl: 'https://test-rpc-url', + ticker: 'test_ticker', + invalidKey: 'new-chain', + invalidKey2: {}, + }; + + controller.upsertNetworkConfiguration(rpcUrlNetwork, { + referrer: 'https://test-dapp.com', + source: 'dapp', + }); + + expect( + Object.values(controller.state.networkConfigurations), + ).toStrictEqual( + expect.arrayContaining([ + { + chainId: '0x1', + rpcUrl: 'https://test-rpc-url', + ticker: 'test_ticker', + nickname: undefined, + rpcPrefs: undefined, + id: 'networkConfigurationId', + }, + ]), + ); + }, + ); + }); - describe('NetworkController:getProviderConfig action', () => { - it('returns the provider config in state', async () => { - const messenger = buildMessenger(); + it('should add the given network configuration if its rpcURL does not match an existing configuration without changing or overwriting other configurations', async () => { + (v4 as jest.Mock).mockImplementationOnce(() => 'networkConfigurationId2'); await withController( { - messenger, state: { - providerConfig: { - type: 'mainnet', - chainId: '1', + networkConfigurations: { + networkConfigurationId: { + rpcUrl: 'https://test-rpc-url', + ticker: 'ticker', + nickname: 'nickname', + rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, + chainId: '0x1', + id: 'networkConfigurationId', + }, }, }, }, - async () => { - const providerConfig = await messenger.call( - 'NetworkController:getProviderConfig', - ); - - expect(providerConfig).toStrictEqual({ - type: 'mainnet', - chainId: '1', + async ({ controller }) => { + const rpcUrlNetwork = { + chainId: '0x1', + nickname: 'RPC', + rpcPrefs: undefined, + rpcUrl: 'https://test-rpc-url-2', + ticker: 'RPC', + }; + + controller.upsertNetworkConfiguration(rpcUrlNetwork, { + referrer: 'https://test-dapp.com', + source: 'dapp', }); + + expect( + Object.values(controller.state.networkConfigurations), + ).toStrictEqual( + expect.arrayContaining([ + { + rpcUrl: 'https://test-rpc-url', + ticker: 'ticker', + nickname: 'nickname', + rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, + chainId: '0x1', + id: 'networkConfigurationId', + }, + { ...rpcUrlNetwork, id: 'networkConfigurationId2' }, + ]), + ); }, ); }); - }); - - describe('NetworkController:getEthQuery action', () => { - it('returns the EthQuery object set after the provider is set', async () => { - const messenger = buildMessenger(); - await withController({ messenger }, async ({ controller }) => { - const fakeEthQuery = { - sendAsync: jest.fn(), - }; - jest.spyOn(ethQueryModule, 'default').mockReturnValue(fakeEthQuery); - setFakeProvider(controller); - - const ethQuery = await messenger.call('NetworkController:getEthQuery'); - - expect(ethQuery).toBe(fakeEthQuery); - }); - }); - }); - - describe('upsertNetworkConfiguration', () => { - it('adds the given network configuration when its rpcURL does not match an existing configuration', async () => { - (v4 as jest.Mock).mockImplementationOnce( - () => 'network-configuration-id-1', - ); - const messenger = buildMessenger(); - await withController({ messenger }, async ({ controller }) => { - const rpcUrlNetwork = { - chainId: '0x9999', - rpcUrl: 'https://test-rpc.com', - ticker: 'RPC', - }; - - expect(controller.state.networkConfigurations).toStrictEqual({}); - controller.upsertNetworkConfiguration(rpcUrlNetwork, { - referrer: 'https://test-dapp.com', - source: 'dapp', - }); + 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: '0x1', + id: 'networkConfigurationId', + }, + }, + }, + }, - expect( - Object.values(controller.state.networkConfigurations), - ).toStrictEqual( - expect.arrayContaining([ + async ({ controller }) => { + const updatedConfiguration = { + rpcUrl: 'https://test-rpc-url', + ticker: 'new_rpc_ticker', + nickname: 'new_rpc_chainName', + rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, + chainId: '0x1', + }; + controller.upsertNetworkConfiguration(updatedConfiguration, { + referrer: 'https://test-dapp.com', + source: 'dapp', + }); + expect( + Object.values(controller.state.networkConfigurations), + ).toStrictEqual([ { - ...rpcUrlNetwork, - nickname: undefined, - rpcPrefs: undefined, - id: 'network-configuration-id-1', + rpcUrl: 'https://test-rpc-url', + nickname: 'new_rpc_chainName', + ticker: 'new_rpc_ticker', + rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, + chainId: '0x1', + id: 'networkConfigurationId', }, - ]), - ); - }); + ]); + }, + ); }); - it('update a network configuration when the configuration being added has an rpcURL that matches an existing configuration', async () => { + 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: { - testNetworkConfigurationId: { - rpcUrl: 'https://rpc-url.com', - ticker: 'old_rpc_ticker', - nickname: 'old_rpc_nickname', + networkConfigurationId: { + rpcUrl: 'https://test-rpc-url', + ticker: 'ticker', + nickname: 'nickname', rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, chainId: '0x1', - id: 'testNetworkConfigurationId', + id: 'networkConfigurationId', + }, + networkConfigurationId2: { + rpcUrl: 'https://test-rpc-url-2', + ticker: 'ticker-2', + nickname: 'nickname-2', + rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, + chainId: '0x9999', + id: 'networkConfigurationId2', }, }, }, @@ -3564,86 +3935,255 @@ describe('NetworkController', () => { async ({ controller }) => { controller.upsertNetworkConfiguration( { - rpcUrl: 'https://rpc-url.com', - ticker: 'new_rpc_ticker', - nickname: 'new_rpc_nickname', + rpcUrl: 'https://test-rpc-url', + ticker: 'new-ticker', + nickname: 'new-nickname', rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, chainId: '0x1', }, - { referrer: 'https://test-dapp.com', source: 'dapp' }, + { + referrer: 'https://test-dapp.com', + source: 'dapp', + }, ); + expect( Object.values(controller.state.networkConfigurations), - ).toStrictEqual( - expect.arrayContaining([ - { - rpcUrl: 'https://rpc-url.com', - nickname: 'new_rpc_nickname', - ticker: 'new_rpc_ticker', - rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, - chainId: '0x1', + ).toStrictEqual([ + { + rpcUrl: 'https://test-rpc-url', + ticker: 'new-ticker', + nickname: 'new-nickname', + rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, + chainId: '0x1', + id: 'networkConfigurationId', + }, + { + rpcUrl: 'https://test-rpc-url-2', + ticker: 'ticker-2', + nickname: 'nickname-2', + rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, + chainId: '0x9999', + 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 () => { + (v4 as jest.Mock).mockImplementationOnce(() => 'networkConfigurationId'); + const originalProvider = { + type: 'rpc' as NetworkType, + rpcTarget: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', + }; + await withController( + { + state: { + providerConfig: originalProvider, + networkConfigurations: { + testNetworkConfigurationId: { + rpcUrl: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', id: 'testNetworkConfigurationId', }, - ]), + }, + }, + }, + async ({ controller }) => { + const rpcUrlNetwork = { + chainId: '0x1', + rpcUrl: 'https://test-rpc-url', + ticker: 'test_ticker', + }; + + controller.upsertNetworkConfiguration(rpcUrlNetwork, { + referrer: 'https://test-dapp.com', + source: 'dapp', + }); + + expect(controller.state.providerConfig).toStrictEqual( + originalProvider, ); }, ); }); - 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: '1', - id: testNetworkConfigurationId, - }, + it('should add the given network and set it to active if the setActive option is passed as true', async () => { + (v4 as jest.Mock).mockImplementationOnce(() => 'networkConfigurationId'); + await withController( + { + state: { + providerConfig: { + type: 'rpc', + rpcTarget: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', + }, + networkConfigurations: { + testNetworkConfigurationId: { + rpcUrl: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', }, }, }, - async ({ controller }) => { - controller.removeNetworkConfiguration(testNetworkConfigurationId); - expect(controller.state.networkConfigurations).toStrictEqual({}); + }, + async ({ controller }) => { + const fakeMetamaskProvider = buildFakeMetamaskProvider(); + createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); + const rpcUrlNetwork = { + rpcUrl: 'https://test-rpc-url', + chainId: '0x1', + ticker: 'test_ticker', + }; + + controller.upsertNetworkConfiguration(rpcUrlNetwork, { + setActive: true, + referrer: 'https://test-dapp.com', + source: 'dapp', + }); + + expect(controller.state.providerConfig).toStrictEqual({ + type: 'rpc', + rpcTarget: 'https://test-rpc-url', + chainId: '0x1', + ticker: 'test_ticker', + id: 'networkConfigurationId', + nickname: undefined, + }); + }, + ); + }); + + it('adds new networkConfiguration to networkController store and calls to the metametrics event tracking with the correct values', async () => { + (v4 as jest.Mock).mockImplementationOnce(() => 'networkConfigurationId'); + const trackEventSpy = jest.fn(); + await withController( + { + state: { + providerConfig: { + type: 'rpc', + rpcTarget: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', + }, + networkConfigurations: { + testNetworkConfigurationId: { + rpcUrl: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', + }, + }, }, - ); - }); + trackMetaMetricsEvent: trackEventSpy, + }, + async ({ controller }) => { + const newNetworkConfiguration = { + rpcUrl: 'https://new-chain-rpc-url', + chainId: '0x9999', + ticker: 'NEW', + nickname: 'new-chain', + rpcPrefs: { blockExplorerUrl: 'https://block-explorer' }, + }; + + controller.upsertNetworkConfiguration(newNetworkConfiguration, { + referrer: 'https://test-dapp.com', + source: 'dapp', + }); - 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: '1', - id: testNetworkConfigurationId, - }, + expect( + Object.values(controller.state.networkConfigurations), + ).toStrictEqual([ + { + rpcUrl: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', + }, + { + ...newNetworkConfiguration, + id: 'networkConfigurationId', + }, + ]); + expect(trackEventSpy).toHaveBeenCalledWith({ + event: 'Custom Network Added', + category: 'Network', + referrer: { + url: 'https://test-dapp.com', + }, + properties: { + chain_id: '0x9999', + 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: '1', + id: testNetworkConfigurationId, }, }, }, - async ({ controller }) => { - expect(() => - controller.removeNetworkConfiguration( - invalidNetworkConfigurationId, - ), - ).toThrow( - `networkConfigurationId ${invalidNetworkConfigurationId} does not match a configured networkConfiguration`, - ); + }, + async ({ controller }) => { + controller.removeNetworkConfiguration(testNetworkConfigurationId); + expect(controller.state.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: '1', + id: testNetworkConfigurationId, + }, + }, }, - ); - }); + }, + async ({ controller }) => { + expect(() => + controller.removeNetworkConfiguration( + invalidNetworkConfigurationId, + ), + ).toThrow( + `networkConfigurationId ${invalidNetworkConfigurationId} does not match a configured networkConfiguration`, + ); + }, + ); }); }); }); diff --git a/packages/network-controller/tests/provider-api-tests/helpers.ts b/packages/network-controller/tests/provider-api-tests/helpers.ts index 779dda64f4..781241c2e1 100644 --- a/packages/network-controller/tests/provider-api-tests/helpers.ts +++ b/packages/network-controller/tests/provider-api-tests/helpers.ts @@ -399,21 +399,18 @@ export const withNetworkClient = async ( return Promise.resolve(); }); - const networkConfigurationId = controller.upsertNetworkConfiguration( - { - rpcUrl: customRpcUrl, - chainId: '0x9999', - ticker: 'TEST', - }, - { referrer: 'https://test-dapp.com', source: 'dapp' }, - ); - if (providerType === 'infura') { controller.setProviderType(infuraNetwork); - } else if (networkConfigurationId) { - controller.setActiveNetwork(networkConfigurationId); + } else { + controller.upsertNetworkConfiguration( + { + rpcUrl: customRpcUrl, + chainId: '0x9999', + ticker: 'TEST', + }, + { referrer: 'https://test-dapp.com', source: 'dapp', setActive: true }, + ); } - const ethQuery = messenger.call('NetworkController:getEthQuery'); const { provider, blockTracker } = controller.getProviderAndBlockTracker(); From 6ca35bdc7a395a19388eef90438123f18824c521 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 22 Mar 2023 14:11:22 -0500 Subject: [PATCH 8/8] fix test --- .../tests/NetworkController.test.ts | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index faafdd7fd8..8f249040cc 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -3678,19 +3678,17 @@ describe('NetworkController', () => { it('throws if an options object is not passed as a second argument', async () => { await withController(async ({ controller }) => { - expect(() => - // @ts-expect-error - we want to test the case where no second arg is passed. - controller.upsertNetworkConfiguration({ - chainId: '0x5', - nickname: 'RPC', - rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, - rpcUrl: 'https://mock-rpc-url', - }), - ).toThrow( - new Error( - "Cannot read properties of undefined (reading 'setActive')", - ), - ); + expect( + () => + // @ts-expect-error - we want to test the case where no second arg is passed. + controller.upsertNetworkConfiguration({ + chainId: '0x5', + nickname: 'RPC', + rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, + rpcUrl: 'https://mock-rpc-url', + }), + // eslint-disable-next-line + ).toThrow(); }); });