diff --git a/src/ppom-controller.test.ts b/src/ppom-controller.test.ts index 7460e81d..9a2d726a 100644 --- a/src/ppom-controller.test.ts +++ b/src/ppom-controller.test.ts @@ -64,7 +64,7 @@ describe('PPOMController', () => { const spy = buildFetchSpy(undefined, undefined, 123); buildPPOMController(); - expect(spy).toHaveBeenCalledTimes(0); + expect(spy).toHaveBeenCalledTimes(1); jest.runAllTicks(); await flushPromises(); expect(spy).toHaveBeenCalledTimes(5); @@ -204,7 +204,7 @@ describe('PPOMController', () => { return Promise.resolve(); }); }).rejects.toThrow( - 'Blockaid validation is available only on ethereum mainnet', + 'Blockaid validation not available on network with chainId: 0x2', ); }); @@ -263,7 +263,7 @@ describe('PPOMController', () => { json: () => [ { name: 'blob', - chainId: '0x1', + chainId: Utils.SUPPORTED_NETWORK_CHAINIDS.MAINNET, version: '1.0.0', checksum: '409a7f83ac6b31dc8c77e3ec18038f209bd2f545e0f4177c2e2381aa4e067b49', @@ -316,7 +316,7 @@ describe('PPOMController', () => { onPreferencesChange: (func: any) => { callBack = func; }, - chainId: '0x1', + chainId: Utils.SUPPORTED_NETWORK_CHAINIDS.MAINNET, }); jest.runOnlyPendingTimers(); await flushPromises(); @@ -344,6 +344,21 @@ describe('PPOMController', () => { 'Aborting initialising PPOM as not all files could not be downloaded for the network with chainId: 0x1', ); }); + + it('should initantiate PPOM instance if not already done', async () => { + buildFetchSpy(); + const { ppomController } = buildPPOMController({ + chainId: '0x1', + state: { + versionInfo: VERSION_INFO, + }, + }); + + await ppomController.usePPOM(async (ppom: any) => { + expect(ppom).toBeDefined(); + return Promise.resolve(); + }); + }); }); describe('updatePPOM', () => { @@ -423,11 +438,11 @@ describe('PPOMController', () => { const { changeNetwork, ppomController } = buildPPOMController(); jest.runOnlyPendingTimers(); changeNetwork('0x2'); - expect(Object.keys(ppomController.state.chainStatus)).toHaveLength(2); + expect(Object.keys(ppomController.state.chainStatus)).toHaveLength(1); await ppomController.updatePPOM(); jest.runOnlyPendingTimers(); await flushPromises(); - expect(spy).toHaveBeenCalledTimes(9); + expect(spy).toHaveBeenCalledTimes(10); }); it('should not re-throw error if file write fails', async () => { @@ -444,7 +459,7 @@ describe('PPOMController', () => { await ppomController.updatePPOM(); jest.runOnlyPendingTimers(); await flushPromises(); - expect(spy).toHaveBeenCalledTimes(9); + expect(spy).toHaveBeenCalledTimes(10); }); it('should decrease scheduleInterval if its set very high', async () => { @@ -454,7 +469,7 @@ describe('PPOMController', () => { buildPPOMController({ fileFetchScheduleDuration: REFRESH_TIME_INTERVAL * 100, }); - expect(spy).toHaveBeenCalledTimes(0); + expect(spy).toHaveBeenCalledTimes(1); jest.advanceTimersByTime(REFRESH_TIME_INTERVAL); await flushPromises(); expect(spy).toHaveBeenCalledTimes(6); @@ -466,18 +481,19 @@ describe('PPOMController', () => { it('should delete network more than a week old from chainStatus', async () => { buildFetchSpy(); const { changeNetwork, ppomController } = buildPPOMController({ - chainId: '0x2', + chainId: Utils.SUPPORTED_NETWORK_CHAINIDS.BSC, }); jest.runOnlyPendingTimers(); await flushPromises(); - const chainIdData1 = ppomController.state.chainStatus['0x2']; + const chainIdData1 = + ppomController.state.chainStatus[Utils.SUPPORTED_NETWORK_CHAINIDS.BSC]; expect(chainIdData1).toBeDefined(); - changeNetwork('0x3'); - changeNetwork('0x4'); - jest.advanceTimersByTime(NETWORK_CACHE_DURATION); - jest.runOnlyPendingTimers(); - await flushPromises(); - const chainIdData2 = ppomController.state.chainStatus['0x2']; + changeNetwork(Utils.SUPPORTED_NETWORK_CHAINIDS.OPTIMISM); + changeNetwork(Utils.SUPPORTED_NETWORK_CHAINIDS.POLYGON); + jest.advanceTimersByTime(NETWORK_CACHE_DURATION * 2); + changeNetwork(Utils.SUPPORTED_NETWORK_CHAINIDS.ARBITRUM); + const chainIdData2 = + ppomController.state.chainStatus[Utils.SUPPORTED_NETWORK_CHAINIDS.BSC]; expect(chainIdData2).toBeUndefined(); }); @@ -503,28 +519,28 @@ describe('PPOMController', () => { it('should add network to chainStatus if not already added', () => { buildFetchSpy(); const { changeNetwork, ppomController } = buildPPOMController(); - changeNetwork('0x1'); - const chainIdData1 = ppomController.state.chainStatus['0x1']; + changeNetwork(Utils.SUPPORTED_NETWORK_CHAINIDS.MAINNET); + const chainIdData1 = + ppomController.state.chainStatus[ + Utils.SUPPORTED_NETWORK_CHAINIDS.MAINNET + ]; expect(chainIdData1).toBeDefined(); - changeNetwork('0x2'); - const chainIdData2 = ppomController.state.chainStatus['0x2']; + changeNetwork(Utils.SUPPORTED_NETWORK_CHAINIDS.BSC); + const chainIdData2 = + ppomController.state.chainStatus[Utils.SUPPORTED_NETWORK_CHAINIDS.BSC]; expect(chainIdData2).toBeDefined(); }); it('should trigger file download if preference is enabled', async () => { - const spy = buildFetchSpy({ - status: 500, - }); - const { changeNetwork } = buildPPOMController({ - securityAlertsEnabled: true, - }); + const spy = buildFetchSpy(); + const { changeNetwork } = buildPPOMController(); jest.runOnlyPendingTimers(); await flushPromises(); - expect(spy).toHaveBeenCalledTimes(1); - changeNetwork('0x1'); + expect(spy).toHaveBeenCalledTimes(6); + changeNetwork(Utils.SUPPORTED_NETWORK_CHAINIDS.BSC); jest.runOnlyPendingTimers(); await flushPromises(); - expect(spy).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenCalledTimes(8); }); it('should not trigger file download if preference is not enabled', async () => { @@ -547,14 +563,18 @@ describe('PPOMController', () => { jest.setSystemTime(new Date('2023-01-01')); const lastVisitedBefore = - ppomController?.state?.chainStatus?.['0x1']?.lastVisited; + ppomController?.state?.chainStatus?.[ + Utils.SUPPORTED_NETWORK_CHAINIDS.MAINNET + ]?.lastVisited; jest.useFakeTimers().setSystemTime(new Date('2023-01-02')); - changeNetwork('0x2'); - changeNetwork('0x1'); + changeNetwork(Utils.SUPPORTED_NETWORK_CHAINIDS.BSC); + changeNetwork(Utils.SUPPORTED_NETWORK_CHAINIDS.MAINNET); const lastVisitedAfter = - ppomController?.state?.chainStatus?.['0x1']?.lastVisited; + ppomController?.state?.chainStatus?.[ + Utils.SUPPORTED_NETWORK_CHAINIDS.MAINNET + ]?.lastVisited; expect(lastVisitedBefore !== lastVisitedAfter).toBe(true); }); @@ -562,36 +582,40 @@ describe('PPOMController', () => { jest.useFakeTimers().setSystemTime(new Date('2023-01-01')); buildFetchSpy(); const { changeNetwork, ppomController } = buildPPOMController({ - chainId: '0x2', + chainId: Utils.SUPPORTED_NETWORK_CHAINIDS.BSC, }); expect(Object.keys(ppomController.state.chainStatus)).toHaveLength(1); jest.useFakeTimers().setSystemTime(new Date('2023-01-02')); - changeNetwork('0x3'); + changeNetwork(Utils.SUPPORTED_NETWORK_CHAINIDS.OPTIMISM); jest.useFakeTimers().setSystemTime(new Date('2023-01-05')); - changeNetwork('0x5'); + changeNetwork(Utils.SUPPORTED_NETWORK_CHAINIDS.POLYGON); jest.useFakeTimers().setSystemTime(new Date('2023-01-03')); - changeNetwork('0x4'); + changeNetwork(Utils.SUPPORTED_NETWORK_CHAINIDS.AVALANCHE); jest.useFakeTimers().setSystemTime(new Date('2023-01-04')); - changeNetwork('0x6'); + changeNetwork(Utils.SUPPORTED_NETWORK_CHAINIDS.ARBITRUM); expect(Object.keys(ppomController.state.chainStatus)).toHaveLength(5); jest.useFakeTimers().setSystemTime(new Date('2023-01-06')); - changeNetwork('0x7'); + changeNetwork(Utils.SUPPORTED_NETWORK_CHAINIDS.LINEA_MAINNET); expect(Object.keys(ppomController.state.chainStatus)).toHaveLength(5); - expect(ppomController.state.chainStatus['0x1']).toBeUndefined(); + expect( + ppomController.state.chainStatus[ + Utils.SUPPORTED_NETWORK_CHAINIDS.MAINNET + ], + ).toBeUndefined(); }); it('should not throw error if update ppom fails', async () => { buildFetchSpy(); const { changeNetwork } = buildPPOMController({ chainId: '0x2' }); - changeNetwork('0x1'); + changeNetwork(Utils.SUPPORTED_NETWORK_CHAINIDS.MAINNET); await flushPromises(); expect(async () => { buildFetchSpy({ @@ -604,6 +628,29 @@ describe('PPOMController', () => { await flushPromises(); }).not.toThrow(); }); + + it('should not throw error if reset ppom fails when switching to network not supporting validations', async () => { + buildFetchSpy(undefined, undefined, 123); + const freeMock = jest.fn().mockImplementation(() => { + throw new Error('some error'); + }); + const { changeNetwork } = buildPPOMController({ + ppomProvider: { + ppomInit: async () => { + return Promise.resolve('123'); + }, + PPOM: new PPOMClass(undefined, freeMock), + }, + }); + jest.runOnlyPendingTimers(); + await flushPromises(); + buildFetchSpy({ + status: 500, + }); + expect(async () => { + changeNetwork('0x2'); + }).not.toThrow(); + }); }); describe('onPreferencesChange', () => { @@ -836,7 +883,7 @@ describe('PPOMController', () => { }); describe('initialisePPOM', () => { - it('should publich initialisationStateChange events to the messenger', async () => { + it('should publish initialisationStateChange events to the messenger', async () => { buildFetchSpy(); let callBack: any; const ppomInitialisationCallbackMock = jest.fn(); @@ -858,12 +905,12 @@ describe('PPOMController', () => { ); jest.runOnlyPendingTimers(); await flushPromises(); - expect(ppomInitialisationCallbackMock).toHaveBeenCalledTimes(1); + expect(ppomInitialisationCallbackMock).toHaveBeenCalledTimes(2); callBack({ securityAlertsEnabled: false }); callBack({ securityAlertsEnabled: true }); jest.runOnlyPendingTimers(); await flushPromises(); - expect(ppomInitialisationCallbackMock).toHaveBeenCalledTimes(3); + expect(ppomInitialisationCallbackMock).toHaveBeenCalledTimes(4); }); }); }); diff --git a/src/ppom-controller.ts b/src/ppom-controller.ts index 42f204e5..7957ff9c 100644 --- a/src/ppom-controller.ts +++ b/src/ppom-controller.ts @@ -14,6 +14,8 @@ import { IdGenerator, PROVIDER_ERRORS, addHexPrefix, + blockaidValidationSupportedForNetwork, + checkFilePath, constructURLHref, createPayload, validateSignature, @@ -50,8 +52,6 @@ const ALLOWED_PROVIDER_CALLS = [ 'trace_filter', ]; -const ETHEREUM_CHAIN_ID = '0x1'; - /** * @type PPOMFileVersion * @augments FileMetadata @@ -70,7 +70,6 @@ type PPOMVersionResponse = PPOMFileVersion[]; type ChainInfo = { chainId: string; lastVisited: number; - dataFetched: boolean; versionInfo: PPOMVersionResponse; }; @@ -97,10 +96,10 @@ export type PPOMState = { }; const stateMetaData = { - versionInfo: { persist: false, anonymous: false }, - chainStatus: { persist: false, anonymous: false }, - storageMetadata: { persist: false, anonymous: false }, - versionFileETag: { persist: false, anonymous: false }, + versionInfo: { persist: true, anonymous: false }, + chainStatus: { persist: true, anonymous: false }, + storageMetadata: { persist: true, anonymous: false }, + versionFileETag: { persist: true, anonymous: false }, }; const PPOM_VERSION_FILE_NAME = 'ppom_version.json'; @@ -177,8 +176,6 @@ export class PPOMController extends BaseControllerV2< > { #ppom: any; - #ppomInitError: any; - #provider: any; #storage: PPOMStorage; @@ -279,7 +276,6 @@ export class PPOMController extends BaseControllerV2< [currentChainId]: { chainId: currentChainId, lastVisited: new Date().getTime(), - dataFetched: false, versionInfo: [], }, }, @@ -355,10 +351,14 @@ export class PPOMController extends BaseControllerV2< if (!this.#securityAlertsEnabled) { throw Error('User has securityAlertsEnabled set to false'); } - if (!this.#networkIsSupported(this.#chainId)) { - throw Error('Blockaid validation is available only on ethereum mainnet'); + if (!blockaidValidationSupportedForNetwork(this.#chainId)) { + throw Error( + `Blockaid validation not available on network with chainId: ${ + this.#chainId + }`, + ); } - await this.#reinitPPOMForChainIfRequired(this.#chainId); + await this.#initPPOMIfRequired(); this.#providerRequests = 0; this.#providerRequestsCount = {}; @@ -378,7 +378,7 @@ export class PPOMController extends BaseControllerV2< * Initialise PPOM loading wasm file. * This is done only if user has enabled preference for PPOM Validation. * Initialisation is done as soon as controller is constructed - * or as user enables preference for blcokaid validation. + * or as user enables preference for blockaid validation. */ async #initialisePPOM() { if (this.#securityAlertsEnabled && !this.#ppomInitialised) { @@ -395,14 +395,6 @@ export class PPOMController extends BaseControllerV2< } } - /* - * The function check if ethereum chainId is supported for validation - * Currently it checks for only Ethereum Mainnet but it will include more networks in future. - */ - #networkIsSupported(chainId: string) { - return chainId === ETHEREUM_CHAIN_ID; - } - /* * Clear intervals for data fetching. * This is done if data fetching is no longer needed. @@ -417,12 +409,15 @@ export class PPOMController extends BaseControllerV2< this.#fileScheduleInterval = undefined; } - #setToActiveState() { + /* + * Gets files to current network and pass to PPOM to prepare it fot validating transactions. + */ + #initPPOMforCurrentChain() { this.messagingSystem.publish( 'PPOMController:initialisationStateChangeEvent', PPOMInitialisationStatus.INPROGRESS, ); - this.#reinitPPOMForChainIfRequired(ETHEREUM_CHAIN_ID) + this.#initPPOMWithFiles() .then(async () => { this.messagingSystem.publish( 'PPOMController:initialisationStateChangeEvent', @@ -440,7 +435,22 @@ export class PPOMController extends BaseControllerV2< } /* - * The function resets the controller to inactiva state + * The function updates the version info to get any latest changes and + * initialise PPOM for current chain + */ + #setToActiveState() { + this.#updateVersionInfo() + .then(() => { + this.#initPPOMforCurrentChain(); + }) + .catch((error: Error) => + console.error(`Error in fetching version info: ${error.message}`), + ); + } + + /* + * The function resets the controller to inactive state. + * This is invoked when user disables blockaid preference. * 1. reset the PPOM * 2. clear data fetch intervals * 3. clears version information of data files @@ -457,7 +467,6 @@ export class PPOMController extends BaseControllerV2< if (newChainStatus[chainId]) { const chainInfo: ChainInfo = { ...(newChainStatus[chainId] as ChainInfo), - dataFetched: false, versionInfo: [], }; newChainStatus[chainId] = chainInfo; @@ -467,15 +476,26 @@ export class PPOMController extends BaseControllerV2< draftState.storageMetadata = []; draftState.versionFileETag = ''; }); - // todo: as we move data files to controller storage we should also delete those here } /* - * The function adds new network to chainStatus list. + * The function is invoked on network change, it does following: + * 1. update instant value this.#chainid + * 2. if network is supported by blockaid add / update network in state variable chainStatus + * 2. instantiate PPOM for new network if user has enabled security alerts */ #onNetworkChange(networkControllerState: any): void { const id = addHexPrefix(networkControllerState.providerConfig.chainId); + if (id === this.#chainId) { + return; + } this.#chainId = id; + if (!blockaidValidationSupportedForNetwork(id)) { + this.#resetPPOM().catch((error: Error) => { + console.error(`Error in resetting ppom: ${error.message}`); + }); + return; + } let chainStatus = { ...this.state.chainStatus }; const existingNetworkObject = chainStatus[id]; chainStatus = { @@ -483,7 +503,6 @@ export class PPOMController extends BaseControllerV2< [id]: { chainId: id, lastVisited: new Date().getTime(), - dataFetched: existingNetworkObject?.dataFetched ?? false, versionInfo: existingNetworkObject?.versionInfo ?? [], }, }; @@ -491,8 +510,8 @@ export class PPOMController extends BaseControllerV2< draftState.chainStatus = chainStatus; }); this.#deleteOldChainIds(); - if (this.#networkIsSupported(id) && this.#securityAlertsEnabled) { - this.#setToActiveState(); + if (this.#securityAlertsEnabled) { + this.#initPPOMforCurrentChain(); } } @@ -513,8 +532,7 @@ export class PPOMController extends BaseControllerV2< } /* - * Constructor helper for registering this controller's messaging system - * actions. + * Constructor helper for registering this controller's messaging system actions. */ #registerMessageHandlers(): void { this.messagingSystem.registerActionHandler( @@ -553,19 +571,16 @@ export class PPOMController extends BaseControllerV2< } /* - * The function initialises PPOM. + * The function reset old instance of PPOM if any and + * prepares instance of PPOM by passing files of selected network to it. */ - async #reinitPPOM(chainId: string): Promise { + async #initPPOMWithFiles(): Promise { + if (!blockaidValidationSupportedForNetwork(this.#chainId)) { + return; + } await this.#resetPPOM(); - await this.#getPPOM(chainId); - } - - /* - * The function will return true if data is not already fetched for current chain. - */ - #isDataRequiredForCurrentChain(): boolean { - const { chainStatus } = this.state; - return !chainStatus[this.#chainId]?.dataFetched; + this.#updateVersionInfoForChainId(this.#chainId); + this.#ppom = await this.#getPPOM(this.#chainId); } /* @@ -573,10 +588,8 @@ export class PPOMController extends BaseControllerV2< * If new version info file is available the function will update data files for all chains. */ async #updatePPOM(): Promise { - const versionInfoUpdated = await this.#updateVersionInfo(); - if (versionInfoUpdated) { - await this.#getNewFilesForAllChains(); - } + await this.#updateVersionInfo(); + await this.#getNewFilesForAllChains(); } /* @@ -610,18 +623,10 @@ export class PPOMController extends BaseControllerV2< ); } - // todo: function below can be utility function /* - * The function check to ensure that file path can contain only alphanumeric - * characters and a dot character (.) or slash (/). + * + * Get all files listed in versionInfo passed. */ - #checkFilePath(filePath: string): void { - const filePathRegex = /^[\w./]+$/u; - if (!filePath.match(filePathRegex)) { - throw new Error(`Invalid file path for data file: ${filePath}`); - } - } - async #getAllFiles(versionInfo: PPOMVersionResponse) { const files = await Promise.all( versionInfo.map(async (file) => { @@ -663,7 +668,7 @@ export class PPOMController extends BaseControllerV2< } } // validate file path for valid characters - this.#checkFilePath(fileVersionInfo.filePath); + checkFilePath(fileVersionInfo.filePath); const fileUrl = constructURLHref( this.#cdnBaseUrl, fileVersionInfo.filePath, @@ -690,36 +695,48 @@ export class PPOMController extends BaseControllerV2< } /* - * As files for a chain are fetched this function set dataFetched - * property for that chainId in chainStatus to true. + * Update version info for chainId */ - async #setChainIdDataFetched(chainId: string): Promise { - const { chainStatus, versionInfo } = this.state; - const chainIdObject = chainStatus[chainId]; - const versionInfoForChain = versionInfo.filter( + #updateVersionInfoForChainId(chainId: string) { + const versionInfo = this.state.versionInfo.filter( ({ chainId: id }) => id === chainId, ); - if (chainIdObject) { - this.update((draftState) => { + this.update((draftState) => { + const selectedChainStatus = draftState.chainStatus[chainId]; + if (selectedChainStatus) { draftState.chainStatus = { - ...chainStatus, + ...draftState.chainStatus, [chainId]: { - ...chainIdObject, - dataFetched: true, - versionInfo: versionInfoForChain, + ...selectedChainStatus, + versionInfo, }, }; - }); + } + }); + } + + /* + * Update versionInfo if required. + */ + async #updateVersionInfoIfRequired() { + const { chainStatus } = this.state; + if (!chainStatus[this.#chainId]?.versionInfo?.length) { + const versionInfoFromState = this.state.versionInfo.filter( + ({ chainId: id }) => id === this.#chainId, + ); + if (!versionInfoFromState.length) { + await this.#updateVersionInfo(); + } } } /* * The function will initialise PPOM for the network if required. */ - async #reinitPPOMForChainIfRequired(chainId: string): Promise { - if (this.#isDataRequiredForCurrentChain() || this.#ppom === undefined) { - await this.#reinitPPOM(chainId); - await this.#setChainIdDataFetched(chainId); + async #initPPOMIfRequired(): Promise { + if (this.#ppom === undefined) { + await this.#updateVersionInfoIfRequired(); + await this.#initPPOMWithFiles(); } } @@ -737,12 +754,11 @@ export class PPOMController extends BaseControllerV2< storageMetadata, versionInfo: stateVersionInfo, } = this.state; - const networkIsSupported = this.#networkIsSupported.bind(this); // create a map of chainId and files belonging to that chainId // not include the files for which the version in storage is the latest one // As we add support for multiple chains it will be useful to sort the chain in desc order of lastvisited const chainIdsFileInfoList = Object.keys(chainStatus) - .filter(networkIsSupported) + .filter(blockaidValidationSupportedForNetwork) .map((chainId): { chainId: string; versionInfo: PPOMFileVersion[] } => ({ chainId, versionInfo: stateVersionInfo.filter( @@ -783,9 +799,7 @@ export class PPOMController extends BaseControllerV2< } const currentTimestamp = new Date().getTime(); - const chainIds = Object.keys(this.state.chainStatus).filter( - (id) => id !== ETHEREUM_CHAIN_ID, - ); + const chainIds = Object.keys(this.state.chainStatus); const oldChaninIds: any[] = chainIds.filter( (chainId) => (this.state.chainStatus[chainId] as any).lastVisited < @@ -851,10 +865,9 @@ export class PPOMController extends BaseControllerV2< this.#getFile(fileVersionInfo) .then(async () => { if (isLastFileOfNetwork) { - // if this was last file for the chainId set dataFetched for chainId to true - await this.#setChainIdDataFetched(fileVersionInfo.chainId); - if (fileVersionInfo.chainId === ETHEREUM_CHAIN_ID) { - await this.#reinitPPOM(ETHEREUM_CHAIN_ID); + this.#updateVersionInfoForChainId(this.#chainId); + if (this.#chainId === fileVersionInfo.chainId) { + await this.#initPPOMWithFiles(); } } }) @@ -1003,43 +1016,34 @@ export class PPOMController extends BaseControllerV2< * It will load the data files from storage and pass data files and wasm file to ppom. */ async #getPPOM(chainId: string): Promise { - // For some reason ppom initialisation in contrructor fails for react native + // PPOM initialisation in contructor fails for react native // thus it is added here to prevent validation from failing. await this.#initialisePPOM(); const { chainStatus } = this.state; - let versionInfo = chainStatus[chainId]?.versionInfo; - if (!versionInfo?.length) { - await this.#updateVersionInfo(); - versionInfo = this.state.versionInfo.filter( - ({ chainId: id }) => id === chainId, - ); - } + const versionInfo = chainStatus[chainId]?.versionInfo; + // The following code throw error if no data files are found for the chainId. + // This check has been put in place after suggestion of security team. + // If we want to disable ppom validation on all instances of Metamask, + // this can be achieved by returning empty data from version file. if (versionInfo?.length === undefined || versionInfo?.length === 0) { throw new Error( `Aborting initialising PPOM as no files are found for the network with chainId: ${chainId}`, ); } + // Get all the files for the chainId const files = await this.#getAllFiles(versionInfo); - // The following code throw error if no data files are found for the chainId. - // This check has been put in place after suggestion of security team. - // If we want to disable ppom validation on all instances of Metamask, - // this can be achieved by returning empty data from version file. if (files?.length !== versionInfo?.length) { throw new Error( `Aborting initialising PPOM as not all files could not be downloaded for the network with chainId: ${chainId}`, ); } - if (this.#chainId !== ETHEREUM_CHAIN_ID) { - return undefined; - } - return await this.#ppomMutex.use(async () => { const { PPOM } = this.#ppomProvider; - this.#ppom = PPOM.new(this.#jsonRpcRequest.bind(this), files); + return PPOM.new(this.#jsonRpcRequest.bind(this), files); }); } diff --git a/src/util.ts b/src/util.ts index 8acd94b1..b6b1339c 100644 --- a/src/util.ts +++ b/src/util.ts @@ -5,6 +5,24 @@ import IdIterator from 'json-rpc-random-id'; const EdDSA = elliptic.eddsa; const URL_PREFIX = 'https://'; +export const SUPPORTED_NETWORK_CHAINIDS = { + MAINNET: '0x1', + BSC: '0x38', + OPTIMISM: '0xa', + POLYGON: '0x89', + AVALANCHE: '0xa86a', + ARBITRUM: '0xa4b1', + LINEA_MAINNET: '0xe708', +}; + +export const blockaidValidationSupportedForNetwork = ( + chainId: string, +): boolean => { + return Object.values(SUPPORTED_NETWORK_CHAINIDS).some( + (cid) => cid === chainId, + ); +}; + export const IdGenerator = IdIterator(); export const createPayload = ( @@ -92,3 +110,14 @@ export const addHexPrefix = (str: string) => { return `0x${str}`; }; + +/* + * The function check to ensure that file path can contain only alphanumeric + * characters and a dot character (.) or slash (/). + */ +export const checkFilePath = (filePath: string): void => { + const filePathRegex = /^[\w./]+$/u; + if (!filePath.match(filePathRegex)) { + throw new Error(`Invalid file path for data file: ${filePath}`); + } +}; diff --git a/test/test-utils.ts b/test/test-utils.ts index 81ef64cf..d70fe528 100644 --- a/test/test-utils.ts +++ b/test/test-utils.ts @@ -7,6 +7,7 @@ import type { } from '../src/ppom-controller'; import { PPOMController } from '../src/ppom-controller'; import type { StorageKey } from '../src/ppom-storage'; +import { SUPPORTED_NETWORK_CHAINIDS } from '../src/util'; export const buildDummyResponse = ( resultType = 'DUMMY_RESULT_TYPE', @@ -35,14 +36,14 @@ export const buildStorageBackend = (obj = {}) => { export const StorageMetadata = [ { name: 'data', - chainId: '0x1', + chainId: SUPPORTED_NETWORK_CHAINIDS.MAINNET, version: '1.0.3', checksum: '409a7f83ac6b31dc8c77e3ec18038f209bd2f545e0f4177c2e2381aa4e067b49', }, { name: 'blob', - chainId: '0x1', + chainId: SUPPORTED_NETWORK_CHAINIDS.MAINNET, version: '1.0.0', checksum: '409a7f83ac6b31dc8c77e3ec18038f209bd2f545e0f4177c2e2381aa4e067b49', @@ -61,7 +62,7 @@ export const storageBackendReturningData = buildStorageBackend({ export const VERSION_INFO = [ { name: 'blob', - chainId: '0x1', + chainId: SUPPORTED_NETWORK_CHAINIDS.MAINNET, version: '1.0.0', checksum: '409a7f83ac6b31dc8c77e3ec18038f209bd2f545e0f4177c2e2381aa4e067b49', @@ -71,7 +72,7 @@ export const VERSION_INFO = [ }, { name: 'data', - chainId: '0x1', + chainId: SUPPORTED_NETWORK_CHAINIDS.MAINNET, version: '1.0.3', checksum: '409a7f83ac6b31dc8c77e3ec18038f209bd2f545e0f4177c2e2381aa4e067b49', @@ -186,7 +187,7 @@ export const buildPPOMController = (args?: any) => { const ppomController = new PPOMController({ storageBackend: storageBackendReturningData, provider: () => undefined, - chainId: '0x1', + chainId: SUPPORTED_NETWORK_CHAINIDS.MAINNET, messenger: controllerMessenger.getRestricted({ name: 'PPOMController', allowedEvents: ['NetworkController:stateChange'],