Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .depcheckrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"@types/*",
"prettier-plugin-packagejson",
"ts-node",
"typedoc"
"typedoc",
"@yarnpkg/*",
"clipanion"
Comment on lines +10 to +11
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolves following error from running yarn depcheck

Missing dependencies
* @yarnpkg/core: ./.yarn/plugins/@yarnpkg/plugin-constraints.cjs
* @yarnpkg/cli: ./.yarn/plugins/@yarnpkg/plugin-constraints.cjs
* clipanion: ./.yarn/plugins/@yarnpkg/plugin-constraints.cjs
* @yarnpkg/fslib: ./.yarn/plugins/@yarnpkg/plugin-constraints.cjs

]
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@metamask/base-controller": "^3.0.0",
"@metamask/controller-utils": "^8.0.1",
"@metamask/network-controller": "^17.0.0",
"@metamask/utils": "^8.3.0",
"await-semaphore": "^0.1.3",
"crypto-js": "^4.2.0",
"elliptic": "^6.5.4",
Expand Down
18 changes: 17 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
export * from './ppom-controller';
export type {
NativeCrypto,
PPOMState,
UsePPOM,
UpdatePPOM,
PPOMInitialisationStatusType,
PPOMControllerActions,
PPOMControllerInitialisationStateChangeEvent,
PPOMControllerEvents,
PPOMControllerMessenger,
} from './ppom-controller';
export {
REFRESH_TIME_INTERVAL,
NETWORK_CACHE_DURATION,
PPOMInitialisationStatus,
PPOMController,
} from './ppom-controller';
Comment on lines +1 to +17
Copy link
Contributor Author

@MajorLift MajorLift Jan 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be no breaking changes here. This is the list of existing package-level exports from the ppom-controller file.

Explicit enumeration of exports is safer, and also allows sharing exports internally, which comes in handy with e.g. AllowedEvents, controllerName for test files.


export type { StorageBackend, StorageKey } from './ppom-storage';
2 changes: 1 addition & 1 deletion src/ppom-controller-mocked-storage.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { buildFetchSpy, buildPPOMController } from '../test/test-utils';
import { REFRESH_TIME_INTERVAL } from './ppom-controller';
import { buildFetchSpy, buildPPOMController } from '../test/test-utils';

jest.mock('@metamask/controller-utils', () => {
return {
Expand Down
10 changes: 5 additions & 5 deletions src/ppom-controller.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import {
NETWORK_CACHE_DURATION,
REFRESH_TIME_INTERVAL,
} from './ppom-controller';
import * as Utils from './util';
import {
PPOMClass,
StorageMetadata,
Expand All @@ -7,11 +12,6 @@ import {
buildPPOMController,
buildStorageBackend,
} from '../test/test-utils';
import {
NETWORK_CACHE_DURATION,
REFRESH_TIME_INTERVAL,
} from './ppom-controller';
import * as Utils from './util';

jest.mock('@metamask/controller-utils', () => {
return {
Expand Down
96 changes: 69 additions & 27 deletions src/ppom-controller.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import type { RestrictedControllerMessenger } from '@metamask/base-controller';
import { BaseControllerV2 } from '@metamask/base-controller';
import { safelyExecute, timeoutFetch } from '@metamask/controller-utils';
import type { NetworkControllerStateChangeEvent } from '@metamask/network-controller';
import type {
NetworkControllerStateChangeEvent,
NetworkState,
Provider,
} from '@metamask/network-controller';
import type {
JsonRpcFailure,
Json,
JsonRpcParams,
JsonRpcSuccess,
} from '@metamask/utils';
import { Mutex } from 'await-semaphore';

import type {
Expand Down Expand Up @@ -52,6 +62,14 @@ const ALLOWED_PROVIDER_CALLS = [
'trace_filter',
];

// Provisional skeleton type for PPOM class
// TODO: Replace with actual PPOM class
type PPOM = {
new: (...args: unknown[]) => PPOM;
validateJsonRpc: () => Promise<unknown>;
free: () => void;
} & Record<string, unknown>;
Comment on lines +65 to +71
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrote temporary PPOM type that expresses a minimal interface. Added TODO so this would be eventually replaced with a full type.


/**
* @type PPOMFileVersion
* @augments FileMetadata
Expand Down Expand Up @@ -121,7 +139,7 @@ const versionInfoFileHeaders = {

export type UsePPOM = {
type: `${typeof controllerName}:usePPOM`;
handler: (callback: (ppom: any) => Promise<any>) => Promise<any>;
handler: (callback: (ppom: PPOM) => Promise<unknown>) => Promise<unknown>;
};

export type UpdatePPOM = {
Expand All @@ -147,24 +165,23 @@ export type PPOMControllerInitialisationStateChangeEvent = {
payload: [PPOMInitialisationStatusType];
};

export type PPOMControllerEvents =
| PPOMControllerInitialisationStateChangeEvent
| NetworkControllerStateChangeEvent;
export type PPOMControllerEvents = PPOMControllerInitialisationStateChangeEvent;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For context, these changes align with changes we've been making to controllers in the core repo. For instance, see SelectedNetworkController: https://github.com/MetaMask/core/blob/d3861d03df0ff3f4cf2b479f746b46e602065d76/packages/selected-network-controller/src/SelectedNetworkController.ts#L71-L89

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding context here. To be clear, I didn't use the ControllerGetStateAction and ControllerStateChangeEvent types since those were introduced in base-controller v4, and this package is using v3.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot @MajorLift for these improvements 🙏


export type AllowedEvents = NetworkControllerStateChangeEvent;

export type PPOMControllerMessenger = RestrictedControllerMessenger<
typeof controllerName,
PPOMControllerActions,
| PPOMControllerInitialisationStateChangeEvent
| NetworkControllerStateChangeEvent,
PPOMControllerEvents | AllowedEvents,
never,
NetworkControllerStateChangeEvent['type']
AllowedEvents['type']
>;

// eslint-disable-next-line @typescript-eslint/naming-convention
type PPOMProvider = {
ppomInit: (wasmFilePath: string) => Promise<void>;
// eslint-disable-next-line @typescript-eslint/naming-convention
PPOM: any;
PPOM: PPOM;
};

/**
Expand All @@ -182,15 +199,15 @@ export class PPOMController extends BaseControllerV2<
PPOMState,
PPOMControllerMessenger
> {
#ppom: any;
#ppom: PPOM | undefined;

#provider: any;
#provider: Provider;

#storage: PPOMStorage;

#refreshDataInterval: any;
#refreshDataInterval: ReturnType<typeof setInterval> | undefined;

#fileScheduleInterval: any;
#fileScheduleInterval: ReturnType<typeof setInterval> | undefined;

/*
* This mutex is used to prevent concurrent usage of the PPOM instance
Expand Down Expand Up @@ -269,10 +286,18 @@ export class PPOMController extends BaseControllerV2<
}: {
chainId: string;
messenger: PPOMControllerMessenger;
provider: any;
provider: Provider;
storageBackend: StorageBackend;
securityAlertsEnabled: boolean;
onPreferencesChange: (callback: (perferenceState: any) => void) => void;
onPreferencesChange: (
callback: (
// TOOD: Replace with `PreferencesState` from `@metamask/preferences-controller`
preferencesState: { securityAlertsEnabled: boolean } & Record<
string,
Json
>,
Comment on lines +294 to +298
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrote temporary PreferencesState surrogate to avoid introducing @metamask/preferences-controller as a dependency. Added TODO so this would be eventually replaced with the full type.

) => void,
) => void;
ppomProvider: PPOMProvider;
cdnBaseUrl: string;
providerRequestLimit?: number;
Expand Down Expand Up @@ -361,7 +386,7 @@ export class PPOMController extends BaseControllerV2<
* @param callback - Callback to be invoked with PPOM.
*/
async usePPOM<Type>(
callback: (ppom: any) => Promise<Type>,
callback: (ppom: PPOM) => Promise<Type>,
): Promise<Type & { providerRequestsCount: Record<string, number> }> {
if (!this.#securityAlertsEnabled) {
throw Error('User has securityAlertsEnabled set to false');
Expand All @@ -378,7 +403,9 @@ export class PPOMController extends BaseControllerV2<
this.#providerRequests = 0;
this.#providerRequestsCount = {};
return await this.#ppomMutex.use(async () => {
const result = await callback(this.#ppom);
// `this.#ppom` is defined in `#initPPOMIfRequired`
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const result = await callback(this.#ppom!);

return {
...result,
Expand Down Expand Up @@ -503,7 +530,7 @@ export class PPOMController extends BaseControllerV2<
* 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 {
#onNetworkChange(networkControllerState: NetworkState): void {
const id = addHexPrefix(networkControllerState.providerConfig.chainId);
if (id === this.#chainId) {
return;
Expand Down Expand Up @@ -537,7 +564,13 @@ export class PPOMController extends BaseControllerV2<
/*
* enable / disable PPOM validations as user changes preferences
*/
#onPreferenceChange(preferenceControllerState: any): void {
#onPreferenceChange(
// TOOD: Replace with `PreferencesState` from `@metamask/preferences-controller`
preferenceControllerState: { securityAlertsEnabled: boolean } & Record<
string,
Json
>,
Comment on lines +568 to +572
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

): void {
const blockaidEnabled = preferenceControllerState.securityAlertsEnabled;
if (blockaidEnabled === this.#securityAlertsEnabled) {
return;
Expand Down Expand Up @@ -819,9 +852,11 @@ export class PPOMController extends BaseControllerV2<
const currentTimestamp = new Date().getTime();

const chainIds = Object.keys(this.state.chainStatus);
const oldChaninIds: any[] = chainIds.filter(
const oldChainIds = chainIds.filter(
(chainId) =>
(this.state.chainStatus[chainId] as any).lastVisited <
// `chainId` is of type `keyof typeof this.state.chainStatus`
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.state.chainStatus[chainId]!.lastVisited <
currentTimestamp - NETWORK_CACHE_DURATION &&
chainId !== this.#chainId,
);
Expand All @@ -832,11 +867,13 @@ export class PPOMController extends BaseControllerV2<
Number(this.state.chainStatus[c2]?.lastVisited) -
Number(this.state.chainStatus[c1]?.lastVisited),
)[NETWORK_CACHE_LIMIT.MAX];
oldChaninIds.push(oldestChainId);
// `oldestChainId` will always be defined, as `chainIds` is guaranteed to have at least `NETWORK_CACHE_LIMIT.MAX` elements
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
oldChainIds.push(oldestChainId!);
}

const chainStatus = { ...this.state.chainStatus };
oldChaninIds.forEach((chainId) => {
oldChainIds.forEach((chainId) => {
delete chainStatus[chainId];
});

Expand Down Expand Up @@ -918,6 +955,7 @@ export class PPOMController extends BaseControllerV2<
url: string,
options: Record<string, unknown> = {},
method = 'GET',
// TODO: Fix `any` usage - provide minimum expected type for `response`.
): Promise<any> {
const response = await safelyExecute(
async () =>
Expand Down Expand Up @@ -996,8 +1034,12 @@ export class PPOMController extends BaseControllerV2<
*/
async #jsonRpcRequest(
method: string,
params: Record<string, unknown>,
): Promise<any> {
params: JsonRpcParams,
): Promise<
| JsonRpcSuccess<Json>
| (Omit<JsonRpcFailure, 'error'> & { error: unknown })
Comment on lines +1039 to +1040
Copy link
Contributor Author

@MajorLift MajorLift Feb 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used the JsonRpcResponse<Json> type here instead of unknown, even though this technically narrows the response type in the sendAsync callback compared to any (see line 1059).

This is because JsonRpcEngine is where the callback passed into sendAsync actually gets invoked, and that class consistently uses the JsonRpcResponse type for responses.

The error type in JsonRpcFailure is widened to unknown since that's the only type accepted by sendAsync. See https://github.com/MetaMask/ppom-validator/pull/89/files#r1470601394 for a discussion on this point.

Note: sendAsync is set to be deprecated in favor of request in a new EIP1193-compliant provider class. request will use JsonRpcResponse<Json>.

| ReturnType<(typeof PROVIDER_ERRORS)[keyof typeof PROVIDER_ERRORS]>
> {
return new Promise((resolve) => {
// Resolve with error if number of requests from PPOM to provider exceeds the limit for the current transaction
if (this.#providerRequests > this.#providerRequestLimit) {
Expand All @@ -1018,7 +1060,7 @@ export class PPOMController extends BaseControllerV2<
// Invoke provider and return result
this.#provider.sendAsync(
createPayload(method, params),
(error: Error, res: any) => {
(error, res: JsonRpcSuccess<Json>) => {
if (error) {
resolve({
jsonrpc: '2.0',
Expand All @@ -1039,7 +1081,7 @@ 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<any> {
async #getPPOM(chainId: string): Promise<PPOM> {
// PPOM initialisation in contructor fails for react native
// thus it is added here to prevent validation from failing.
await this.#initialisePPOM();
Expand Down
4 changes: 2 additions & 2 deletions src/ppom-storage.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { StorageKey } from './ppom-storage';
import { PPOMStorage } from './ppom-storage';
import {
DUMMY_ARRAY_BUFFER_DATA,
buildStorageBackend,
simpleStorageBackend,
storageBackendReturningData,
} from '../test/test-utils';
import type { StorageKey } from './ppom-storage';
import { PPOMStorage } from './ppom-storage';

const DUMMY_CHECKSUM = 'DUMMY_CHECKSUM';
const DUMMY_NAME = 'DUMMY_NAME';
Expand Down
2 changes: 1 addition & 1 deletion src/ppom-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export class PPOMStorage {
// check if the file is readable (e.g. corrupted or deleted)
try {
await this.readFile(fileMetadata.name, fileMetadata.chainId);
} catch (exp: any) {
} catch (exp: unknown) {
console.error('Error: ', exp);
continue;
}
Expand Down
Loading