Skip to content

Commit

Permalink
feat: [ADN-362] Update Account migration model (#325)
Browse files Browse the repository at this point in the history
  • Loading branch information
jinoosss authored Jan 16, 2024
2 parents 39be474 + 88874c4 commit 291fe16
Show file tree
Hide file tree
Showing 8 changed files with 669 additions and 13 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { decryptAES } from 'adena-module';
import { StorageMigration005 } from './storage-migration-v005';

const mockStorageData = {
NETWORKS: [],
CURRENT_CHAIN_ID: '',
CURRENT_NETWORK_ID: '',
SERIALIZED: 'U2FsdGVkX19eI8kOCI/T9o1Ru0b2wdj5rHxmG4QbLQ0yZH4kDa8/gg6Ac2JslvEm',
ENCRYPTED_STORED_PASSWORD: '',
CURRENT_ACCOUNT_ID: '',
ACCOUNT_NAMES: {},
ESTABLISH_SITES: {},
ADDRESS_BOOK: [],
ACCOUNT_TOKEN_METAINFOS: {},
};

describe('serialized wallet migration V004', () => {
it('version', () => {
const migration = new StorageMigration005();
expect(migration.version).toBe(5);
});

it('up success', async () => {
const mockData = {
version: 2,
data: mockStorageData,
};
const migration = new StorageMigration005();
const result = await migration.up(mockData);

expect(result.version).toBe(5);
expect(result.data).not.toBeNull();
expect(result.data.NETWORKS).toEqual([]);
expect(result.data.CURRENT_CHAIN_ID).toBe('');
expect(result.data.CURRENT_NETWORK_ID).toBe('');
expect(result.data.SERIALIZED).toBe(
'U2FsdGVkX19eI8kOCI/T9o1Ru0b2wdj5rHxmG4QbLQ0yZH4kDa8/gg6Ac2JslvEm',
);
expect(result.data.ENCRYPTED_STORED_PASSWORD).toBe('');
expect(result.data.CURRENT_ACCOUNT_ID).toBe('');
expect(result.data.ACCOUNT_NAMES).toEqual({});
expect(result.data.ESTABLISH_SITES).toEqual({});
expect(result.data.ADDRESS_BOOK).toEqual([]);
});

it('up password success', async () => {
const mockData = {
version: 1,
data: mockStorageData,
};
const password = '123';
const migration = new StorageMigration005();
const result = await migration.up(mockData);

expect(result.version).toBe(5);
expect(result.data).not.toBeNull();
expect(result.data.NETWORKS).toEqual([]);
expect(result.data.CURRENT_CHAIN_ID).toBe('');
expect(result.data.CURRENT_NETWORK_ID).toBe('');
expect(result.data.SERIALIZED).not.toBe('');
expect(result.data.ENCRYPTED_STORED_PASSWORD).toBe('');
expect(result.data.CURRENT_ACCOUNT_ID).toBe('');
expect(result.data.ACCOUNT_NAMES).toEqual({});
expect(result.data.ESTABLISH_SITES).toEqual({});
expect(result.data.ADDRESS_BOOK).toEqual([]);

const serialized = result.data.SERIALIZED;
const decrypted = await decryptAES(serialized, password);
const wallet = JSON.parse(decrypted);

expect(wallet.accounts).toHaveLength(0);
expect(wallet.keyrings).toHaveLength(0);
});

it('up failed throw error', async () => {
const mockData: any = {
version: 1,
data: { ...mockStorageData, SERIALIZED: null },
};
const migration = new StorageMigration005();

await expect(migration.up(mockData)).rejects.toThrow(
'Storage Data does not match version V004',
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { Migration } from '@migrates/migrator';
import { StorageModel } from '@common/storage';
import {
AccountTokenMetainfoModelV004,
EstablishSitesModelV004,
NetworksModelV004,
StorageModelDataV004,
} from '../v004/storage-model-v004';
import {
AccountTokenMetainfoModelV005,
EstablishSitesModelV005,
NetworksModelV005,
StorageModelDataV005,
} from './storage-model-v005';

export class StorageMigration005 implements Migration<StorageModelDataV005> {
public readonly version = 5;

async up(
current: StorageModel<StorageModelDataV004>,
): Promise<StorageModel<StorageModelDataV005>> {
if (!this.validateModelV004(current.data)) {
throw new Error('Storage Data does not match version V004');
}
const previous: StorageModelDataV004 = current.data;
return {
version: this.version,
data: {
...previous,
NETWORKS: this.migrateNetworks(previous.NETWORKS),
ACCOUNT_TOKEN_METAINFOS: this.migrateAccountTokenMetainfo(previous.ACCOUNT_TOKEN_METAINFOS),
ESTABLISH_SITES: this.migrateEstablishSites(previous.ESTABLISH_SITES),
},
};
}

private validateModelV004(currentData: StorageModelDataV004): boolean {
const storageDataKeys = [
'NETWORKS',
'CURRENT_CHAIN_ID',
'CURRENT_NETWORK_ID',
'SERIALIZED',
'ENCRYPTED_STORED_PASSWORD',
'CURRENT_ACCOUNT_ID',
'ACCOUNT_NAMES',
'ESTABLISH_SITES',
'ADDRESS_BOOK',
'ACCOUNT_TOKEN_METAINFOS',
];
const hasKeys = Object.keys(currentData).every((dataKey) => storageDataKeys.includes(dataKey));
if (!hasKeys) {
return false;
}
if (!Array.isArray(currentData.NETWORKS)) {
return false;
}
if (typeof currentData.CURRENT_CHAIN_ID !== 'string') {
return false;
}
if (typeof currentData.CURRENT_NETWORK_ID !== 'string') {
return false;
}
if (typeof currentData.SERIALIZED !== 'string') {
return false;
}
if (typeof currentData.ENCRYPTED_STORED_PASSWORD !== 'string') {
return false;
}
if (typeof currentData.CURRENT_ACCOUNT_ID !== 'string') {
return false;
}
if (typeof currentData.ACCOUNT_NAMES !== 'object') {
return false;
}
if (typeof currentData.ESTABLISH_SITES !== 'object') {
return false;
}
if (typeof currentData.ADDRESS_BOOK !== 'object') {
return false;
}
return true;
}

private migrateNetworks(networksDataV004: NetworksModelV004): NetworksModelV005 {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const networks = networksDataV004.map((network: any, index) => {
if (index < 3) {
return {
...network,
id: network.networkId,
default: true,
main: network.networkId === 'test3',
};
}
return {
...network,
id: network.networkId || Date.now(),
default: false,
main: false,
};
});
return networks;
}

private migrateAccountTokenMetainfo(
accountTokenMetainfo: AccountTokenMetainfoModelV004,
): AccountTokenMetainfoModelV005 {
const changedAccountTokenMetainfo: AccountTokenMetainfoModelV005 = {};
for (const accountId of Object.keys(accountTokenMetainfo)) {
const tokenMetainfos = accountTokenMetainfo[accountId].map((tokenMetainfo) => ({
...tokenMetainfo,
networkId:
tokenMetainfo.type === 'gno-native' && tokenMetainfo.symbol === 'GNOT'
? 'DEFAULT'
: 'test3',
}));
changedAccountTokenMetainfo[accountId] = tokenMetainfos;
}
return changedAccountTokenMetainfo;
}

private migrateEstablishSites(establishSites: EstablishSitesModelV004): EstablishSitesModelV005 {
const changedEstablishSites: EstablishSitesModelV005 = {};
for (const accountId of Object.keys(establishSites)) {
const establishSitesOfAccount = establishSites[accountId].filter(
(establishSite, index) =>
establishSites[accountId].findIndex(
(current) => current.hostname === establishSite.hostname,
) === index,
);
changedEstablishSites[accountId] = establishSitesOfAccount;
}
return changedEstablishSites;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
export type StorageModelV005 = {
version: 5;
data: StorageModelDataV005;
};

export type StorageModelDataV005 = {
NETWORKS: NetworksModelV005;
CURRENT_CHAIN_ID: CurrentChainIdModelV005;
CURRENT_NETWORK_ID: CurrentNetworkIdModelV005;
SERIALIZED: SerializedModelV005;
ENCRYPTED_STORED_PASSWORD: EncryptedStoredPasswordModelV005;
CURRENT_ACCOUNT_ID: CurrentAccountIdModelV005;
ACCOUNT_NAMES: AccountNamesModelV005;
ESTABLISH_SITES: EstablishSitesModelV005;
ADDRESS_BOOK: AddressBookModelV005;
ACCOUNT_TOKEN_METAINFOS: AccountTokenMetainfoModelV005;
};

export type NetworksModelV005 = {
id: string;
default: boolean;
main: boolean;
chainId: string;
chainName: string;
networkId: string;
networkName: string;
addressPrefix: string;
rpcUrl: string;
gnoUrl: string;
apiUrl: string;
linkUrl: string;
deleted?: boolean;
}[];

export type CurrentChainIdModelV005 = string;

export type CurrentNetworkIdModelV005 = string;

export type SerializedModelV005 = string;

export type WalletModelV005 = {
accounts: AccountDataModelV005[];
keyrings: KeyringDataModelV005[];
currentAccountId?: string;
};

type AccountDataModelV005 = {
id?: string;
index: number;
type: 'HD_WALLET' | 'PRIVATE_KEY' | 'LEDGER' | 'WEB3_AUTH' | 'AIRGAP';
name: string;
keyringId: string;
hdPath?: number;
publicKey: number[];
addressBytes?: number[];
};

type KeyringDataModelV005 = {
id?: string;
type: 'HD_WALLET' | 'PRIVATE_KEY' | 'LEDGER' | 'WEB3_AUTH' | 'AIRGAP';
publicKey?: number[];
privateKey?: number[];
seed?: number[];
mnemonic?: string;
addressBytes?: number[];
};

export type EncryptedStoredPasswordModelV005 = string;

export type CurrentAccountIdModelV005 = string;

export type AccountNamesModelV005 = { [key in string]: string };

export type EstablishSitesModelV005 = {
[key in string]: {
hostname: string;
chainId: string;
account: string;
name: string;
favicon: string | null;
establishedTime: string;
}[];
};

export type AddressBookModelV005 = {
id: string;
name: string;
address: string;
createdAt: string;
}[];

export type AccountTokenMetainfoModelV005 = {
[key in string]: {
main: boolean;
tokenId: string;
networkId: string;
display: boolean;
type: 'gno-native' | 'grc20' | 'ibc-native' | 'ibc-tokens';
name: string;
symbol: string;
decimals: number;
description?: string;
websiteUrl?: string;
image: string;
denom?: string;
pkgPath?: string;
originChain?: string;
originDenom?: string;
originType?: string;
path?: string;
channel?: string;
port?: string;
}[];
};
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ describe('StorageMigrator', () => {
const migrated = await migrator.migrate(current);

expect(migrated).not.toBeNull();
expect(migrated?.version).toBe(4);
expect(migrated?.version).toBe(5);
expect(migrated?.data).not.toBeNull();
expect(migrated?.data.NETWORKS).toHaveLength(0);
expect(migrated?.data.CURRENT_CHAIN_ID).toBe('');
Expand All @@ -89,7 +89,7 @@ describe('StorageMigrator', () => {
const migrated = await migrator.migrate(current);

expect(migrated).not.toBeNull();
expect(migrated?.version).toBe(4);
expect(migrated?.version).toBe(5);
expect(migrated?.data).not.toBeNull();
expect(migrated?.data.SERIALIZED).not.toBe('');
expect(migrated?.data.ADDRESS_BOOK).toHaveLength(1);
Expand Down
Loading

0 comments on commit 291fe16

Please sign in to comment.