Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

warn wallet_watchAsset ERC721,ERC1155 #264

Merged
merged 12 commits into from
Jun 26, 2023
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 || '',
jiexi marked this conversation as resolved.
Show resolved Hide resolved
)
) {
log.warn(messages.warnings.rpc.walletWatchAssetNFTExperimental);
sentWarnings.walletWatchAssetNFTExperimental = true;
}
next();
};
Expand Down