Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add serialization/deserialization unit test to QiHDWallet #343

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
17 changes: 0 additions & 17 deletions src/_tests/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -376,23 +376,6 @@ export interface TestCaseQiTransaction {
signed: string;
}

export interface TestCaseQiSerialization {
name: string;
mnemonic: string;
params: Array<AddrParams>;
outpoints: Array<outpointInfo>;
serialized: {
version: number;
phrase: string;
coinType: number;
addresses: Array<AddressInfo>;
changeAddresses: Array<AddressInfo>;
gapAddresses: Array<AddressInfo>;
changeGapAddresses: Array<AddressInfo>;
outpoints: Array<outpointInfo>;
};
}

export interface TestCaseQiSignMessage {
name: string;
mnemonic: string;
Expand Down
187 changes: 187 additions & 0 deletions src/_tests/unit/qihdwallet-serialization.unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import assert from 'assert';
import { loadTests } from '../utils.js';
import { QiHDWallet, SerializedQiHDWallet, Zone, AddressStatus } from '../../index.js';

describe('QiHDWallet Serialization/Deserialization', function () {
this.timeout(10000);
const tests = loadTests<SerializedQiHDWallet>('qi-wallet-serialization');

for (const test of tests) {
it('should correctly deserialize and reserialize wallet state', async function () {
// First deserialize the wallet from test data
const deserializedWallet = await QiHDWallet.deserialize(test);

// Now serialize it back
const serializedWallet = deserializedWallet.serialize();

// Verify all properties match the original test data
assert.strictEqual(serializedWallet.version, test.version, 'Version mismatch');
assert.strictEqual(serializedWallet.phrase, test.phrase, 'Phrase mismatch');
assert.strictEqual(serializedWallet.coinType, test.coinType, 'Coin type mismatch');

// Compare outpoints
assert.deepStrictEqual(serializedWallet.outpoints, test.outpoints, 'Outpoints mismatch');

// Compare pending outpoints
assert.deepStrictEqual(
serializedWallet.pendingOutpoints,
test.pendingOutpoints,
'Pending outpoints mismatch',
);

// Compare addresses
assert.deepStrictEqual(
serializedWallet.addresses.sort((a, b) => a.index - b.index),
test.addresses.sort((a, b) => a.index - b.index),
'Addresses mismatch',
);

// Compare sender payment code info
assert.deepStrictEqual(
serializedWallet.senderPaymentCodeInfo,
test.senderPaymentCodeInfo,
'Sender payment code info mismatch',
);

// Finally compare the entire serialized object
assert.deepStrictEqual(
serializedWallet,
test,
'Complete serialized wallet does not match original test data',
);
});

it('should maintain wallet functionality after deserialization', async function () {
const deserializedWallet = await QiHDWallet.deserialize(test);
const zone = Zone.Cyprus1;

// Verify the wallet has the correct number of addresses
const externalAddresses = deserializedWallet.getAddressesForZone(zone);
assert.strictEqual(
externalAddresses.length,
test.addresses.filter((addr) => addr.derivationPath === 'BIP44:external' && addr.zone === zone).length,
'External addresses count mismatch',
);

// Verify the wallet has the correct number of change addresses
const changeAddresses = deserializedWallet.getChangeAddressesForZone(zone);
assert.strictEqual(
changeAddresses.length,
test.addresses.filter((addr) => addr.derivationPath === 'BIP44:change' && addr.zone === zone).length,
'Change addresses count mismatch',
);

// Verify gap addresses
const gapAddresses = deserializedWallet.getGapAddressesForZone(zone);
assert.strictEqual(
gapAddresses.length,
test.addresses.filter(
(addr) =>
addr.derivationPath === 'BIP44:external' &&
addr.zone === zone &&
addr.status === AddressStatus.UNUSED,
).length,
'Gap addresses count mismatch',
);

// Verify outpoints were correctly imported
const zoneOutpoints = deserializedWallet.getOutpoints(zone);
assert.strictEqual(
zoneOutpoints.length,
test.outpoints.filter((outpoint) => outpoint.zone === zone).length,
'Outpoints count mismatch',
);

// Verify payment channels were correctly restored
const paymentCodes = Object.keys(test.senderPaymentCodeInfo);
for (const paymentCode of paymentCodes) {
// Verify channel is open
assert.strictEqual(
deserializedWallet.channelIsOpen(paymentCode),
true,
`Payment channel ${paymentCode} not restored`,
);

// Verify payment channel addresses for zone
const paymentChannelAddresses = deserializedWallet.getPaymentChannelAddressesForZone(paymentCode, zone);
assert.strictEqual(
paymentChannelAddresses.length,
test.addresses.filter((addr) => addr.derivationPath === paymentCode && addr.zone === zone).length,
'Payment channel addresses count mismatch',
);

// Verify gap payment channel addresses
const gapPaymentChannelAddresses = deserializedWallet.getGapPaymentChannelAddresses(paymentCode);
assert.strictEqual(
gapPaymentChannelAddresses.length,
test.addresses.filter(
(addr) => addr.derivationPath === paymentCode && addr.status === AddressStatus.UNUSED,
).length,
'Gap payment channel addresses count mismatch',
);

// Verify the addresses match the expected ones
const expectedPaymentChannelAddresses = test.addresses
.filter((addr) => addr.derivationPath === paymentCode && addr.zone === zone)
.sort((a, b) => a.index - b.index);

assert.deepStrictEqual(
paymentChannelAddresses.sort((a, b) => a.index - b.index),
expectedPaymentChannelAddresses,
'Payment channel addresses do not match expected addresses',
);
}
});

it('should correctly handle gap addresses and payment channel addresses', async function () {
const deserializedWallet = await QiHDWallet.deserialize(test);
const zone = '0x00' as Zone;

// Test gap addresses functionality
const gapAddresses = deserializedWallet.getGapAddressesForZone(zone);
for (const addr of gapAddresses) {
assert.strictEqual(addr.status, AddressStatus.UNUSED, 'Gap address should be unused');
assert.strictEqual(addr.derivationPath, 'BIP44:external', 'Gap address should be external');
assert.strictEqual(addr.zone, zone, 'Gap address should be in correct zone');
}

// Test payment channel functionality for each payment code
const paymentCodes = Object.keys(test.senderPaymentCodeInfo);
for (const paymentCode of paymentCodes) {
// Test gap payment channel addresses
const gapPaymentAddresses = deserializedWallet.getGapPaymentChannelAddresses(paymentCode);
for (const addr of gapPaymentAddresses) {
assert.strictEqual(addr.status, AddressStatus.UNUSED, 'Gap payment address should be unused');
assert.strictEqual(
addr.derivationPath,
paymentCode,
'Gap payment address should have correct payment code',
);
}

// Test zone-specific payment channel addresses
const zonePaymentAddresses = deserializedWallet.getPaymentChannelAddressesForZone(paymentCode, zone);
for (const addr of zonePaymentAddresses) {
assert.strictEqual(
addr.derivationPath,
paymentCode,
'Payment address should have correct payment code',
);
assert.strictEqual(addr.zone, zone, 'Payment address should be in correct zone');
}

// Verify all payment addresses are in the original test data
const allTestAddresses = test.addresses
.filter((addr) => addr.derivationPath === paymentCode)
.map((addr) => addr.address);

for (const addr of zonePaymentAddresses) {
assert.ok(
allTestAddresses.includes(addr.address),
'Payment address should exist in original test data',
);
}
}
});
}
});
69 changes: 0 additions & 69 deletions src/_tests/unit/qihdwallet.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
TestCaseQiAddresses,
TestCaseQiSignMessage,
TestCaseQiTransaction,
TestCaseQiSerialization,
AddressInfo,
TxInput,
TxOutput,
Expand Down Expand Up @@ -67,74 +66,6 @@ describe('QiHDWallet: Test address generation and retrieval', function () {
}
});

describe('QiHDWallet: Test serialization and deserialization of QiHDWallet', function () {
this.timeout(10000);
const tests = loadTests<TestCaseQiSerialization>('qi-serialization');
for (const test of tests) {
const mnemonic = Mnemonic.fromPhrase(test.mnemonic);
const qiWallet = QiHDWallet.fromMnemonic(mnemonic);
let serialized: any;
it(`tests serialization QuaiHDWallet: ${test.name}`, async function () {
for (const param of test.params) {
qiWallet.getNextAddressSync(param.account, param.zone);
qiWallet.getNextChangeAddressSync(param.account, param.zone);
}
qiWallet.importOutpoints(test.outpoints);
serialized = qiWallet.serialize();
assert.deepEqual(serialized, test.serialized);
});

it(`tests deserialization QiHDWallet: ${test.name}`, async function () {
const deserialized = await QiHDWallet.deserialize(serialized);
assert.deepEqual(deserialized.serialize(), serialized);
});
}
});

describe('QiHDWallet: Test serialization and deserialization of QiHDWallet with payment codes', function () {
this.timeout(10000);
it('tests serialization and deserialization with payment codes and addresses', async function () {
// Create Alice's wallet
const aliceMnemonic = Mnemonic.fromPhrase(
'empower cook violin million wool twelve involve nice donate author mammal salt royal shiver birth olympic embody hello beef suit isolate mixed text spot',
);
const aliceQiWallet = QiHDWallet.fromMnemonic(aliceMnemonic);

// Create Bob's wallet
const bobMnemonic = Mnemonic.fromPhrase(
'innocent perfect bus miss prevent night oval position aspect nut angle usage expose grace juice',
);
const bobQiWallet = QiHDWallet.fromMnemonic(bobMnemonic);

// Get payment codes
const alicePaymentCode = await aliceQiWallet.getPaymentCode(0);
const bobPaymentCode = await bobQiWallet.getPaymentCode(0);

// Generate addresses
await aliceQiWallet.getNextSendAddress(bobPaymentCode, Zone.Cyprus1);
await aliceQiWallet.getNextReceiveAddress(bobPaymentCode, Zone.Cyprus1);

// Serialize Alice's wallet
const serializedAliceWallet = aliceQiWallet.serialize();

// Deserialize Alice's wallet
const deserializedAliceWallet = await QiHDWallet.deserialize(serializedAliceWallet);

// Assertions
assert.strictEqual(
deserializedAliceWallet.getPaymentCode(0),
alicePaymentCode,
'Payment code should match after deserialization',
);

assert.equal(
deserializedAliceWallet.channelIsOpen(alicePaymentCode),
aliceQiWallet.channelIsOpen(alicePaymentCode),
'Channel should be open',
);
});
});

describe('QiHDWallet: Test transaction signing', function () {
const tests = loadTests<TestCaseQiTransaction>('qi-transaction');
for (const test of tests) {
Expand Down
1 change: 1 addition & 0 deletions src/quais.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ export {
QiAddressInfo,
NeuteredAddressInfo,
OutpointInfo,
AddressStatus,
} from './wallet/index.js';

// WORDLIST
Expand Down
2 changes: 1 addition & 1 deletion src/wallet/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@ export { Wallet } from './wallet.js';

export type { KeystoreAccount, EncryptOptions } from './json-keystore.js';

export { QiHDWallet, SerializedQiHDWallet, QiAddressInfo, OutpointInfo } from './qi-hdwallet.js';
export { QiHDWallet, SerializedQiHDWallet, QiAddressInfo, OutpointInfo, AddressStatus } from './qi-hdwallet.js';

export { HDNodeVoidWallet, HDNodeWallet } from './hdnodewallet.js';
2 changes: 1 addition & 1 deletion src/wallet/qi-hdwallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export interface OutpointInfo {
*
* @enum {string}
*/
enum AddressStatus {
export enum AddressStatus {
USED = 'USED',
UNUSED = 'UNUSED',
ATTEMPTED_USE = 'ATTEMPTED_USE',
Expand Down
Binary file added testcases/qi-wallet-serialization.json.gz
Binary file not shown.
Loading