Skip to content

Commit

Permalink
feat: state migration (#585)
Browse files Browse the repository at this point in the history
Co-authored-by: martines3000 <domajnko.martin@gmail.com>
  • Loading branch information
andyv09 and martines3000 committed Mar 12, 2024
1 parent 1149402 commit 526a627
Show file tree
Hide file tree
Showing 11 changed files with 189 additions and 16 deletions.
6 changes: 6 additions & 0 deletions .changeset/gorgeous-cougars-marry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@blockchain-lab-um/masca-types": patch
"@blockchain-lab-um/masca": patch
---

Add state migration & update tests
16 changes: 15 additions & 1 deletion packages/snap/src/storage/Storage.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,21 @@ import {

import { getInitialSnapState } from '../utils/config';
import SnapStorage from './Snap.storage';
import { migrateToV2 } from 'src/utils/stateMigration';

class StorageService {
static instance: MascaState;

static async init(): Promise<void> {
const state = await SnapStorage.load();
let state = await SnapStorage.load();

if (!state) {
StorageService.instance = getInitialSnapState();
return;
}

state = StorageService.migrateState(state);

StorageService.instance = state as MascaState;
}

Expand All @@ -38,6 +41,17 @@ class StorageService {
StorageService.instance[CURRENT_STATE_VERSION].currentAccount
];
}

static migrateState = (state: any): MascaState => {
if (state[CURRENT_STATE_VERSION]) return state;

let newState = state;
if (state.v1) {
newState = migrateToV2(state);
}

return newState;
};
}

export default StorageService;
2 changes: 1 addition & 1 deletion packages/snap/src/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ const initialPermissions: DappPermissions = {
export const getInitialPermissions = () => cloneDeep(initialPermissions);

const initialSnapState: MascaState = {
v1: {
v2: {
accountState: {},
currentAccount: '',
config: {
Expand Down
14 changes: 14 additions & 0 deletions packages/snap/src/utils/stateMigration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { MascaLegacyStateV1, MascaState } from '@blockchain-lab-um/masca-types';
import { getInitialPermissions } from './config';

export const migrateToV2 = (state: MascaLegacyStateV1): MascaState => {
const newState: any = { v2: state.v1 };

// Remove friendly dapps
newState.v2.config.dApp.friendlyDapps = undefined;

// Initialize permissions
newState.v2.config.dApp.permissions = { 'masca.io': getInitialPermissions() };

return newState as MascaState;
};
6 changes: 3 additions & 3 deletions packages/snap/tests/data/defaultSnapState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ import { getEmptyAccountState } from '../../src/utils/config';
const defaultSnapState = (address: string): MascaState => {
const accountState: Record<string, MascaAccountState> = {};
accountState[address] = getEmptyAccountState();

return {
v1: {
const state = {
[CURRENT_STATE_VERSION]: {
accountState,
currentAccount: address,
config: {
Expand All @@ -25,6 +24,7 @@ const defaultSnapState = (address: string): MascaState => {
},
},
};
return state as MascaState;
};

export const getDefaultSnapState = (address: string): MascaState => {
Expand Down
65 changes: 63 additions & 2 deletions packages/snap/tests/unit/Storage.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { CURRENT_STATE_VERSION } from '@blockchain-lab-um/masca-types';
import {
CURRENT_STATE_VERSION,
MascaLegacyStateV1,
} from '@blockchain-lab-um/masca-types';
import { MetaMaskInpageProvider } from '@metamask/providers';
import { SnapsProvider } from '@metamask/snaps-sdk';
import { beforeEach, describe, expect, it } from 'vitest';
import { beforeEach, describe, expect, it, vi } from 'vitest';

import StorageService from '../../src/storage/Storage.service';
import { getInitialSnapState } from '../../src/utils/config';
import { SnapMock, createMockSnap } from '../helpers/snapMock';
import * as MigrateState from '../../src/utils/stateMigration';

describe('Storage Service', () => {
let snapMock: SnapsProvider & SnapMock;
Expand Down Expand Up @@ -59,3 +63,60 @@ describe('Storage Service', () => {
expect.assertions(2);
});
});
describe('State Migration', () => {
let snapMock: SnapsProvider & SnapMock;
beforeEach(async () => {
snapMock = createMockSnap();
snapMock.rpcMocks.snap_manageState({
operation: 'clear',
});
global.snap = snapMock;
global.ethereum = snapMock as unknown as MetaMaskInpageProvider;
});

it('should not migrate state from latest version', async () => {
const spy = vi.spyOn(MigrateState, 'migrateToV2');
const state = getInitialSnapState();
StorageService.set(state);

await StorageService.save();

const newState = StorageService.get();

expect(newState).toHaveProperty('v2');

StorageService.init();
await StorageService.save();

const newState2 = StorageService.get();
expect(newState2).toHaveProperty('v2');
expect(spy).toHaveBeenCalledTimes(0);

expect.assertions(3);
});

it('should succeed migrating state from v1 to v2', async () => {
const spy = vi.spyOn(MigrateState, 'migrateToV2');
let state: any = getInitialSnapState();
state = { v1: state.v2 };
state.v1.config.dApp.friendlyDapps = ['masca.io'];
state.v1.config.dApp.permissions = undefined;
const legacyStateV1: MascaLegacyStateV1 = state;
StorageService.set(legacyStateV1 as any);

await StorageService.save();

const newState = StorageService.get();

expect(newState).toHaveProperty('v1');

StorageService.init();
await StorageService.save();

const newState2 = StorageService.get();
expect(newState2).toHaveProperty('v2');
expect(spy).toHaveBeenCalled();

expect.assertions(3);
});
});
14 changes: 7 additions & 7 deletions packages/snap/tests/unit/requestParams.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -701,12 +701,13 @@ describe('Utils [requestParams]', () => {
describe('failure', () => {
it('empty object', () => {
expect(() => isValidMascaState({})).toThrowError(
'invalid_argument: $input.v1'
`invalid_argument: $input.${CURRENT_STATE_VERSION}`
);
});
it('empty state with version', () => {
expect(() => isValidMascaState({ v1: {} })).toThrowError(
'invalid_argument: $input.v1.accountState, $input.v1.currentAccount, $input.v1.config'
const state = { [CURRENT_STATE_VERSION]: {} };
expect(() => isValidMascaState(state)).toThrowError(
`invalid_argument: $input.${CURRENT_STATE_VERSION}.accountState, $input.${CURRENT_STATE_VERSION}.currentAccount, $input.${CURRENT_STATE_VERSION}.config`
);
});
it('null', () => {
Expand All @@ -715,10 +716,9 @@ describe('Utils [requestParams]', () => {
);
});
it('missing fields', () => {
expect(() =>
isValidMascaState({ v1: { accountState: {} } })
).toThrowError(
'invalid_argument: $input.v1.currentAccount, $input.v1.config'
const state = { [CURRENT_STATE_VERSION]: { accountState: {} } };
expect(() => isValidMascaState(state)).toThrowError(
`invalid_argument: $input.${CURRENT_STATE_VERSION}.currentAccount, $input.${CURRENT_STATE_VERSION}.config`
);
});
});
Expand Down
2 changes: 1 addition & 1 deletion packages/types/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export type AvailableCredentialStores =
export const isavailableCredentialStores = (x: string) =>
isIn<AvailableCredentialStores>(availableCredentialStores, x);

export const CURRENT_STATE_VERSION = 'v1';
export const CURRENT_STATE_VERSION = 'v2';

/**
* @description
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export * from './state.js';
export * from './networks.js';
export * from './typia-generated/index.js';
export * from './signData.js';
export * from './legacyState.js';
77 changes: 77 additions & 0 deletions packages/types/src/legacyState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { IdentityMerkleTreeMetaInformation } from '@0xpolygonid/js-sdk';
import { Blockchain, DidMethod, NetworkId } from '@iden3/js-iden3-core';
import type { W3CVerifiableCredential } from '@veramo/core';

import type {
AvailableCredentialStores,
AvailableMethods,
} from './constants.js';

interface MascaLegacyConfigV1 {
snap: {
acceptedTerms: boolean;
};
dApp: {
disablePopups: boolean;
friendlyDapps: string[];
};
}

interface MascaLegacyAccountConfigV1 {
ssi: {
selectedMethod: AvailableMethods;
storesEnabled: Record<AvailableCredentialStores, boolean>;
};
}

export interface MascaLegacyStateV1 {
/**
* Version 1 of Masca state
*/
v1: {
/**
* Account specific storage
*/
accountState: Record<string, MascaLegacyAccountStateV1>;
/**
* Current account
*/
currentAccount: string;
/**
* Configuration for Masca
*/
config: MascaLegacyConfigV1;
};
}

/**
* Masca State for a MetaMask address
*/
interface MascaLegacyAccountStateV1 {
polygon: {
state: PolygonLegacyStateV1;
};
veramo: {
credentials: Record<string, W3CVerifiableCredential>;
};
general: {
account: MascaLegacyAccountConfigV1;
ceramicSession?: string;
};
}

interface PolygonLegacyBaseStateV1 {
credentials: Record<string, string>;
identities: Record<string, string>;
profiles: Record<string, string>;
merkleTreeMeta: IdentityMerkleTreeMetaInformation[];
merkleTree: Record<string, string>;
}

type PolygonLegacyStateV1 = Record<
DidMethod.Iden3 | DidMethod.PolygonId,
Record<
Blockchain.Ethereum | Blockchain.Polygon,
Record<NetworkId.Main | NetworkId.Mumbai, PolygonLegacyBaseStateV1>
>
>;
2 changes: 1 addition & 1 deletion packages/types/src/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export interface MascaState {
/**
* Version 1 of Masca state
*/
v1: {
v2: {
/**
* Account specific storage
*/
Expand Down

0 comments on commit 526a627

Please sign in to comment.