From 67d1fb0eda5e4655bca049d483759f332ab18023 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Thu, 3 Nov 2022 12:53:11 +0100 Subject: [PATCH 1/6] Add transaction insight caveat for accessing transaction origin --- .../src/snaps/endowments/index.ts | 9 +- .../snaps/endowments/transaction-insight.ts | 124 +++++++++++++++++- .../src/common/commands.ts | 3 +- .../src/common/validation.ts | 2 + packages/snaps-types/src/types.d.ts | 1 + packages/snaps-utils/src/caveats.ts | 5 + 6 files changed, 140 insertions(+), 4 deletions(-) diff --git a/packages/snaps-controllers/src/snaps/endowments/index.ts b/packages/snaps-controllers/src/snaps/endowments/index.ts index 4b991fa04d..5d7a37b4e1 100644 --- a/packages/snaps-controllers/src/snaps/endowments/index.ts +++ b/packages/snaps-controllers/src/snaps/endowments/index.ts @@ -7,7 +7,11 @@ import { } from './cronjob'; import { longRunningEndowmentBuilder } from './long-running'; import { networkAccessEndowmentBuilder } from './network-access'; -import { transactionInsightEndowmentBuilder } from './transaction-insight'; +import { + getTransactionInsightCaveatMapper, + transactionInsightCaveatSpecifications, + transactionInsightEndowmentBuilder, +} from './transaction-insight'; import { keyringEndowmentBuilder, keyringCaveatSpecifications, @@ -29,6 +33,7 @@ export const endowmentPermissionBuilders = { export const endowmentCaveatSpecifications = { ...keyringCaveatSpecifications, ...cronjobCaveatSpecifications, + ...transactionInsightCaveatSpecifications, }; export const endowmentCaveatMappers: Record< @@ -37,6 +42,8 @@ export const endowmentCaveatMappers: Record< > = { [keyringEndowmentBuilder.targetKey]: getKeyringCaveatMapper, [cronjobEndowmentBuilder.targetKey]: getCronjobCaveatMapper, + [transactionInsightEndowmentBuilder.targetKey]: + getTransactionInsightCaveatMapper, }; export * from './enum'; diff --git a/packages/snaps-controllers/src/snaps/endowments/transaction-insight.ts b/packages/snaps-controllers/src/snaps/endowments/transaction-insight.ts index e3d644000e..be9a6df275 100644 --- a/packages/snaps-controllers/src/snaps/endowments/transaction-insight.ts +++ b/packages/snaps-controllers/src/snaps/endowments/transaction-insight.ts @@ -3,7 +3,21 @@ import { PermissionType, EndowmentGetterParams, ValidPermissionSpecification, + PermissionValidatorConstraint, + PermissionConstraint, + CaveatSpecificationConstraint, + Caveat, } from '@metamask/controllers'; +import { + assert, + hasProperty, + isObject, + isPlainObject, + Json, + NonEmptyArray, +} from '@metamask/utils'; +import { SnapCaveatType } from '@metamask/snap-utils'; +import { ethErrors } from 'eth-rpc-errors'; import { SnapEndowments } from './enum'; const permissionName = SnapEndowments.TransactionInsight; @@ -12,7 +26,8 @@ type TransactionInsightEndowmentSpecification = ValidPermissionSpecification<{ permissionType: PermissionType.Endowment; targetKey: typeof permissionName; endowmentGetter: (_options?: EndowmentGetterParams) => undefined; - allowedCaveats: null; + allowedCaveats: Readonly> | null; + validator: PermissionValidatorConstraint; }>; /** @@ -30,8 +45,19 @@ const specificationBuilder: PermissionSpecificationBuilder< return { permissionType: PermissionType.Endowment, targetKey: permissionName, - allowedCaveats: null, + allowedCaveats: [SnapCaveatType.SnapTransactionInsight], endowmentGetter: (_getterOptions?: EndowmentGetterParams) => undefined, + validator: ({ caveats }) => { + if ( + caveats !== null && + (caveats?.length > 1 || + caveats[0].type !== SnapCaveatType.SnapTransactionInsight) + ) { + throw ethErrors.rpc.invalidParams({ + message: `Expected a single "${SnapCaveatType.SnapTransactionInsight}" caveat.`, + }); + } + }, }; }; @@ -39,3 +65,97 @@ export const transactionInsightEndowmentBuilder = Object.freeze({ targetKey: permissionName, specificationBuilder, } as const); + +/** + * Validates the type of the caveat value. + * + * @param caveat - The caveat to validate. + * @throws If the caveat value is invalid. + */ +function validateCaveat(caveat: Caveat): void { + if (!hasProperty(caveat, 'value') || !isPlainObject(caveat.value)) { + throw ethErrors.rpc.invalidParams({ + message: 'Expected a plain object.', + }); + } + + const { value } = caveat; + + if (!hasProperty(value, 'allowTransactionOrigin')) { + throw ethErrors.rpc.invalidParams({ + message: 'Expected a plain object.', + }); + } + + assert( + typeof value.allowTransactionOrigin === 'boolean', + 'Expected allowTransactionOrigin to have type "boolean"', + ); +} + +/** + * Map a raw value from the `initialPermissions` to a caveat specification. + * Note that this function does not do any validation, that's handled by the + * PermissionsController when the permission is requested. + * + * @param value - The raw value from the `initialPermissions`. + * @returns The caveat specification. + */ +export function getTransactionInsightCaveatMapper( + value: Json, +): Pick { + if (!value || (isObject(value) && Object.keys(value).length === 0)) { + return { caveats: null }; + } + return { + caveats: [ + { + type: SnapCaveatType.SnapTransactionInsight, + value, + }, + ], + }; +} + +export type TransactionInsightCaveat = { + allowTransactionOrigin?: boolean; +}; + +/** + * Getter function to get the keyring namespaces from a permission. + * + * This does basic validation of the caveat, but does not validate the type or + * value of the namespaces object itself, as this is handled by the + * `PermissionsController` when the permission is requested. + * + * @param permission - The permission to get the keyring namespaces from. + * @returns The keyring namespaces, or `null` if the permission does not have a + * keyring caveat. + */ +export function getTransactionInsightCaveat( + permission?: PermissionConstraint, +): TransactionInsightCaveat | null { + if (!permission?.caveats) { + return null; + } + + assert(permission.caveats.length === 1); + assert(permission.caveats[0].type === SnapCaveatType.SnapTransactionInsight); + + const caveat = permission.caveats[0] as Caveat< + string, + TransactionInsightCaveat + >; + + return caveat.value ?? null; +} + +export const transactionInsightCaveatSpecifications: Record< + SnapCaveatType.SnapTransactionInsight, + CaveatSpecificationConstraint +> = { + [SnapCaveatType.SnapTransactionInsight]: Object.freeze({ + type: SnapCaveatType.SnapTransactionInsight, + validator: (caveat: Caveat) => validateCaveat(caveat), + }), +}; diff --git a/packages/snaps-execution-environments/src/common/commands.ts b/packages/snaps-execution-environments/src/common/commands.ts index 3365067ebe..38cc4093bc 100644 --- a/packages/snaps-execution-environments/src/common/commands.ts +++ b/packages/snaps-execution-environments/src/common/commands.ts @@ -36,10 +36,11 @@ export function getHandlerArguments( case HandlerType.OnTransaction: { assertIsOnTransactionRequestArguments(request.params); - const { transaction, chainId } = request.params; + const { transaction, chainId, transactionOrigin } = request.params; return { transaction, chainId, + transactionOrigin, }; } diff --git a/packages/snaps-execution-environments/src/common/validation.ts b/packages/snaps-execution-environments/src/common/validation.ts index 4c7b6aad75..d45ef3e3a6 100644 --- a/packages/snaps-execution-environments/src/common/validation.ts +++ b/packages/snaps-execution-environments/src/common/validation.ts @@ -16,6 +16,7 @@ import { Infer, is, literal, + nullable, object, omit, optional, @@ -153,6 +154,7 @@ export const OnTransactionRequestArgumentsStruct = object({ // TODO: Improve `transaction` type. transaction: record(string(), JsonStruct), chainId: ChainIdStruct, + transactionOrigin: nullable(string()), }); export type OnTransactionRequestArguments = Infer< diff --git a/packages/snaps-types/src/types.d.ts b/packages/snaps-types/src/types.d.ts index 587d953c6f..5cffe9b9b5 100644 --- a/packages/snaps-types/src/types.d.ts +++ b/packages/snaps-types/src/types.d.ts @@ -16,6 +16,7 @@ export type OnTransactionResponse = { export type OnTransactionHandler = (args: { transaction: { [key: string]: unknown }; chainId: string; + transactionOrigin?: string; }) => Promise; export type OnCronjobHandler = (args: { diff --git a/packages/snaps-utils/src/caveats.ts b/packages/snaps-utils/src/caveats.ts index ec04ccf6af..2a1d282751 100644 --- a/packages/snaps-utils/src/caveats.ts +++ b/packages/snaps-utils/src/caveats.ts @@ -18,4 +18,9 @@ export enum SnapCaveatType { * Caveat specifying a snap cronjob. */ SnapCronjob = 'snapCronjob', + + /** + * Caveat specifying optional metadata for transaction insight. + */ + SnapTransactionInsight = 'snapTransactionInsight', } From 918e8f4767e0abfba1d93a905196d27b38e4e977 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Thu, 3 Nov 2022 13:29:34 +0100 Subject: [PATCH 2/6] Fix tests --- .../src/snaps/endowments/transaction-insight.test.ts | 4 +++- .../src/common/BaseSnapExecutor.test.ts | 8 ++++++-- .../src/common/validation.test.ts | 9 +++++++-- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/snaps-controllers/src/snaps/endowments/transaction-insight.test.ts b/packages/snaps-controllers/src/snaps/endowments/transaction-insight.test.ts index 63f6619f4b..8dbbdc0ebe 100644 --- a/packages/snaps-controllers/src/snaps/endowments/transaction-insight.test.ts +++ b/packages/snaps-controllers/src/snaps/endowments/transaction-insight.test.ts @@ -1,4 +1,5 @@ import { PermissionType } from '@metamask/controllers'; +import { SnapCaveatType } from '@metamask/snap-utils'; import { transactionInsightEndowmentBuilder } from './transaction-insight'; import { SnapEndowments } from '.'; @@ -9,8 +10,9 @@ describe('endowment:transaction-insight', () => { expect(specification).toStrictEqual({ permissionType: PermissionType.Endowment, targetKey: SnapEndowments.TransactionInsight, + allowedCaveats: [SnapCaveatType.SnapTransactionInsight], endowmentGetter: expect.any(Function), - allowedCaveats: null, + validator: expect.any(Function), }); expect(specification.endowmentGetter()).toBeUndefined(); diff --git a/packages/snaps-execution-environments/src/common/BaseSnapExecutor.test.ts b/packages/snaps-execution-environments/src/common/BaseSnapExecutor.test.ts index 030b902032..12fcb3004b 100644 --- a/packages/snaps-execution-environments/src/common/BaseSnapExecutor.test.ts +++ b/packages/snaps-execution-environments/src/common/BaseSnapExecutor.test.ts @@ -853,7 +853,7 @@ describe('BaseSnapExecutor', () => { it('supports onTransaction export', async () => { const CODE = ` - module.exports.onTransaction = ({ transaction, chainId }) => ({ transaction, chainId }); + module.exports.onTransaction = ({ transaction, chainId, transactionOrigin }) => ({ transaction, chainId, transactionOrigin }); `; const executor = new TestSnapExecutor(); @@ -869,7 +869,11 @@ describe('BaseSnapExecutor', () => { // We also have to decide on the shape of that object. const transaction = { maxFeePerGas: '0x' }; - const params = { transaction, chainId: 'eip155:1' }; + const params = { + transaction, + chainId: 'eip155:1', + transactionOrigin: null, + }; await executor.writeCommand({ jsonrpc: '2.0', diff --git a/packages/snaps-execution-environments/src/common/validation.test.ts b/packages/snaps-execution-environments/src/common/validation.test.ts index 244cc199d5..9c740867d1 100644 --- a/packages/snaps-execution-environments/src/common/validation.test.ts +++ b/packages/snaps-execution-environments/src/common/validation.test.ts @@ -41,12 +41,17 @@ describe('isEndowmentsArray', () => { describe('assertIsOnTransactionRequestArguments', () => { it.each([ - { transaction: {}, chainId: 'eip155:1' }, + { transaction: {}, chainId: 'eip155:1', transactionOrigin: null }, { transaction: { foo: 'bar' }, chainId: 'bip122:000000000019d6689c085ae165831e93', + transactionOrigin: null, + }, + { + transaction: { bar: 'baz' }, + chainId: 'eip155:2', + transactionOrigin: null, }, - { transaction: { bar: 'baz' }, chainId: 'eip155:2' }, ])('does not throw for a valid transaction params object', (args) => { expect(() => assertIsOnTransactionRequestArguments(args)).not.toThrow(); }); From 64185b9647ac62252adc803a15e876d938cc8d9e Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Tue, 15 Nov 2022 12:06:50 +0100 Subject: [PATCH 3/6] Simplify caveat --- .../endowments/transaction-insight.test.ts | 4 +- .../snaps/endowments/transaction-insight.ts | 59 ++++++++----------- packages/snaps-utils/src/caveats.ts | 4 +- .../snaps-utils/src/manifest/validation.ts | 6 +- 4 files changed, 35 insertions(+), 38 deletions(-) diff --git a/packages/snaps-controllers/src/snaps/endowments/transaction-insight.test.ts b/packages/snaps-controllers/src/snaps/endowments/transaction-insight.test.ts index 8dbbdc0ebe..176fee7a4b 100644 --- a/packages/snaps-controllers/src/snaps/endowments/transaction-insight.test.ts +++ b/packages/snaps-controllers/src/snaps/endowments/transaction-insight.test.ts @@ -1,5 +1,5 @@ import { PermissionType } from '@metamask/controllers'; -import { SnapCaveatType } from '@metamask/snap-utils'; +import { SnapCaveatType } from '@metamask/snaps-utils'; import { transactionInsightEndowmentBuilder } from './transaction-insight'; import { SnapEndowments } from '.'; @@ -10,7 +10,7 @@ describe('endowment:transaction-insight', () => { expect(specification).toStrictEqual({ permissionType: PermissionType.Endowment, targetKey: SnapEndowments.TransactionInsight, - allowedCaveats: [SnapCaveatType.SnapTransactionInsight], + allowedCaveats: [SnapCaveatType.TransactionOrigin], endowmentGetter: expect.any(Function), validator: expect.any(Function), }); diff --git a/packages/snaps-controllers/src/snaps/endowments/transaction-insight.ts b/packages/snaps-controllers/src/snaps/endowments/transaction-insight.ts index be9a6df275..633fdfb9f3 100644 --- a/packages/snaps-controllers/src/snaps/endowments/transaction-insight.ts +++ b/packages/snaps-controllers/src/snaps/endowments/transaction-insight.ts @@ -16,7 +16,7 @@ import { Json, NonEmptyArray, } from '@metamask/utils'; -import { SnapCaveatType } from '@metamask/snap-utils'; +import { SnapCaveatType } from '@metamask/snaps-utils'; import { ethErrors } from 'eth-rpc-errors'; import { SnapEndowments } from './enum'; @@ -45,16 +45,16 @@ const specificationBuilder: PermissionSpecificationBuilder< return { permissionType: PermissionType.Endowment, targetKey: permissionName, - allowedCaveats: [SnapCaveatType.SnapTransactionInsight], + allowedCaveats: [SnapCaveatType.TransactionOrigin], endowmentGetter: (_getterOptions?: EndowmentGetterParams) => undefined, validator: ({ caveats }) => { if ( caveats !== null && (caveats?.length > 1 || - caveats[0].type !== SnapCaveatType.SnapTransactionInsight) + caveats[0].type !== SnapCaveatType.TransactionOrigin) ) { throw ethErrors.rpc.invalidParams({ - message: `Expected a single "${SnapCaveatType.SnapTransactionInsight}" caveat.`, + message: `Expected a single "${SnapCaveatType.TransactionOrigin}" caveat.`, }); } }, @@ -81,15 +81,9 @@ function validateCaveat(caveat: Caveat): void { const { value } = caveat; - if (!hasProperty(value, 'allowTransactionOrigin')) { - throw ethErrors.rpc.invalidParams({ - message: 'Expected a plain object.', - }); - } - assert( - typeof value.allowTransactionOrigin === 'boolean', - 'Expected allowTransactionOrigin to have type "boolean"', + typeof value === 'boolean', + 'Expected caveat value to have type "boolean"', ); } @@ -104,58 +98,57 @@ function validateCaveat(caveat: Caveat): void { export function getTransactionInsightCaveatMapper( value: Json, ): Pick { - if (!value || (isObject(value) && Object.keys(value).length === 0)) { + if ( + !value || + !isObject(value) || + (isObject(value) && Object.keys(value).length === 0) + ) { return { caveats: null }; } return { caveats: [ { - type: SnapCaveatType.SnapTransactionInsight, - value, + type: SnapCaveatType.TransactionOrigin, + value: + hasProperty(value, 'allowTransactionOrigin') && + value.allowTransactionOrigin, }, ], }; } -export type TransactionInsightCaveat = { - allowTransactionOrigin?: boolean; -}; - /** - * Getter function to get the keyring namespaces from a permission. + * Getter function to get the transaction origin caveat from a permission. * * This does basic validation of the caveat, but does not validate the type or * value of the namespaces object itself, as this is handled by the * `PermissionsController` when the permission is requested. * - * @param permission - The permission to get the keyring namespaces from. - * @returns The keyring namespaces, or `null` if the permission does not have a - * keyring caveat. + * @param permission - The permission to get the transaction origin caveat from. + * @returns The transaction origin, or `null` if the permission does not have a + * transaction origin caveat. */ -export function getTransactionInsightCaveat( +export function getTransactionOriginCaveat( permission?: PermissionConstraint, -): TransactionInsightCaveat | null { +): boolean | null { if (!permission?.caveats) { return null; } assert(permission.caveats.length === 1); - assert(permission.caveats[0].type === SnapCaveatType.SnapTransactionInsight); + assert(permission.caveats[0].type === SnapCaveatType.TransactionOrigin); - const caveat = permission.caveats[0] as Caveat< - string, - TransactionInsightCaveat - >; + const caveat = permission.caveats[0] as Caveat; return caveat.value ?? null; } export const transactionInsightCaveatSpecifications: Record< - SnapCaveatType.SnapTransactionInsight, + SnapCaveatType.TransactionOrigin, CaveatSpecificationConstraint > = { - [SnapCaveatType.SnapTransactionInsight]: Object.freeze({ - type: SnapCaveatType.SnapTransactionInsight, + [SnapCaveatType.TransactionOrigin]: Object.freeze({ + type: SnapCaveatType.TransactionOrigin, validator: (caveat: Caveat) => validateCaveat(caveat), }), }; diff --git a/packages/snaps-utils/src/caveats.ts b/packages/snaps-utils/src/caveats.ts index 2a1d282751..5c2c10a400 100644 --- a/packages/snaps-utils/src/caveats.ts +++ b/packages/snaps-utils/src/caveats.ts @@ -20,7 +20,7 @@ export enum SnapCaveatType { SnapCronjob = 'snapCronjob', /** - * Caveat specifying optional metadata for transaction insight. + * Caveat specifying access to the transaction origin, used by `endowment:transaction-insight`. */ - SnapTransactionInsight = 'snapTransactionInsight', + TransactionOrigin = 'transactionOrigin', } diff --git a/packages/snaps-utils/src/manifest/validation.ts b/packages/snaps-utils/src/manifest/validation.ts index 740b12e1f8..eb7c2da964 100644 --- a/packages/snaps-utils/src/manifest/validation.ts +++ b/packages/snaps-utils/src/manifest/validation.ts @@ -149,7 +149,11 @@ export type Bip32PublicKey = Infer; const PermissionsStruct = type({ 'endowment:long-running': optional(object({})), 'endowment:network-access': optional(object({})), - 'endowment:transaction-insight': optional(object({})), + 'endowment:transaction-insight': optional( + object({ + allowTransactionOrigin: boolean(), + }), + ), 'endowment:cronjob': optional( object({ jobs: CronjobSpecificationArrayStruct }), ), From 1d3ff4925eca04e783e1825d863861b8ddf062f6 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Tue, 15 Nov 2022 12:12:40 +0100 Subject: [PATCH 4/6] Fix a validation issue --- packages/snaps-utils/src/manifest/validation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/snaps-utils/src/manifest/validation.ts b/packages/snaps-utils/src/manifest/validation.ts index eb7c2da964..a407c35f1b 100644 --- a/packages/snaps-utils/src/manifest/validation.ts +++ b/packages/snaps-utils/src/manifest/validation.ts @@ -151,7 +151,7 @@ const PermissionsStruct = type({ 'endowment:network-access': optional(object({})), 'endowment:transaction-insight': optional( object({ - allowTransactionOrigin: boolean(), + allowTransactionOrigin: optional(boolean()), }), ), 'endowment:cronjob': optional( From 75c80c00233cb9bbd1dbf876f3b5924e33467976 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Tue, 15 Nov 2022 13:30:14 +0100 Subject: [PATCH 5/6] Fix tests --- .../endowments/transaction-insight.test.ts | 168 +++++++++++++++++- .../snaps/endowments/transaction-insight.ts | 6 +- .../jest.config.js | 4 +- 3 files changed, 169 insertions(+), 9 deletions(-) diff --git a/packages/snaps-controllers/src/snaps/endowments/transaction-insight.test.ts b/packages/snaps-controllers/src/snaps/endowments/transaction-insight.test.ts index 176fee7a4b..ae116e9250 100644 --- a/packages/snaps-controllers/src/snaps/endowments/transaction-insight.test.ts +++ b/packages/snaps-controllers/src/snaps/endowments/transaction-insight.test.ts @@ -1,12 +1,18 @@ -import { PermissionType } from '@metamask/controllers'; +import { PermissionConstraint, PermissionType } from '@metamask/controllers'; import { SnapCaveatType } from '@metamask/snaps-utils'; -import { transactionInsightEndowmentBuilder } from './transaction-insight'; +import { + getTransactionInsightCaveatMapper, + getTransactionOriginCaveat, + transactionInsightCaveatSpecifications, + transactionInsightEndowmentBuilder, +} from './transaction-insight'; import { SnapEndowments } from '.'; describe('endowment:transaction-insight', () => { + const specification = transactionInsightEndowmentBuilder.specificationBuilder( + {}, + ); it('builds the expected permission specification', () => { - const specification = - transactionInsightEndowmentBuilder.specificationBuilder({}); expect(specification).toStrictEqual({ permissionType: PermissionType.Endowment, targetKey: SnapEndowments.TransactionInsight, @@ -17,4 +23,158 @@ describe('endowment:transaction-insight', () => { expect(specification.endowmentGetter()).toBeUndefined(); }); + + describe('validator', () => { + it('allows no caveats', () => { + expect(() => + // @ts-expect-error Missing required permission types. + specification.validator({}), + ).not.toThrow('Expected a single "transactionOrigin" caveat.'); + }); + + it('throws if the caveat is not a single "transactionOrigin"', () => { + expect(() => + // @ts-expect-error Missing other required permission types. + specification.validator({ + caveats: [{ type: 'foo', value: 'bar' }], + }), + ).toThrow('Expected a single "transactionOrigin" caveat.'); + + expect(() => + // @ts-expect-error Missing other required permission types. + specification.validator({ + caveats: [ + { type: 'transactionOrigin', value: [] }, + { type: 'transactionOrigin', value: [] }, + ], + }), + ).toThrow('Expected a single "transactionOrigin" caveat.'); + }); + }); +}); + +describe('getTransactionOriginCaveat', () => { + it('returns the value from a transaction insight permission', () => { + const permission: PermissionConstraint = { + date: 0, + parentCapability: 'foo', + invoker: 'bar', + id: 'baz', + caveats: [ + { + type: SnapCaveatType.TransactionOrigin, + value: true, + }, + ], + }; + + expect(getTransactionOriginCaveat(permission)).toStrictEqual(true); + }); + + it('returns null if the input is undefined', () => { + expect(getTransactionOriginCaveat(undefined)).toBeNull(); + }); + + it('returns null if the permission does not have caveats', () => { + const permission: PermissionConstraint = { + date: 0, + parentCapability: 'foo', + invoker: 'bar', + id: 'baz', + caveats: null, + }; + + expect(getTransactionOriginCaveat(permission)).toBeNull(); + }); + + it('throws if the permission does not have exactly one caveat', () => { + const permission: PermissionConstraint = { + date: 0, + parentCapability: 'foo', + invoker: 'bar', + id: 'baz', + caveats: [ + { + type: SnapCaveatType.TransactionOrigin, + value: true, + }, + { + type: SnapCaveatType.TransactionOrigin, + value: true, + }, + ], + }; + + expect(() => getTransactionOriginCaveat(permission)).toThrow( + 'Assertion failed', + ); + }); + + it('throws if the first caveat is not a "snapKeyring" caveat', () => { + const permission: PermissionConstraint = { + date: 0, + parentCapability: 'foo', + invoker: 'bar', + id: 'baz', + caveats: [ + { + type: SnapCaveatType.PermittedCoinTypes, + value: 'foo', + }, + ], + }; + + expect(() => getTransactionOriginCaveat(permission)).toThrow( + 'Assertion failed', + ); + }); +}); + +describe('getTransactionInsightCaveatMapper', () => { + it('maps input to a caveat', () => { + expect( + getTransactionInsightCaveatMapper({ + allowTransactionOrigin: true, + }), + ).toStrictEqual({ + caveats: [ + { + type: 'transactionOrigin', + value: true, + }, + ], + }); + }); + + it('does not include caveat if input is empty object', () => { + expect(getTransactionInsightCaveatMapper({})).toStrictEqual({ + caveats: null, + }); + }); +}); + +describe('transactionInsightCaveatSpecifications', () => { + describe('validator', () => { + it('throws if the caveat values are invalid', () => { + expect(() => + transactionInsightCaveatSpecifications[ + SnapCaveatType.TransactionOrigin + ].validator?.( + // @ts-expect-error Missing value type. + { + type: SnapCaveatType.TransactionOrigin, + }, + ), + ).toThrow('Expected a plain object.'); + + expect(() => + transactionInsightCaveatSpecifications[ + SnapCaveatType.TransactionOrigin + ].validator?.({ + type: SnapCaveatType.TransactionOrigin, + value: undefined, + }), + ).toThrow('Expected caveat value to have type "boolean"'); + }); + }); }); diff --git a/packages/snaps-controllers/src/snaps/endowments/transaction-insight.ts b/packages/snaps-controllers/src/snaps/endowments/transaction-insight.ts index 633fdfb9f3..7d49feebfe 100644 --- a/packages/snaps-controllers/src/snaps/endowments/transaction-insight.ts +++ b/packages/snaps-controllers/src/snaps/endowments/transaction-insight.ts @@ -49,8 +49,8 @@ const specificationBuilder: PermissionSpecificationBuilder< endowmentGetter: (_getterOptions?: EndowmentGetterParams) => undefined, validator: ({ caveats }) => { if ( - caveats !== null && - (caveats?.length > 1 || + (caveats !== null && caveats?.length > 1) || + (caveats?.length === 1 && caveats[0].type !== SnapCaveatType.TransactionOrigin) ) { throw ethErrors.rpc.invalidParams({ @@ -73,7 +73,7 @@ export const transactionInsightEndowmentBuilder = Object.freeze({ * @throws If the caveat value is invalid. */ function validateCaveat(caveat: Caveat): void { - if (!hasProperty(caveat, 'value') || !isPlainObject(caveat.value)) { + if (!hasProperty(caveat, 'value') || !isPlainObject(caveat)) { throw ethErrors.rpc.invalidParams({ message: 'Expected a plain object.', }); diff --git a/packages/snaps-execution-environments/jest.config.js b/packages/snaps-execution-environments/jest.config.js index e9c3866721..8dd2980d36 100644 --- a/packages/snaps-execution-environments/jest.config.js +++ b/packages/snaps-execution-environments/jest.config.js @@ -7,8 +7,8 @@ module.exports = deepmerge(baseConfig, { global: { branches: 89.7, functions: 90.43, - lines: 88.45, - statements: 88.45, + lines: 88.46, + statements: 88.46, }, }, testEnvironment: '/jest.environment.js', From 2eb0db216e98b89498fe8898088899758dfc406e Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Tue, 15 Nov 2022 16:19:12 +0100 Subject: [PATCH 6/6] Update packages/snaps-controllers/src/snaps/endowments/transaction-insight.test.ts Co-authored-by: Maarten Zuidhoorn --- .../src/snaps/endowments/transaction-insight.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/snaps-controllers/src/snaps/endowments/transaction-insight.test.ts b/packages/snaps-controllers/src/snaps/endowments/transaction-insight.test.ts index ae116e9250..9d31beca3d 100644 --- a/packages/snaps-controllers/src/snaps/endowments/transaction-insight.test.ts +++ b/packages/snaps-controllers/src/snaps/endowments/transaction-insight.test.ts @@ -29,7 +29,7 @@ describe('endowment:transaction-insight', () => { expect(() => // @ts-expect-error Missing required permission types. specification.validator({}), - ).not.toThrow('Expected a single "transactionOrigin" caveat.'); + ).not.toThrow(); }); it('throws if the caveat is not a single "transactionOrigin"', () => {