Skip to content

Commit

Permalink
test: e2e tests (#343)
Browse files Browse the repository at this point in the history
Co-authored-by: Tarik Gul <[email protected]>
  • Loading branch information
bee344 and TarikGul authored Jan 30, 2024
1 parent 6b42fdb commit 7d5de87
Show file tree
Hide file tree
Showing 24 changed files with 1,171 additions and 23 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ zombienet-macos
zombienet-linux-arm64
zombienet-linux-x64
zombienet.log
zombienet/bin/*

# Binaries
/bin/*
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -323,3 +323,7 @@ From the root directory run `./<zombienet_binary_name> -p native spawn ./zombien
### Create an asset

From the root directory run `yarn start:zombienet-post-script`. You can run this right after running your zombienet network.

## E2E Testing

You can access the E2E tests and its documentation [here](./e2e-tests/).
36 changes: 36 additions & 0 deletions e2e-tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
## E2E Tests

End-to-end tests that run on a zombienet testnet.

**NOTE: tested using polkadot v1.4.0**

### Setup

To setup the testing environment you need to first download the `polkadot`, `polkadot-execute-worker`, `polkadot-prepare-worker` and `polkadot-parachain` from the `polkadot-sdk` [release page](https://github.com/paritytech/polkadot-sdk/releases/latest), as well as the `trappist-node` from its [release page](https://github.com/paritytech/trappist/releases/latest), and place them in the `../zombienet/bin/` folder.

You also need to have the latest `zombienet` executable in the `../zombienet/` folder, which you can download from [here](https://github.com/paritytech/zombienet/releases/latest).

### Launching zombienet

To launch the zombienet run the following commands:
```bash
$ yarn build && yarn e2e:build
```
Then run:
```bash
$ yarn e2e:zombienet
```
And this will launch the zombienet using the config file located in the `../zombienet/` directory. Once it finished its setup, you can proceed to the following step.

### Launching the tests

For testing, we provide 4 options:

* Testing liquidity tokens transfers with the command `yarn e2e:liquidity-assets`.
* Testing foreign assets transfers with the command `yarn e2e:foreign-assets`.
* Testing local transferss with the command `yarn e2e:local`.
* Testing assets transfers with the command `yarn e2e:assets`.

Each of these commands will run the appropiate script to setup the basics, located in `../scripts/`. Wait for it to finish setting up the testing environment, and then go through the tests indicated in the `./tests/index.ts` file for the chosen option.

After each testing suite has been completed, it's recommended to restart the zombienet before running another test suite.
138 changes: 138 additions & 0 deletions e2e-tests/balance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// Copyright 2023 Parity Technologies (UK) Ltd.

import type { ApiPromise } from '@polkadot/api';
import type { FrameSystemAccountInfo, PalletAssetsAssetAccount } from '@polkadot/types/lookup';
import type { Option } from '@polkadot/types-codec';

/**
* Initial and final balances for a specific assetId, used by `balanceTracker`
* to validate the evolution of a particular account's balance
*
* @interface IBalance
*
* @property {[string, number][]} IBalance.initial Account's initial balance
* for the given assetIds
* @property {[string, number][]} IBalance.final Account's final balance for
* the given assetIds
*/
export interface IBalance {
initial: [string, number][];
final: [string, number][];
}

/**
* Function to keep track of the evolution of the balances for specific assetIds
* for a given account, used to validate the correct processing of the tests'
* transactions.
* It queries the node for the appropiate pallet's balance for the given test
* suite, and stores it as either `initial`, if no previous value is stored,
* or `final`. Both properties consist of an array of tuples containing an assetId
* and its balance for the account for the moment the value was queried, either
* before or after running the test.
*
* @param api api instance
* @param test name of the test currently being ran
* @param address address of the account whose balance is being queried
* @param assetIds Ids of the assets being queried
* @param balance initial balance for the account queried
* @returns instance of IBalance containing the initial and optionally the final
* balance of the account queried
*/
export const balanceTracker = async (
api: ApiPromise,
test: string,
address: string,
assetIds: string[],
balance?: IBalance,
): Promise<IBalance> => {
let balances: IBalance = { initial: [], final: [] };
let accountInfo: FrameSystemAccountInfo | Option<PalletAssetsAssetAccount>;
switch (test) {
case '--foreign-assets':
if (!balance) {
for (const assetId of assetIds) {
accountInfo = (await api.query.foreignAssets.account(assetId, address)) as Option<PalletAssetsAssetAccount>;
if (accountInfo.valueOf() === null) {
balances.initial.push([assetId, 0]);
} else {
balances.initial.push([assetId, accountInfo.unwrap().balance.toBn().toNumber()]);
}
}
} else {
balances = balance;
for (const assetId of assetIds) {
accountInfo = (await api.query.foreignAssets.account(assetId, address)) as Option<PalletAssetsAssetAccount>;
balances.final.push([assetId, accountInfo.unwrap().balance.toBn().toNumber()]);
}
}
return balances;
case '--liquidity-assets':
if (!balance) {
for (const assetId of assetIds) {
accountInfo = await api.query.poolAssets.account(assetId, address);
if (accountInfo.isNone) {
balances.initial.push([assetId, 0]);
} else {
balances.initial.push([assetId, accountInfo.unwrap().balance.toBn().toNumber()]);
}
}
} else {
balances = balance;
for (const assetId of assetIds) {
accountInfo = await api.query.poolAssets.account(assetId, address);
balances.final.push([assetId, accountInfo.unwrap().balance.toBn().toNumber()]);
}
}
return balances;
case '--local':
if (!balance) {
accountInfo = await api.query.system.account(address);
if (accountInfo === null) {
balances.initial.push(['0', 0]);
} else {
balances.initial.push(['0', Number(accountInfo.data.free)]);
}
} else {
balances = balance;
accountInfo = await api.query.system.account(address);
balances.final.push(['0', Number(accountInfo.data.free)]);
}
return balances;
case '--assets':
if (!balance) {
for (const assetId of assetIds) {
accountInfo = await api.query.assets.account(assetId, address);
if (accountInfo.isNone) {
balances.initial.push([assetId, 0]);
} else {
balances.initial.push([assetId, accountInfo.unwrap().balance.toBn().toNumber()]);
}
}
} else {
balances = balance;
for (const assetId of assetIds) {
accountInfo = await api.query.assets.account(assetId, address);
balances.final.push([assetId, accountInfo.unwrap().balance.toBn().toNumber()]);
}
}
return balances;
default:
if (!balance) {
for (const assetId of assetIds) {
accountInfo = await api.query.system.account(address);
if (accountInfo) {
balances.initial.push([assetId, accountInfo.data.free.toBn().toNumber()]);
} else {
balances.initial.push([assetId, 0]);
}
}
} else {
balances = balance;
for (const assetId of assetIds) {
accountInfo = await api.query.system.account(address);
balances.final.push([assetId, accountInfo.data.free.toBn().toNumber()]);
}
}
return balances;
}
};
10 changes: 10 additions & 0 deletions e2e-tests/consts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright 2023 Parity Technologies (UK) Ltd.

export const KUSAMA_ASSET_HUB_WS_URL = 'ws://127.0.0.1:9911';
export const ROCOCO_ALICE_WS_URL = 'ws://127.0.0.1:9900';
export const TRAPPIST_WS_URL = 'ws://127.0.0.1:9921';

export const BOB_ADDR = '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty';
export const FERDE_ADDR = '5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL';

export const EXTRINSIC_VERSION = 4;
152 changes: 152 additions & 0 deletions e2e-tests/executor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// Copyright 2023 Parity Technologies (UK) Ltd.
import { ApiPromise, WsProvider } from '@polkadot/api';
import { Keyring } from '@polkadot/keyring';
import { cryptoWaitReady } from '@polkadot/util-crypto';

import { delay } from '../scripts/util';
import { constructApiPromise } from '../src';
import { balanceTracker, IBalance } from './balance';
import { KUSAMA_ASSET_HUB_WS_URL, ROCOCO_ALICE_WS_URL, TRAPPIST_WS_URL } from './consts';
import { startProgressBar, startTestLogger, terminateProgressBar, testResultLogger, updateProgressBar } from './logger';
import { assetTests, foreignAssetsTests, IndividualTest, liquidPoolsTests, localTests, tests } from './tests';
import { verification } from './verification';

const executor = async (testCase: string) => {
let originWsUrl = '';
let destWsUrl = '';
let testData: IndividualTest[] = [];

await cryptoWaitReady();

const keyring = new Keyring({ type: 'sr25519' });
let n: { [K: string]: Function } = {};

switch (testCase) {
case '--foreign-assets':
testData = tests.foreignAssets;
n = foreignAssetsTests;
break;
case '--liquidity-assets':
testData = tests.liquidPools;
n = liquidPoolsTests;
break;
case '--local':
testData = tests.local;
n = localTests;
break;
case '--assets':
testData = tests.assets;
n = assetTests;
break;
}

let counter: number = 0;

startTestLogger(testCase);

const progressBar = startProgressBar(testData, testCase);

const results: [string, string, string, boolean][] = [];

for (const t of testData) {
const originChainId: string = t.args[0];
const destChainId: string = t.args[1];
const originAddr: string = t.args[2];
const destAddr: string = t.args[3];
const assetIds: string[] = t.args[4].slice(1, -1).split(',');
const amounts: string[] = t.args[5].slice(1, -1).split(',');
const opts: object = JSON.parse(t.args[6], (key: string, value: string) => {
return key === 'paysWithFeeOrigin' ? JSON.stringify(value) : value;
}) as object;
let chainName: string = '';

switch (originChainId) {
case '0':
originWsUrl = ROCOCO_ALICE_WS_URL;
chainName = 'Rococo';
break;
case '1000':
originWsUrl = KUSAMA_ASSET_HUB_WS_URL;
chainName = 'Kusama Asset Hub';
break;
case '1836':
originWsUrl = TRAPPIST_WS_URL;
chainName = 'Trappist';
break;
}
if (originChainId == destChainId) {
destWsUrl = originWsUrl;
} else {
switch (destChainId) {
case '0':
destWsUrl = ROCOCO_ALICE_WS_URL;
chainName = 'Rococo';
break;
case '1000':
destWsUrl = KUSAMA_ASSET_HUB_WS_URL;
chainName = 'Kusama Asset Hub';
break;
case '1836':
destWsUrl = TRAPPIST_WS_URL;
chainName = 'Trappist';
break;
}
}

const { api, specName, safeXcmVersion } = await constructApiPromise(originWsUrl);

await api.isReady;

const originApi = api;
const destinationApi =
originChainId == destChainId
? originApi
: await ApiPromise.create({
provider: new WsProvider(destWsUrl),
noInitWarn: true,
});

await destinationApi.isReady;
const destInitialBalance: IBalance = await balanceTracker(destinationApi, testCase, destAddr, assetIds);
const originKeyring = keyring.addFromUri(originAddr);

//eslint-disable-next-line @typescript-eslint/no-unsafe-call
await n[t.test](originKeyring, destChainId, destAddr, assetIds, amounts, opts, api, specName, safeXcmVersion);

await delay(24000);

const destFinalBalance: IBalance = await balanceTracker(
destinationApi,
testCase,
destAddr,
assetIds,
destInitialBalance,
);

const verificationAssetIds: string[] = t.verification[0].slice(1, -1).split(',');
const verificationAmounts: string[] = t.verification[1].slice(1, -1).split(',');

const correctlyReceived = verification(verificationAssetIds, verificationAmounts, destFinalBalance);

await originApi.disconnect();
await destinationApi.disconnect();

counter += 1;

updateProgressBar(counter, progressBar);

for (let i = 0; i < assetIds.length; i++) {
results.push([t.test, assetIds[i], chainName, correctlyReceived[i][1]]);
}
}

for (let i = 0; i < results.length; i++) {
testResultLogger(results[i][0], results[i][1], results[i][2], results[i][3]);
}

terminateProgressBar(progressBar, testCase);
};

executor(process.argv[2])
.catch((err) => console.error(err))
.finally(() => process.exit());
Loading

0 comments on commit 7d5de87

Please sign in to comment.