2023-03-14 19:31:19 +01:00
|
|
|
import { errorCodes } from 'eth-rpc-errors';
|
2023-04-04 17:25:57 +02:00
|
|
|
import { detectSIWE } from '@metamask/controller-utils';
|
2022-07-20 15:25:04 +02:00
|
|
|
import { MESSAGE_TYPE } from '../../../shared/constants/app';
|
2023-03-29 20:25:01 +02:00
|
|
|
import {
|
2023-04-03 17:31:04 +02:00
|
|
|
MetaMetricsEventName,
|
|
|
|
MetaMetricsEventUiCustomization,
|
2023-03-29 20:25:01 +02:00
|
|
|
} from '../../../shared/constants/metametrics';
|
2022-07-20 15:25:04 +02:00
|
|
|
import { SECOND } from '../../../shared/constants/time';
|
|
|
|
import createRPCMethodTrackingMiddleware from './createRPCMethodTrackingMiddleware';
|
|
|
|
|
|
|
|
const trackEvent = jest.fn();
|
|
|
|
const metricsState = { participateInMetaMetrics: null };
|
|
|
|
const getMetricsState = () => metricsState;
|
|
|
|
|
2023-03-23 18:01:51 +01:00
|
|
|
let flagAsDangerous = 0;
|
|
|
|
|
|
|
|
const securityProviderRequest = () => {
|
|
|
|
return {
|
|
|
|
flagAsDangerous,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2022-07-20 15:25:04 +02:00
|
|
|
const handler = createRPCMethodTrackingMiddleware({
|
|
|
|
trackEvent,
|
|
|
|
getMetricsState,
|
|
|
|
rateLimitSeconds: 1,
|
2023-03-23 18:01:51 +01:00
|
|
|
securityProviderRequest,
|
2022-07-20 15:25:04 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
function getNext(timeout = 500) {
|
|
|
|
let deferred;
|
|
|
|
const promise = new Promise((resolve) => {
|
|
|
|
deferred = {
|
|
|
|
resolve,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
const cb = () => deferred.resolve();
|
|
|
|
let triggerNext;
|
|
|
|
setTimeout(() => {
|
|
|
|
deferred.resolve();
|
|
|
|
}, timeout);
|
|
|
|
return {
|
|
|
|
executeMiddlewareStack: async () => {
|
|
|
|
if (triggerNext) {
|
|
|
|
triggerNext(() => cb());
|
|
|
|
}
|
|
|
|
return await deferred.resolve();
|
|
|
|
},
|
|
|
|
promise,
|
|
|
|
next: (postReqHandler) => {
|
|
|
|
triggerNext = postReqHandler;
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
const waitForSeconds = async (seconds) =>
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, SECOND * seconds));
|
|
|
|
|
2023-04-04 17:25:57 +02:00
|
|
|
jest.mock('@metamask/controller-utils', () => ({
|
2023-03-29 20:25:01 +02:00
|
|
|
detectSIWE: jest.fn().mockImplementation(() => {
|
|
|
|
return { isSIWEMessage: false };
|
|
|
|
}),
|
|
|
|
}));
|
|
|
|
|
2022-07-20 15:25:04 +02:00
|
|
|
describe('createRPCMethodTrackingMiddleware', () => {
|
|
|
|
afterEach(() => {
|
|
|
|
jest.resetAllMocks();
|
|
|
|
metricsState.participateInMetaMetrics = null;
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('before participateInMetaMetrics is set', () => {
|
|
|
|
it('should not track an event for a signature request', async () => {
|
|
|
|
const req = {
|
|
|
|
method: MESSAGE_TYPE.ETH_SIGN,
|
|
|
|
origin: 'some.dapp',
|
|
|
|
};
|
|
|
|
|
|
|
|
const res = {
|
|
|
|
error: null,
|
|
|
|
};
|
|
|
|
const { executeMiddlewareStack, next } = getNext();
|
|
|
|
handler(req, res, next);
|
|
|
|
await executeMiddlewareStack();
|
|
|
|
expect(trackEvent).not.toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('participateInMetaMetrics is set to false', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
metricsState.participateInMetaMetrics = false;
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should not track an event for a signature request', async () => {
|
|
|
|
const req = {
|
|
|
|
method: MESSAGE_TYPE.ETH_SIGN,
|
|
|
|
origin: 'some.dapp',
|
|
|
|
};
|
|
|
|
|
|
|
|
const res = {
|
|
|
|
error: null,
|
|
|
|
};
|
|
|
|
const { executeMiddlewareStack, next } = getNext();
|
|
|
|
handler(req, res, next);
|
|
|
|
await executeMiddlewareStack();
|
|
|
|
expect(trackEvent).not.toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('participateInMetaMetrics is set to true', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
metricsState.participateInMetaMetrics = true;
|
|
|
|
});
|
|
|
|
|
2023-04-03 17:31:04 +02:00
|
|
|
it(`should immediately track a ${MetaMetricsEventName.SignatureRequested} event`, async () => {
|
2022-07-20 15:25:04 +02:00
|
|
|
const req = {
|
|
|
|
method: MESSAGE_TYPE.ETH_SIGN,
|
|
|
|
origin: 'some.dapp',
|
|
|
|
};
|
|
|
|
|
|
|
|
const res = {
|
|
|
|
error: null,
|
|
|
|
};
|
|
|
|
const { next } = getNext();
|
2023-03-23 18:01:51 +01:00
|
|
|
await handler(req, res, next);
|
2022-07-20 15:25:04 +02:00
|
|
|
expect(trackEvent).toHaveBeenCalledTimes(1);
|
|
|
|
expect(trackEvent.mock.calls[0][0]).toMatchObject({
|
|
|
|
category: 'inpage_provider',
|
2023-04-03 17:31:04 +02:00
|
|
|
event: MetaMetricsEventName.SignatureRequested,
|
2023-03-23 18:01:51 +01:00
|
|
|
properties: {
|
|
|
|
signature_type: MESSAGE_TYPE.ETH_SIGN,
|
|
|
|
},
|
2022-07-20 15:25:04 +02:00
|
|
|
referrer: { url: 'some.dapp' },
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2023-04-03 17:31:04 +02:00
|
|
|
it(`should track a ${MetaMetricsEventName.SignatureApproved} event if the user approves`, async () => {
|
2022-07-20 15:25:04 +02:00
|
|
|
const req = {
|
|
|
|
method: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4,
|
|
|
|
origin: 'some.dapp',
|
|
|
|
};
|
|
|
|
|
|
|
|
const res = {
|
|
|
|
error: null,
|
|
|
|
};
|
|
|
|
const { next, executeMiddlewareStack } = getNext();
|
2023-03-23 18:01:51 +01:00
|
|
|
await handler(req, res, next);
|
2022-07-20 15:25:04 +02:00
|
|
|
await executeMiddlewareStack();
|
|
|
|
expect(trackEvent).toHaveBeenCalledTimes(2);
|
|
|
|
expect(trackEvent.mock.calls[1][0]).toMatchObject({
|
|
|
|
category: 'inpage_provider',
|
2023-04-03 17:31:04 +02:00
|
|
|
event: MetaMetricsEventName.SignatureApproved,
|
2023-03-23 18:01:51 +01:00
|
|
|
properties: {
|
|
|
|
signature_type: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4,
|
|
|
|
},
|
2022-07-20 15:25:04 +02:00
|
|
|
referrer: { url: 'some.dapp' },
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2023-04-03 17:31:04 +02:00
|
|
|
it(`should track a ${MetaMetricsEventName.SignatureRejected} event if the user approves`, async () => {
|
2022-07-20 15:25:04 +02:00
|
|
|
const req = {
|
|
|
|
method: MESSAGE_TYPE.PERSONAL_SIGN,
|
|
|
|
origin: 'some.dapp',
|
|
|
|
};
|
|
|
|
|
|
|
|
const res = {
|
2023-03-29 20:25:01 +02:00
|
|
|
error: { code: errorCodes.provider.userRejectedRequest },
|
2022-07-20 15:25:04 +02:00
|
|
|
};
|
|
|
|
const { next, executeMiddlewareStack } = getNext();
|
2023-03-23 18:01:51 +01:00
|
|
|
await handler(req, res, next);
|
2022-07-20 15:25:04 +02:00
|
|
|
await executeMiddlewareStack();
|
|
|
|
expect(trackEvent).toHaveBeenCalledTimes(2);
|
|
|
|
expect(trackEvent.mock.calls[1][0]).toMatchObject({
|
|
|
|
category: 'inpage_provider',
|
2023-04-03 17:31:04 +02:00
|
|
|
event: MetaMetricsEventName.SignatureRejected,
|
2023-03-23 18:01:51 +01:00
|
|
|
properties: {
|
|
|
|
signature_type: MESSAGE_TYPE.PERSONAL_SIGN,
|
|
|
|
},
|
2022-07-20 15:25:04 +02:00
|
|
|
referrer: { url: 'some.dapp' },
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2023-04-03 17:31:04 +02:00
|
|
|
it(`should track a ${MetaMetricsEventName.PermissionsApproved} event if the user approves`, async () => {
|
2022-07-20 15:25:04 +02:00
|
|
|
const req = {
|
|
|
|
method: MESSAGE_TYPE.ETH_REQUEST_ACCOUNTS,
|
|
|
|
origin: 'some.dapp',
|
|
|
|
};
|
|
|
|
|
|
|
|
const res = {};
|
|
|
|
const { next, executeMiddlewareStack } = getNext();
|
2023-03-23 18:01:51 +01:00
|
|
|
await handler(req, res, next);
|
2022-07-20 15:25:04 +02:00
|
|
|
await executeMiddlewareStack();
|
|
|
|
expect(trackEvent).toHaveBeenCalledTimes(2);
|
|
|
|
expect(trackEvent.mock.calls[1][0]).toMatchObject({
|
|
|
|
category: 'inpage_provider',
|
2023-04-03 17:31:04 +02:00
|
|
|
event: MetaMetricsEventName.PermissionsApproved,
|
2022-07-20 15:25:04 +02:00
|
|
|
properties: { method: MESSAGE_TYPE.ETH_REQUEST_ACCOUNTS },
|
|
|
|
referrer: { url: 'some.dapp' },
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it(`should never track blocked methods such as ${MESSAGE_TYPE.GET_PROVIDER_STATE}`, () => {
|
|
|
|
const req = {
|
|
|
|
method: MESSAGE_TYPE.GET_PROVIDER_STATE,
|
|
|
|
origin: 'www.notadapp.com',
|
|
|
|
};
|
|
|
|
|
|
|
|
const res = {
|
|
|
|
error: null,
|
|
|
|
};
|
|
|
|
const { next, executeMiddlewareStack } = getNext();
|
|
|
|
handler(req, res, next);
|
|
|
|
expect(trackEvent).not.toHaveBeenCalled();
|
|
|
|
executeMiddlewareStack();
|
|
|
|
});
|
|
|
|
|
|
|
|
it(`should only track events when not rate limited`, async () => {
|
|
|
|
const req = {
|
|
|
|
method: 'eth_chainId',
|
|
|
|
origin: 'some.dapp',
|
|
|
|
};
|
|
|
|
|
|
|
|
const res = {
|
|
|
|
error: null,
|
|
|
|
};
|
|
|
|
|
|
|
|
let callCount = 0;
|
|
|
|
|
|
|
|
while (callCount < 3) {
|
|
|
|
callCount += 1;
|
|
|
|
const { next, executeMiddlewareStack } = getNext();
|
|
|
|
handler(req, res, next);
|
|
|
|
await executeMiddlewareStack();
|
|
|
|
if (callCount !== 3) {
|
|
|
|
await waitForSeconds(0.6);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
expect(trackEvent).toHaveBeenCalledTimes(2);
|
|
|
|
expect(trackEvent.mock.calls[0][0].properties.method).toBe('eth_chainId');
|
|
|
|
expect(trackEvent.mock.calls[1][0].properties.method).toBe('eth_chainId');
|
|
|
|
});
|
2023-03-14 19:31:19 +01:00
|
|
|
|
2023-03-29 20:25:01 +02:00
|
|
|
it('should track Sign-in With Ethereum (SIWE) message if detected', async () => {
|
|
|
|
const req = {
|
|
|
|
method: MESSAGE_TYPE.PERSONAL_SIGN,
|
|
|
|
origin: 'some.dapp',
|
|
|
|
};
|
|
|
|
const res = {
|
|
|
|
error: null,
|
|
|
|
};
|
|
|
|
const { next, executeMiddlewareStack } = getNext();
|
|
|
|
|
|
|
|
detectSIWE.mockImplementation(() => {
|
|
|
|
return { isSIWEMessage: true };
|
|
|
|
});
|
|
|
|
|
|
|
|
await handler(req, res, next);
|
|
|
|
await executeMiddlewareStack();
|
|
|
|
|
|
|
|
expect(trackEvent).toHaveBeenCalledTimes(2);
|
|
|
|
|
|
|
|
expect(trackEvent.mock.calls[1][0]).toMatchObject({
|
|
|
|
category: 'inpage_provider',
|
2023-04-03 17:31:04 +02:00
|
|
|
event: MetaMetricsEventName.SignatureApproved,
|
2023-03-29 20:25:01 +02:00
|
|
|
properties: {
|
|
|
|
signature_type: MESSAGE_TYPE.PERSONAL_SIGN,
|
2023-04-03 17:31:04 +02:00
|
|
|
ui_customizations: [MetaMetricsEventUiCustomization.Siwe],
|
2023-03-29 20:25:01 +02:00
|
|
|
},
|
|
|
|
referrer: { url: 'some.dapp' },
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2023-03-14 19:31:19 +01:00
|
|
|
describe(`when '${MESSAGE_TYPE.ETH_SIGN}' is disabled in advanced settings`, () => {
|
2023-04-03 17:31:04 +02:00
|
|
|
it(`should track ${MetaMetricsEventName.SignatureFailed} and include error property`, async () => {
|
2023-03-14 19:31:19 +01:00
|
|
|
const mockError = { code: errorCodes.rpc.methodNotFound };
|
|
|
|
const req = {
|
|
|
|
method: MESSAGE_TYPE.ETH_SIGN,
|
|
|
|
origin: 'some.dapp',
|
|
|
|
};
|
|
|
|
const res = {
|
|
|
|
error: mockError,
|
|
|
|
};
|
|
|
|
const { next, executeMiddlewareStack } = getNext();
|
|
|
|
|
2023-03-23 18:01:51 +01:00
|
|
|
await handler(req, res, next);
|
2023-03-14 19:31:19 +01:00
|
|
|
await executeMiddlewareStack();
|
|
|
|
|
|
|
|
expect(trackEvent).toHaveBeenCalledTimes(2);
|
|
|
|
|
|
|
|
expect(trackEvent.mock.calls[1][0]).toMatchObject({
|
|
|
|
category: 'inpage_provider',
|
2023-04-03 17:31:04 +02:00
|
|
|
event: MetaMetricsEventName.SignatureFailed,
|
2023-03-14 19:31:19 +01:00
|
|
|
properties: {
|
|
|
|
signature_type: MESSAGE_TYPE.ETH_SIGN,
|
|
|
|
error: mockError,
|
|
|
|
},
|
|
|
|
referrer: { url: 'some.dapp' },
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2023-03-23 18:01:51 +01:00
|
|
|
|
2023-03-29 20:25:01 +02:00
|
|
|
describe('when request is flagged as safe by security provider', () => {
|
2023-04-03 17:31:04 +02:00
|
|
|
it(`should immediately track a ${MetaMetricsEventName.SignatureRequested} event`, async () => {
|
2023-03-29 20:25:01 +02:00
|
|
|
const req = {
|
|
|
|
method: MESSAGE_TYPE.ETH_SIGN,
|
|
|
|
origin: 'some.dapp',
|
|
|
|
};
|
|
|
|
const res = {
|
|
|
|
error: null,
|
|
|
|
};
|
|
|
|
const { next } = getNext();
|
2023-03-23 18:01:51 +01:00
|
|
|
|
2023-03-29 20:25:01 +02:00
|
|
|
await handler(req, res, next);
|
2023-03-23 18:01:51 +01:00
|
|
|
|
2023-03-29 20:25:01 +02:00
|
|
|
expect(trackEvent).toHaveBeenCalledTimes(1);
|
|
|
|
expect(trackEvent.mock.calls[0][0]).toMatchObject({
|
|
|
|
category: 'inpage_provider',
|
2023-04-03 17:31:04 +02:00
|
|
|
event: MetaMetricsEventName.SignatureRequested,
|
2023-03-29 20:25:01 +02:00
|
|
|
properties: {
|
|
|
|
signature_type: MESSAGE_TYPE.ETH_SIGN,
|
|
|
|
},
|
|
|
|
referrer: { url: 'some.dapp' },
|
|
|
|
});
|
2023-03-23 18:01:51 +01:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2023-03-29 20:25:01 +02:00
|
|
|
describe('when request is flagged as malicious by security provider', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
flagAsDangerous = 1;
|
|
|
|
});
|
2023-03-23 18:01:51 +01:00
|
|
|
|
2023-04-03 17:31:04 +02:00
|
|
|
it(`should immediately track a ${MetaMetricsEventName.SignatureRequested} event which is flagged as malicious`, async () => {
|
2023-03-29 20:25:01 +02:00
|
|
|
const req = {
|
|
|
|
method: MESSAGE_TYPE.ETH_SIGN,
|
|
|
|
origin: 'some.dapp',
|
|
|
|
};
|
|
|
|
const res = {
|
|
|
|
error: null,
|
|
|
|
};
|
|
|
|
const { next } = getNext();
|
2023-03-23 18:01:51 +01:00
|
|
|
|
2023-03-29 20:25:01 +02:00
|
|
|
await handler(req, res, next);
|
|
|
|
|
|
|
|
expect(trackEvent).toHaveBeenCalledTimes(1);
|
|
|
|
expect(trackEvent.mock.calls[0][0]).toMatchObject({
|
|
|
|
category: 'inpage_provider',
|
2023-04-03 17:31:04 +02:00
|
|
|
event: MetaMetricsEventName.SignatureRequested,
|
2023-03-29 20:25:01 +02:00
|
|
|
properties: {
|
|
|
|
signature_type: MESSAGE_TYPE.ETH_SIGN,
|
|
|
|
ui_customizations: ['flagged_as_malicious'],
|
|
|
|
},
|
|
|
|
referrer: { url: 'some.dapp' },
|
|
|
|
});
|
2023-03-23 18:01:51 +01:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2023-03-29 20:25:01 +02:00
|
|
|
describe('when request flagged as safety unknown by security provider', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
flagAsDangerous = 2;
|
|
|
|
});
|
2023-03-23 18:01:51 +01:00
|
|
|
|
2023-04-03 17:31:04 +02:00
|
|
|
it(`should immediately track a ${MetaMetricsEventName.SignatureRequested} event which is flagged as safety unknown`, async () => {
|
2023-03-29 20:25:01 +02:00
|
|
|
const req = {
|
|
|
|
method: MESSAGE_TYPE.ETH_SIGN,
|
|
|
|
origin: 'some.dapp',
|
|
|
|
};
|
|
|
|
const res = {
|
|
|
|
error: null,
|
|
|
|
};
|
|
|
|
const { next } = getNext();
|
2023-03-23 18:01:51 +01:00
|
|
|
|
2023-03-29 20:25:01 +02:00
|
|
|
await handler(req, res, next);
|
|
|
|
|
|
|
|
expect(trackEvent).toHaveBeenCalledTimes(1);
|
|
|
|
expect(trackEvent.mock.calls[0][0]).toMatchObject({
|
|
|
|
category: 'inpage_provider',
|
2023-04-03 17:31:04 +02:00
|
|
|
event: MetaMetricsEventName.SignatureRequested,
|
2023-03-29 20:25:01 +02:00
|
|
|
properties: {
|
|
|
|
signature_type: MESSAGE_TYPE.ETH_SIGN,
|
|
|
|
ui_customizations: ['flagged_as_safety_unknown'],
|
|
|
|
},
|
|
|
|
referrer: { url: 'some.dapp' },
|
|
|
|
});
|
2023-03-23 18:01:51 +01:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2022-07-20 15:25:04 +02:00
|
|
|
});
|