Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/snaps-controllers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
"@metamask/phishing-controller": "^15.0.0",
"@metamask/post-message-stream": "^10.0.0",
"@metamask/rpc-errors": "^7.0.3",
"@metamask/snaps-registry": "^3.2.3",
"@metamask/snaps-registry": "^3.3.0",
"@metamask/snaps-rpc-methods": "workspace:^",
"@metamask/snaps-sdk": "workspace:^",
"@metamask/snaps-utils": "workspace:^",
Expand Down
96 changes: 96 additions & 0 deletions packages/snaps-controllers/src/snaps/registry/json.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ const getRegistry = (args?: Partial<JsonSnapsRegistryArgs>) => {
registry: new JsonSnapsRegistry({
messenger,
publicKey: MOCK_PUBLIC_KEY,
clientConfig: {
type: 'extension',
version: '13.9.0' as SemVerVersion,
},
...args,
}),
messenger,
Expand Down Expand Up @@ -84,6 +88,43 @@ const MOCK_EMPTY_SIGNATURE_FILE = {
format: 'DER',
};

const MOCK_DATABASE_COMPATIBILITY = {
verifiedSnaps: {
[MOCK_SNAP_ID]: {
id: MOCK_SNAP_ID,
metadata: {
name: 'Mock Snap',
},
versions: {
['1.0.0' as SemVerVersion]: {
checksum: DEFAULT_SNAP_SHASUM,
clientVersions: {
extension: '>=13.9.0',
},
},
['1.1.0' as SemVerVersion]: {
checksum: DEFAULT_SNAP_SHASUM,
clientVersions: {
extension: '>=15.0.0',
},
},
},
},
},
blockedSnaps: [],
};

/**
* To regenerate the signature, repeat the instructions above but with MOCK_DATABASE_COMPATIBILITY
*/
const MOCK_COMPATIBILITY_SIGNATURE =
'0x3045022100c0dd17483ac052b25a24c43a84de7b7b38194ac770cadb53a83ca950150631bd02204ed1f6b3359901199e2752d148079084cda13439150136055be5d4a3df205115';
const MOCK_COMPATIBILITY_SIGNATURE_FILE = {
signature: MOCK_COMPATIBILITY_SIGNATURE,
curve: 'secp256k1',
format: 'DER',
};

describe('JsonSnapsRegistry', () => {
fetchMock.enableMocks();

Expand Down Expand Up @@ -240,6 +281,46 @@ describe('JsonSnapsRegistry', () => {
});
});

it('returns verified for compatible Snaps', async () => {
fetchMock
.mockResponseOnce(JSON.stringify(MOCK_DATABASE_COMPATIBILITY))
.mockResponseOnce(JSON.stringify(MOCK_COMPATIBILITY_SIGNATURE_FILE));

const { messenger } = getRegistry();
const result = await messenger.call('SnapsRegistry:get', {
[MOCK_SNAP_ID]: {
version: '1.0.0' as SemVerVersion,
checksum: DEFAULT_SNAP_SHASUM,
},
});

expect(result).toStrictEqual({
[MOCK_SNAP_ID]: {
status: SnapsRegistryStatus.Verified,
},
});
});

it('returns unverified for non compatible Snaps', async () => {
fetchMock
.mockResponseOnce(JSON.stringify(MOCK_DATABASE_COMPATIBILITY))
.mockResponseOnce(JSON.stringify(MOCK_COMPATIBILITY_SIGNATURE_FILE));

const { messenger } = getRegistry();
const result = await messenger.call('SnapsRegistry:get', {
[MOCK_SNAP_ID]: {
version: '1.1.0' as SemVerVersion,
checksum: DEFAULT_SNAP_SHASUM,
},
});

expect(result).toStrictEqual({
[MOCK_SNAP_ID]: {
status: SnapsRegistryStatus.Unverified,
},
});
});

it('uses existing state if registry is unavailable', async () => {
fetchMock.mockResponse('', { status: 404 });

Expand Down Expand Up @@ -372,6 +453,21 @@ describe('JsonSnapsRegistry', () => {
expect(result).toBe('1.0.0');
});

it('resolves to a compatible allowlisted version', async () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a test to make sure we resolve to 1.1.0 if the client version allows for it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fetchMock
.mockResponseOnce(JSON.stringify(MOCK_DATABASE_COMPATIBILITY))
.mockResponseOnce(JSON.stringify(MOCK_COMPATIBILITY_SIGNATURE_FILE));

const { messenger } = getRegistry();
const result = await messenger.call(
'SnapsRegistry:resolveVersion',
MOCK_SNAP_ID,
'^1.0.0' as SemVerRange,
);

expect(result).toBe('1.0.0');
});

it('returns version range if snap is not on the allowlist', async () => {
fetchMock
.mockResponseOnce(
Expand Down
34 changes: 30 additions & 4 deletions packages/snaps-controllers/src/snaps/registry/json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ type JsonSnapsRegistryUrl = {
signature: string;
};

export type ClientConfig = {
type: 'extension' | 'mobile';
version: SemVerVersion;
};

export type JsonSnapsRegistryArgs = {
messenger: SnapsRegistryMessenger;
state?: SnapsRegistryState;
Expand All @@ -47,6 +52,7 @@ export type JsonSnapsRegistryArgs = {
recentFetchThreshold?: number;
refetchOnAllowlistMiss?: boolean;
publicKey?: Hex;
clientConfig: ClientConfig;
};

export type GetResult = {
Expand Down Expand Up @@ -117,6 +123,8 @@ export class JsonSnapsRegistry extends BaseController<

readonly #publicKey: Hex;

readonly #clientConfig: ClientConfig;

readonly #fetchFunction: typeof fetch;

readonly #recentFetchThreshold: number;
Expand All @@ -133,6 +141,7 @@ export class JsonSnapsRegistry extends BaseController<
signature: SNAP_REGISTRY_SIGNATURE_URL,
},
publicKey = DEFAULT_PUBLIC_KEY,
clientConfig,
fetchFunction = globalThis.fetch.bind(undefined),
recentFetchThreshold = inMilliseconds(5, Duration.Minute),
refetchOnAllowlistMiss = true,
Expand Down Expand Up @@ -167,6 +176,7 @@ export class JsonSnapsRegistry extends BaseController<
});
this.#url = url;
this.#publicKey = publicKey;
this.#clientConfig = clientConfig;
this.#fetchFunction = fetchFunction;
this.#recentFetchThreshold = recentFetchThreshold;
this.#refetchOnAllowlistMiss = refetchOnAllowlistMiss;
Expand Down Expand Up @@ -284,7 +294,11 @@ export class JsonSnapsRegistry extends BaseController<

const verified = database?.verifiedSnaps[snapId];
const version = verified?.versions?.[snapInfo.version];
if (version && version.checksum === snapInfo.checksum) {
const clientRange = version?.clientVersions?.[this.#clientConfig.type];
const isCompatible =
!clientRange ||
satisfiesVersionRange(this.#clientConfig.version, clientRange);
if (version && version.checksum === snapInfo.checksum && isCompatible) {
return { status: SnapsRegistryStatus.Verified };
}
// For now, if we have an allowlist miss, we can refetch once and try again.
Expand Down Expand Up @@ -338,11 +352,23 @@ export class JsonSnapsRegistry extends BaseController<
return versionRange;
}

const targetVersion = getTargetVersion(
Object.keys(versions) as SemVerVersion[],
versionRange,
const compatibleVersions = Object.entries(versions).reduce<SemVerVersion[]>(
(accumulator, [version, metadata]) => {
const clientRange = metadata.clientVersions?.[this.#clientConfig.type];
if (
!clientRange ||
satisfiesVersionRange(this.#clientConfig.version, clientRange)
) {
accumulator.push(version as SemVerVersion);
}

return accumulator;
},
[],
);

const targetVersion = getTargetVersion(compatibleVersions, versionRange);

if (!targetVersion && this.#refetchOnAllowlistMiss && !refetch) {
await this.#triggerUpdate();
return this.#resolveVersion(snapId, versionRange, true);
Expand Down
2 changes: 1 addition & 1 deletion packages/snaps-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
"@metamask/permission-controller": "^12.1.0",
"@metamask/rpc-errors": "^7.0.3",
"@metamask/slip44": "^4.3.0",
"@metamask/snaps-registry": "^3.2.3",
"@metamask/snaps-registry": "^3.3.0",
"@metamask/snaps-sdk": "workspace:^",
"@metamask/superstruct": "^3.2.1",
"@metamask/utils": "^11.8.1",
Expand Down
16 changes: 8 additions & 8 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4262,7 +4262,7 @@ __metadata:
"@metamask/phishing-controller": "npm:^15.0.0"
"@metamask/post-message-stream": "npm:^10.0.0"
"@metamask/rpc-errors": "npm:^7.0.3"
"@metamask/snaps-registry": "npm:^3.2.3"
"@metamask/snaps-registry": "npm:^3.3.0"
"@metamask/snaps-rpc-methods": "workspace:^"
"@metamask/snaps-sdk": "workspace:^"
"@metamask/snaps-utils": "workspace:^"
Expand Down Expand Up @@ -4413,15 +4413,15 @@ __metadata:
languageName: unknown
linkType: soft

"@metamask/snaps-registry@npm:^3.2.3":
version: 3.2.3
resolution: "@metamask/snaps-registry@npm:3.2.3"
"@metamask/snaps-registry@npm:^3.3.0":
version: 3.3.0
resolution: "@metamask/snaps-registry@npm:3.3.0"
dependencies:
"@metamask/superstruct": "npm:^3.1.0"
"@metamask/utils": "npm:^11.0.1"
"@metamask/superstruct": "npm:^3.2.1"
"@metamask/utils": "npm:^11.4.0"
"@noble/curves": "npm:^1.2.0"
"@noble/hashes": "npm:^1.3.2"
checksum: 10/37760f29b7aaa337d815cf0c11fa34af5093d87fdc60a3750c494cf8bae6293cd52da03e7694b467b79733052d75ec6e3781ab3590d7259a050784e5be347d12
checksum: 10/1a53ad150318cbaf703b639a3a831a6ac57f84b2266ac176e6b0d470df31ecf66f0f885256f17a7acae265ada085c904ba97f1e2cb5371e136bf90778ffaed0a
languageName: node
linkType: hard

Expand Down Expand Up @@ -4599,7 +4599,7 @@ __metadata:
"@metamask/post-message-stream": "npm:^10.0.0"
"@metamask/rpc-errors": "npm:^7.0.3"
"@metamask/slip44": "npm:^4.3.0"
"@metamask/snaps-registry": "npm:^3.2.3"
"@metamask/snaps-registry": "npm:^3.3.0"
"@metamask/snaps-sdk": "workspace:^"
"@metamask/superstruct": "npm:^3.2.1"
"@metamask/utils": "npm:^11.8.1"
Expand Down
Loading