Skip to content

Commit

Permalink
console.warn experimental wallet_watchAsset ERC721,ERC1155 (#264)
Browse files Browse the repository at this point in the history
* warn wallet_watchAsset ERC721,ERC1155

* fix describe name

* add const from @metamask/controller-utils

* Add mip1 and lifecycle links

* fix allowscripts

* update coverage

* use test looops

* Copy over constants

* Copy over constants

* update spec coverage

* lint
  • Loading branch information
jiexi authored Jun 26, 2023
1 parent 23fff81 commit 715a188
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 64 deletions.
6 changes: 3 additions & 3 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ const baseConfig = {
// An object that configures minimum threshold enforcement for coverage results
coverageThreshold: {
global: {
branches: 59.77,
branches: 61.29,
functions: 60.24,
lines: 63.15,
statements: 63.33,
lines: 63.82,
statements: 63.97,
},
},

Expand Down
4 changes: 4 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// TOKEN STANDARDS
export const ERC721 = 'ERC721';
export const ERC1155 = 'ERC1155';
export const ERC20 = 'ERC20';
1 change: 1 addition & 0 deletions src/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const messages = {
rpc: {
ethDecryptDeprecation: `MetaMask: The RPC method 'eth_decrypt' is deprecated and may be removed in the future.\nFor more information, see: https://medium.com/metamask/metamask-api-method-deprecation-2b0564a84686`,
ethGetEncryptionPublicKeyDeprecation: `MetaMask: The RPC method 'eth_getEncryptionPublicKey' is deprecated and may be removed in the future.\nFor more information, see: https://medium.com/metamask/metamask-api-method-deprecation-2b0564a84686`,
walletWatchAssetNFTExperimental: `MetaMask: The RPC method 'wallet_watchAsset' is experimental for ERC721/ERC1155 assets and may change in the future.\nFor more information, see: https://github.com/MetaMask/metamask-improvement-proposals/blob/main/MIPs/mip-1.md and https://github.com/MetaMask/metamask-improvement-proposals/blob/main/PROCESS-GUIDE.md#proposal-lifecycle`,
},
// misc
experimentalMethods: `MetaMask: 'ethereum._metamask' exposes non-standard, experimental methods. They may be removed or changed without warning.`,
Expand Down
155 changes: 95 additions & 60 deletions src/middleware/createRpcWarningMiddleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,90 +3,123 @@ import { JsonRpcEngine, JsonRpcFailure, JsonRpcSuccess } from 'json-rpc-engine';
import { createRpcWarningMiddleware } from './createRpcWarningMiddleware';
import messages from '../messages';

const warnings = [
const affected = [
{
scenario: 'eth_decrypt',
method: 'eth_decrypt',
warning: messages.warnings.rpc.ethDecryptDeprecation,
},
{
scenario: 'eth_getEncryptionPublicKey',
method: 'eth_getEncryptionPublicKey',
warning: messages.warnings.rpc.ethGetEncryptionPublicKeyDeprecation,
},
{
scenario: 'wallet_watchAsset with `type: "ERC721"`',
method: 'wallet_watchAsset',
params: {
type: 'ERC721',
options: {},
},
warning: messages.warnings.rpc.walletWatchAssetNFTExperimental,
},
{
scenario: 'wallet_watchAsset with `type: "ERC1155"`',
method: 'wallet_watchAsset',
params: {
type: 'ERC1155',
options: {},
},
warning: messages.warnings.rpc.walletWatchAssetNFTExperimental,
},
];

const unaffected = [
{
scenario: 'eth_chainId',
method: 'eth_chainId',
},
{
scenario: 'wallet_watchAsset with `type: "ERC20"`',
method: 'wallet_watchAsset',
params: {
type: 'ERC20',
options: {},
},
},
];

describe('createRpcWarningMiddleware', () => {
for (const { method, warning } of warnings) {
describe(`${method}`, () => {
it('should warn the first time the method is called', async () => {
const consoleWarnSpy = jest.spyOn(globalThis.console, 'warn');
const middleware = createRpcWarningMiddleware(globalThis.console);
const engine = new JsonRpcEngine();
engine.push(middleware);

await engine.handle({ jsonrpc: '2.0', id: 1, method });

expect(consoleWarnSpy).toHaveBeenCalledWith(warning);
expect(consoleWarnSpy).toHaveBeenCalledTimes(1);
});
describe.each(affected)('$scenario', ({ method, params = {}, warning }) => {
it('should warn the first time the method is called', async () => {
const consoleWarnSpy = jest.spyOn(globalThis.console, 'warn');
const middleware = createRpcWarningMiddleware(globalThis.console);
const engine = new JsonRpcEngine();
engine.push(middleware);

it('should not warn the second time the method is called', async () => {
const consoleWarnSpy = jest.spyOn(globalThis.console, 'warn');
const middleware = createRpcWarningMiddleware(globalThis.console);
const engine = new JsonRpcEngine();
engine.push(middleware);
await engine.handle({ jsonrpc: '2.0', id: 1, method, params });

await engine.handle({ jsonrpc: '2.0', id: 1, method });
await engine.handle({ jsonrpc: '2.0', id: 1, method });
expect(consoleWarnSpy).toHaveBeenCalledWith(warning);
expect(consoleWarnSpy).toHaveBeenCalledTimes(1);
});

expect(consoleWarnSpy).toHaveBeenCalledWith(warning);
expect(consoleWarnSpy).toHaveBeenCalledTimes(1);
});
it('should not warn the second time the method is called', async () => {
const consoleWarnSpy = jest.spyOn(globalThis.console, 'warn');
const middleware = createRpcWarningMiddleware(globalThis.console);
const engine = new JsonRpcEngine();
engine.push(middleware);

it('should allow the method to succeed', async () => {
const middleware = createRpcWarningMiddleware(globalThis.console);
const engine = new JsonRpcEngine();
engine.push(middleware);
engine.push((_req, res, _next, end) => {
res.result = 'success!';
end();
});

const response = (await engine.handle({
jsonrpc: '2.0',
id: 1,
method,
})) as JsonRpcSuccess<unknown>;

expect(response.result).toBe('success!');
await engine.handle({ jsonrpc: '2.0', id: 1, method, params });
await engine.handle({ jsonrpc: '2.0', id: 1, method, params });

expect(consoleWarnSpy).toHaveBeenCalledWith(warning);
expect(consoleWarnSpy).toHaveBeenCalledTimes(1);
});

it('should allow the method to succeed', async () => {
const middleware = createRpcWarningMiddleware(globalThis.console);
const engine = new JsonRpcEngine();
engine.push(middleware);
engine.push((_req, res, _next, end) => {
res.result = 'success!';
end();
});

it('should allow the method to fail', async () => {
const middleware = createRpcWarningMiddleware(globalThis.console);
const engine = new JsonRpcEngine();
engine.push(middleware);
engine.push(() => {
throw new Error('Failure!');
});

const result = (await engine.handle({
jsonrpc: '2.0',
id: 1,
method,
})) as JsonRpcFailure;

expect(result.error.message).toBe('Failure!');
const response = (await engine.handle({
jsonrpc: '2.0',
id: 1,
method,
})) as JsonRpcSuccess<unknown>;

expect(response.result).toBe('success!');
});

it('should allow the method to fail', async () => {
const middleware = createRpcWarningMiddleware(globalThis.console);
const engine = new JsonRpcEngine();
engine.push(middleware);
engine.push(() => {
throw new Error('Failure!');
});

const result = (await engine.handle({
jsonrpc: '2.0',
id: 1,
method,
})) as JsonRpcFailure;

expect(result.error.message).toBe('Failure!');
});
}
});

describe('unaffected method', () => {
describe.each(unaffected)('$scenario', ({ method, params = {} }) => {
it('should not issue a warning', async () => {
const consoleWarnSpy = jest.spyOn(globalThis.console, 'warn');
const middleware = createRpcWarningMiddleware(globalThis.console);
const engine = new JsonRpcEngine();
engine.push(middleware);

await engine.handle({ jsonrpc: '2.0', id: 1, method: 'eth_chainId' });
await engine.handle({ jsonrpc: '2.0', id: 1, method, params });

expect(consoleWarnSpy).not.toHaveBeenCalled();
});
Expand All @@ -103,7 +136,8 @@ describe('createRpcWarningMiddleware', () => {
const response = (await engine.handle({
jsonrpc: '2.0',
id: 1,
method: 'eth_chainId',
method,
params,
})) as JsonRpcSuccess<unknown>;

expect(response.result).toBe('success!');
Expand All @@ -120,7 +154,8 @@ describe('createRpcWarningMiddleware', () => {
const result = (await engine.handle({
jsonrpc: '2.0',
id: 1,
method: 'eth_chainId',
method,
params,
})) as JsonRpcFailure;

expect(result.error.message).toBe('Failure!');
Expand Down
13 changes: 12 additions & 1 deletion src/middleware/createRpcWarningMiddleware.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { JsonRpcMiddleware } from 'json-rpc-engine';
import type { JsonRpcMiddleware, JsonRpcRequest } from 'json-rpc-engine';

import { ERC1155, ERC721 } from '../constants';
import messages from '../messages';
import type { ConsoleLike } from '../utils';

Expand All @@ -15,6 +16,7 @@ export function createRpcWarningMiddleware(
const sentWarnings = {
ethDecryptDeprecation: false,
ethGetEncryptionPublicKeyDeprecation: false,
walletWatchAssetNFTExperimental: false,
};

return (req, _res, next) => {
Expand All @@ -27,6 +29,15 @@ export function createRpcWarningMiddleware(
) {
log.warn(messages.warnings.rpc.ethGetEncryptionPublicKeyDeprecation);
sentWarnings.ethGetEncryptionPublicKeyDeprecation = true;
} else if (
!sentWarnings.walletWatchAssetNFTExperimental &&
req.method === 'wallet_watchAsset' &&
[ERC721, ERC1155].includes(
(req as JsonRpcRequest<{ type: string }>).params?.type || '',
)
) {
log.warn(messages.warnings.rpc.walletWatchAssetNFTExperimental);
sentWarnings.walletWatchAssetNFTExperimental = true;
}
next();
};
Expand Down

0 comments on commit 715a188

Please sign in to comment.