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

PolkaMask tests #6

Merged
merged 4 commits into from
Jan 13, 2024
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
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write",
"lint:misc": "prettier '**/*.json' '**/*.md' '!**/CHANGELOG.md' '**/*.yml' --ignore-path .gitignore",
"start": "yarn workspaces foreach --parallel --interlaced --verbose run start",
"test": "cd packages/snap && jest test",
"test": "cd packages/snap && yarn test",
"docker:build": "docker build -t polkagate/dapp -f docker/Dockerfile .",
"docker:run": "docker run --rm -it --name polkamask-ui -p 80:80 -p 8000:8000 -p 8080:8080 polkagate/dapp:latest",
"docker": "yarn docker:build && yarn docker:run",
Expand Down Expand Up @@ -59,7 +59,7 @@
"dependencies": {
"@metamask/detect-provider": "^2.0.0",
"@metamask/key-tree": "^9.0.0",
"@polkadot/api": "^10.9.1",
"@polkadot/api": "^10.11.2",
"@polkadot/extension-chains": "latest",
"@polkadot/keyring": "^12.5.1",
"@polkadot/types": "^10.11.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/dapp/dev package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"@mui/icons-material": "latest",
"@mui/lab": "latest",
"@mui/material": "latest",
"@polkadot/api": "latest",
"@polkadot/api": "^10.11.2",
"@polkadot/api-derive": "latest",
"@polkadot/apps-config": "latest",
"@polkadot/extension-inject": "latest",
Expand Down
4 changes: 2 additions & 2 deletions packages/dapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"@mui/icons-material": "latest",
"@mui/lab": "latest",
"@mui/material": "latest",
"@polkadot/api": "latest",
"@polkadot/api": "^10.11.2",
"@polkadot/api-derive": "latest",
"@polkadot/apps-config": "latest",
"@polkadot/extension-inject": "latest",
Expand Down Expand Up @@ -54,4 +54,4 @@
"last 1 safari version"
]
}
}
}
4 changes: 2 additions & 2 deletions packages/snap/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@
"lint:misc": "prettier '**/*.json' '**/*.md' '!CHANGELOG.md' --ignore-path .gitignore",
"serve": "mm-snap serve",
"start": "mm-snap watch",
"test": "jest"
"test": "jest --silent"
},
"dependencies": {
"@metamask/key-tree": "^9.0.0",
"@metamask/snaps-sdk": "^1.3.2",
"@metamask/utils": "^8.2.1",
"@polkadot/api": "^10.11.1",
"@polkadot/api": "^10.11.2",
"@polkadot/apps-config": "^0.133.1",
"@polkadot/keyring": "^12.6.2",
"@polkadot/networks": "^12.6.2",
Expand Down
2 changes: 1 addition & 1 deletion packages/snap/snap.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/polkagate/polkamask.git"
},
"source": {
"shasum": "7nvo1EwntrBk820zUJ8SpEogOALKDeKlshU6yw2E+HU=",
"shasum": "TQdrvrkZRijxZzf42ounDauv4xm9lVSztfGQRkagO7E=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
220 changes: 188 additions & 32 deletions packages/snap/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,58 @@
/* eslint-disable jest/no-conditional-expect */
/* eslint-disable prettier/prettier */
/* eslint-disable jest/no-restricted-matchers */
/* eslint-disable jest/prefer-strict-equal */

import { installSnap } from '@metamask/snaps-jest';
import { copyable, divider, heading, panel, text } from '@metamask/snaps-sdk';
import { Json } from '@metamask/utils';
import { hexToU8a, isHex } from '@polkadot/util';
// import { expect } from '@jest/globals';
import { decodeAddress, encodeAddress } from '@polkadot/util-crypto';

jest.setTimeout(60000);

/**
* Verifies the validity of a substrate-based address.
*
* @param _address - The address to be checked for being a valid address.
* @returns True if the address is a valid substrate address.
*/
function isValidAddress(_address: string | undefined): boolean {
try {
encodeAddress(
isHex(_address)
? hexToU8a(_address)
: decodeAddress(_address)
);
import { copyable, divider, heading, panel, text, row, RowVariant } from '@metamask/snaps-sdk';
import { Json, JsonRpcParams } from '@metamask/utils';
import { decodeAddress, signatureVerify } from '@polkadot/util-crypto';
import { u8aToHex, isHex } from '@polkadot/util';
import { ApiPromise, HttpProvider } from '@polkadot/api';
import isValidAddress from '../../dapp/src/util/isValidAddress';
import { buildPayload } from '../../dapp/src/util/buildPayload';
import { getGenesisHash } from './chains';
import { getFormatted } from './util/getFormatted';
import { formatCamelCase } from './util/formatCamelCase';

const getApi = async () => {
const httpEndpointURL = 'https://wnd-rpc.stakeworld.io';
const httpProvider = new HttpProvider(httpEndpointURL);

const api = await ApiPromise.create({ provider: httpProvider });

return true;
} catch (error) {
return false;
}
return api;
}

jest.setTimeout(200000);

const isValidSignature = (signedMessage: string | Uint8Array, signature: string, address: string | Uint8Array) => {
const publicKey = decodeAddress(address);
const hexPublicKey = u8aToHex(publicKey);

return signatureVerify(signedMessage, signature, hexPublicKey).isValid;
};

const origin = 'Jest Test';
const sampleAccountAddress = '5GHSA3fJCRqLZhwgo6ezTjjQgBrBoLWTbznWPunH6wEzJ7vm';
const sampleWestendAccountAddress = '5Cc8FwTx2nGbM26BdJqdseQBF8C1JeF1tbiabwPHa2UhB4fv';
let metamaskAccountAddr: string | undefined;

describe('onRpcRequest', () => {
beforeAll(async () => {
const { request, close } = await installSnap();

const response = await request({
method: 'getAddress',
origin
});

if ('result' in response.response) {
metamaskAccountAddr = response.response.result?.toString();
}

await close();
});

it('throws an error if the requested method does not exist', async () => {
const { request, close } = await installSnap();

Expand All @@ -52,25 +70,67 @@ describe('onRpcRequest', () => {
});

it('"getAddress" RPC request method', async () => {
let samplePolkadotAccountAddress = '';
let sampleKusamaAccountAddress = '';

const { request, close } = await installSnap();

const response = await request({
const response = request({
method: 'getAddress',
origin
});

const ui = await response.getInterface({ timeout: 60000 });

let accountAddr: string | undefined;

if ('result' in response.response) {
accountAddr = response.response.result?.toString();
const returnedValue = await response;

if ('result' in returnedValue.response) {
accountAddr = returnedValue.response.result?.toString();
samplePolkadotAccountAddress = getFormatted(getGenesisHash('polkadot'), accountAddr ?? '');
sampleKusamaAccountAddress = getFormatted(getGenesisHash('kusama'), accountAddr ?? '');
} else {
accountAddr = undefined;
}

const expectedInterface = (panel([
heading('Your Account on Different Chains'),
divider(),
panel([
text('**Polkadot**'),
copyable(samplePolkadotAccountAddress),
text(
`Transferable: **0** / 0`,
),
divider(),
]),
panel([
text('**Kusama**'),
copyable(sampleKusamaAccountAddress),
text(
`Transferable: **0** / 0`,
),
divider(),
]),
panel([
text('**Westend**'),
copyable(accountAddr),
text(
`Transferable: **0** / 0`,
),
]),
]));

expect(response).toBeTruthy();
expect(accountAddr).toBeTruthy();
expect(isValidAddress(accountAddr)).toBe(true);

expect(ui.type).toBe('alert');
samplePolkadotAccountAddress && sampleKusamaAccountAddress && expect(ui.content).toEqual(expectedInterface);

await ui.ok();

await close();
});

Expand Down Expand Up @@ -102,10 +162,12 @@ describe('onRpcRequest', () => {
const signRawParams = {
raw: {
data: 'Sign me!',
address: sampleAccountAddress
address: sampleWestendAccountAddress
}
};

let snapSignature;

const expectedInterface = (
panel([
heading(`A signature request is received from ${origin}`),
Expand All @@ -128,8 +190,102 @@ describe('onRpcRequest', () => {
expect(ui).toRender(expectedInterface);
await ui.ok();

const result = await response;
expect(result).toBeTruthy();
const returnedValue = await response;

if ('result' in returnedValue.response) {
const signature = returnedValue.response.result;
snapSignature = JSON.parse(JSON.stringify(signature)).signature;
}

expect(isValidSignature(signRawParams.raw.data, snapSignature, metamaskAccountAddr ?? '')).toBeTruthy();

await close();
});

it('"signJSON" RPC request method', async () => {
const api = await getApi();

const params = [sampleWestendAccountAddress, '5000000000000'];
const tx = api.tx.balances.transferKeepAlive(...params);
const fee = await (await tx.paymentInfo(sampleWestendAccountAddress)).partialFee;
const payload = await buildPayload(api, tx, metamaskAccountAddr ?? '');

const expectedInterface = (
panel([
heading(`Transaction Approval Request from ${origin}`),
divider(),
row('Action: ', text(`**${formatCamelCase('balances')}** (**${formatCamelCase('transferKeepAlive')}**)`)),
divider(),
row('Amount:', text(`**5 WND** `)),
text(
'To: ',
),
copyable(metamaskAccountAddr),
divider(),
row('Estimated Fee:', text(`**${fee.toHuman()}**`)),
divider(),
row('Chain Name:', text(`**${formatCamelCase('westend')}**`)),
divider(),
row(
'More info:',
text('**See [Pallet::transfer_keep_alive].**'),
RowVariant.Warning,
)
])
);

const { request, close } = await installSnap();

const response = request({
method: 'signJSON',
origin,
params: { payload: payload as unknown as JsonRpcParams }
});

const ui = await response.getInterface({ timeout: 120000 });
expect(ui.type).toBe('confirmation');
expect(ui.content).toEqual(expectedInterface);

await ui.ok();

const returnedValue = await response;

if ('result' in returnedValue.response) {
const signature = returnedValue.response.result;
const sign = JSON.parse(JSON.stringify(signature));

expect(isHex(sign.signature) && sign.signature.length === 132).toBeTruthy();
}

await close();
});

it('"signJSON" RPC request method invalid payload', async () => {
const { request, close } = await installSnap();

const response = request({
method: 'signJSON',
origin,
params: { payload: {} }
});

const getUI = async () => {
try {
await response.getInterface({ timeout: 20000 });

return true;
} catch (error) {
return false;
}
};

expect(await getUI()).toBeFalsy();

const returnedValue = await response;

if ('result' in returnedValue.response) {
expect(returnedValue.response.result).toBeFalsy();
}

await close();
});
Expand Down
Loading
Loading