-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: Tarik Gul <[email protected]>
- Loading branch information
Showing
24 changed files
with
1,171 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); |
Oops, something went wrong.