Skip to content

Commit

Permalink
Add more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Mrtenz committed Nov 23, 2022
1 parent b285e63 commit 7a4f207
Show file tree
Hide file tree
Showing 6 changed files with 278 additions and 19 deletions.
4 changes: 2 additions & 2 deletions packages/snaps-controllers/jest.config.js
Expand Up @@ -7,8 +7,8 @@ module.exports = deepmerge(baseConfig, {
global: {
branches: 88.74,
functions: 97.6,
lines: 96.74,
statements: 96.74,
lines: 97.1,
statements: 97.1,
},
},
projects: [
Expand Down
123 changes: 122 additions & 1 deletion packages/snaps-controllers/src/snaps/SnapController.test.ts
Expand Up @@ -45,12 +45,16 @@ import {
getSnapControllerWithEES,
getSnapControllerWithEESOptions,
MOCK_BLOCK_NUMBER,
MOCK_DAPP_SUBJECT_METADATA,
MOCK_DAPPS_RPC_ORIGINS_PERMISSION,
MOCK_NAMESPACES,
MOCK_RPC_ORIGINS_PERMISSION,
MOCK_SNAP_SUBJECT_METADATA,
PERSISTED_MOCK_KEYRING_SNAP,
sleep,
} from '../test-utils';
import { delay } from '../utils';
import { SnapEndowments } from './endowments';
import { handlerEndowments, SnapEndowments } from './endowments';
import { SnapControllerState, SNAP_APPROVAL_UPDATE } from './SnapController';

const { subtle } = new Crypto();
Expand Down Expand Up @@ -1319,6 +1323,123 @@ describe('SnapController', () => {
await service.terminateAllSnaps();
});

describe('handleRequest', () => {
it.each(Object.keys(handlerEndowments) as HandlerType[])(
'throws if the snap does not have permission for the handler',
async (handler) => {
const rootMessenger = getControllerMessenger();
const messenger = getSnapControllerMessenger(rootMessenger);
const snapController = getSnapController(
getSnapControllerOptions({
messenger,
state: {
snaps: getPersistedSnapsState(),
},
}),
);

rootMessenger.registerActionHandler(
'PermissionController:hasPermission',
() => false,
);

const snap = snapController.getExpect(MOCK_SNAP_ID);
await expect(
snapController.handleRequest({
snapId: snap.id,
origin: 'foo.com',
handler,
request: { jsonrpc: '2.0', method: 'test' },
}),
).rejects.toThrow(
`Snap "${snap.id}" is not permitted to use "${handlerEndowments[handler]}".`,
);

snapController.destroy();
},
);

it('throws if the snap does not have permission to handle JSON-RPC requests from dapps', async () => {
const rootMessenger = getControllerMessenger();
const messenger = getSnapControllerMessenger(rootMessenger);
const snapController = getSnapController(
getSnapControllerOptions({
messenger,
state: {
snaps: getPersistedSnapsState(),
},
}),
);

rootMessenger.registerActionHandler(
'PermissionController:getPermissions',
() => ({
// Permission to receive JSON-RPC requests from other Snaps.
[SnapEndowments.Rpc]: MOCK_RPC_ORIGINS_PERMISSION,
}),
);

rootMessenger.registerActionHandler(
'SubjectMetadataController:getSubjectMetadata',
() => MOCK_DAPP_SUBJECT_METADATA,
);

const snap = snapController.getExpect(MOCK_SNAP_ID);
await expect(
snapController.handleRequest({
snapId: snap.id,
origin: MOCK_ORIGIN,
handler: HandlerType.OnRpcRequest,
request: { jsonrpc: '2.0', method: 'test' },
}),
).rejects.toThrow(
`Snap "${snap.id}" is not permitted to handle JSON-RPC requests from "${MOCK_ORIGIN}".`,
);

snapController.destroy();
});

it('throws if the snap does not have permission to handle JSON-RPC requests from snaps', async () => {
const rootMessenger = getControllerMessenger();
const messenger = getSnapControllerMessenger(rootMessenger);
const snapController = getSnapController(
getSnapControllerOptions({
messenger,
state: {
snaps: getPersistedSnapsState(),
},
}),
);

rootMessenger.registerActionHandler(
'PermissionController:getPermissions',
() => ({
// Permission to receive JSON-RPC requests from dapps.
[SnapEndowments.Rpc]: MOCK_DAPPS_RPC_ORIGINS_PERMISSION,
}),
);

rootMessenger.registerActionHandler(
'SubjectMetadataController:getSubjectMetadata',
() => MOCK_SNAP_SUBJECT_METADATA,
);

const snap = snapController.getExpect(MOCK_SNAP_ID);
await expect(
snapController.handleRequest({
snapId: snap.id,
origin: MOCK_SNAP_ID,
handler: HandlerType.OnRpcRequest,
request: { jsonrpc: '2.0', method: 'test' },
}),
).rejects.toThrow(
`Snap "${snap.id}" is not permitted to handle JSON-RPC requests from "${MOCK_SNAP_ID}".`,
);

snapController.destroy();
});
});

describe('getRpcRequestHandler', () => {
it('handlers populate the "jsonrpc" property if missing', async () => {
const rootMessenger = getControllerMessenger();
Expand Down
2 changes: 1 addition & 1 deletion packages/snaps-controllers/src/snaps/endowments/index.ts
Expand Up @@ -56,7 +56,7 @@ export const endowmentCaveatMappers: Record<
[rpcEndowmentBuilder.targetKey]: getRpcCaveatMapper,
};

export const handlerEndowments = {
export const handlerEndowments: Record<HandlerType, string> = {
[HandlerType.OnRpcRequest]: rpcEndowmentBuilder.targetKey,
[HandlerType.SnapKeyring]: keyringEndowmentBuilder.targetKey,
[HandlerType.OnTransaction]: transactionInsightEndowmentBuilder.targetKey,
Expand Down
129 changes: 129 additions & 0 deletions packages/snaps-controllers/src/snaps/endowments/rpc.test.ts
@@ -0,0 +1,129 @@
import { PermissionType } from '@metamask/permission-controller';
import { SnapCaveatType } from '@metamask/snaps-utils';

import { SnapEndowments } from '.';
import {
getRpcCaveatMapper,
getRpcCaveatOrigins,
rpcCaveatSpecifications,
rpcEndowmentBuilder,
} from './rpc';

describe('endowment:rpc', () => {
it('builds the expected permission specification', () => {
const specification = rpcEndowmentBuilder.specificationBuilder({});
expect(specification).toStrictEqual({
permissionType: PermissionType.Endowment,
targetKey: SnapEndowments.Rpc,
endowmentGetter: expect.any(Function),
allowedCaveats: [SnapCaveatType.RpcOrigin],
validator: expect.any(Function),
});

expect(specification.endowmentGetter()).toBeUndefined();
});

describe('validator', () => {
it('throws if the caveat is not a single "transactionOrigin"', () => {
const specification = rpcEndowmentBuilder.specificationBuilder({});

expect(() =>
specification.validator({
// @ts-expect-error Missing other required permission types.
caveats: undefined,
}),
).toThrow('Expected a single "rpcOrigin" caveat.');

expect(() =>
// @ts-expect-error Missing other required permission types.
specification.validator({
caveats: [{ type: 'foo', value: 'bar' }],
}),
).toThrow('Expected a single "rpcOrigin" caveat.');

expect(() =>
// @ts-expect-error Missing other required permission types.
specification.validator({
caveats: [
{ type: 'rpcOrigin', value: { snaps: true, dapps: false } },
{ type: 'rpcOrigin', value: { snaps: true, dapps: false } },
],
}),
).toThrow('Expected a single "rpcOrigin" caveat.');
});
});
});

describe('getRpcCaveatMapper', () => {
it('maps a value to a caveat', () => {
expect(getRpcCaveatMapper({ snaps: true, dapps: false })).toStrictEqual({
caveats: [
{
type: SnapCaveatType.RpcOrigin,
value: { snaps: true, dapps: false },
},
],
});
});
});

describe('getRpcCaveatOrigins', () => {
it('returns the origins from the caveat', () => {
expect(
// @ts-expect-error Missing other required permission types.
getRpcCaveatOrigins({
caveats: [
{
type: SnapCaveatType.RpcOrigin,
value: { snaps: true, dapps: false },
},
],
}),
).toStrictEqual({ snaps: true, dapps: false });
});

it('throws if the caveat is not a single "rpcOrigin"', () => {
expect(() =>
// @ts-expect-error Missing other required permission types.
getRpcCaveatOrigins({
caveats: [{ type: 'foo', value: 'bar' }],
}),
).toThrow('Assertion failed.');

expect(() =>
// @ts-expect-error Missing other required permission types.
getRpcCaveatOrigins({
caveats: [
{ type: 'rpcOrigin', value: { snaps: true, dapps: false } },
{ type: 'rpcOrigin', value: { snaps: true, dapps: false } },
],
}),
).toThrow('Assertion failed.');
});
});

describe('rpcCaveatSpecifications', () => {
describe('validator', () => {
it('throws if the caveat values are invalid', () => {
expect(() =>
rpcCaveatSpecifications[SnapCaveatType.RpcOrigin].validator?.(
// @ts-expect-error Missing value type.
{
type: SnapCaveatType.TransactionOrigin,
},
),
).toThrow('Invalid JSON-RPC origins: Expected a plain object.');

expect(() =>
rpcCaveatSpecifications[SnapCaveatType.RpcOrigin].validator?.({
type: SnapCaveatType.TransactionOrigin,
value: {
foo: 'bar',
},
}),
).toThrow(
'Invalid JSON-RPC origins: At path: foo -- Expected a value of type `never`, but received: `"bar"`.',
);
});
});
});
16 changes: 3 additions & 13 deletions packages/snaps-controllers/src/snaps/endowments/rpc.ts
Expand Up @@ -81,19 +81,12 @@ export const rpcEndowmentBuilder = Object.freeze({
function validateCaveatOrigins(caveat: Caveat<string, any>) {
if (!hasProperty(caveat, 'value') || !isPlainObject(caveat.value)) {
throw ethErrors.rpc.invalidParams({
message: 'Expected a plain object.',
message: 'Invalid JSON-RPC origins: Expected a plain object.',
});
}

const { value } = caveat;

if (!hasProperty(value, 'origins') || !isPlainObject(value)) {
throw ethErrors.rpc.invalidParams({
message: 'Expected a plain object.',
});
}

assertIsRpcOrigins(value.origins, ethErrors.rpc.invalidParams);
assertIsRpcOrigins(value, ethErrors.rpc.invalidParams);
}

/**
Expand Down Expand Up @@ -127,10 +120,7 @@ export function getRpcCaveatMapper(
export function getRpcCaveatOrigins(
permission?: PermissionConstraint,
): RpcOrigins | null {
if (!permission?.caveats) {
return null;
}

assert(permission?.caveats);
assert(permission.caveats.length === 1);
assert(permission.caveats[0].type === SnapCaveatType.RpcOrigin);

Expand Down
23 changes: 21 additions & 2 deletions packages/snaps-controllers/src/test-utils/controller.ts
Expand Up @@ -8,6 +8,7 @@ import { SnapCaveatType } from '@metamask/snaps-utils';
import {
getPersistedSnapObject,
getTruncatedSnap,
MOCK_ORIGIN,
MOCK_SNAP_ID,
} from '@metamask/snaps-utils/test-utils';
import {
Expand Down Expand Up @@ -75,14 +76,22 @@ export class MockControllerMessenger<
}
}

export const MOCK_SUBJECT_METADATA: SubjectMetadata = {
export const MOCK_SNAP_SUBJECT_METADATA: SubjectMetadata = {
origin: MOCK_SNAP_ID,
subjectType: SubjectType.Snap,
name: 'foo',
extensionId: 'bar',
iconUrl: 'baz',
};

export const MOCK_DAPP_SUBJECT_METADATA: SubjectMetadata = {
origin: MOCK_ORIGIN,
subjectType: SubjectType.Website,
name: 'foo',
extensionId: 'bar',
iconUrl: 'baz',
};

export const MOCK_RPC_ORIGINS_PERMISSION: PermissionConstraint = {
caveats: [
{ type: SnapCaveatType.RpcOrigin, value: { snaps: true, dapps: false } },
Expand All @@ -93,6 +102,16 @@ export const MOCK_RPC_ORIGINS_PERMISSION: PermissionConstraint = {
parentCapability: SnapEndowments.Rpc,
};

export const MOCK_DAPPS_RPC_ORIGINS_PERMISSION: PermissionConstraint = {
caveats: [
{ type: SnapCaveatType.RpcOrigin, value: { snaps: false, dapps: true } },
],
date: 1664187844588,
id: 'izn0WGUO8cvq_jqvLQuQP',
invoker: MOCK_SNAP_ID,
parentCapability: SnapEndowments.Rpc,
};

export const getControllerMessenger = () => {
const messenger = new MockControllerMessenger<
SnapControllerActions | AllowedActions,
Expand Down Expand Up @@ -153,7 +172,7 @@ export const getControllerMessenger = () => {

messenger.registerActionHandler(
'SubjectMetadataController:getSubjectMetadata',
() => MOCK_SUBJECT_METADATA,
() => MOCK_SNAP_SUBJECT_METADATA,
);

messenger.registerActionHandler('ExecutionService:executeSnap', asyncNoOp);
Expand Down

0 comments on commit 7a4f207

Please sign in to comment.