difftreelog
test added CI for the Polkadex network
in: master
7 files changed
tests/.mocha.envdiffbeforeafterboth--- /dev/null
+++ b/tests/.mocha.env
@@ -0,0 +1,20 @@
+RELAY_ACALA_ID=1002
+RELAY_ASTAR_ID=1005
+RELAY_MOONBEAM_ID=1003
+RELAY_POLKADEX_ID=1006
+RELAY_STATEMINT_ID=1004
+RELAY_UNIQUE_ID=1001
+RELAY_HTTP_URL=http://127.0.0.1:9699/relay/
+RELAY_ACALA_HTTP_URL=http://127.0.0.1:9699/relay-acala/
+RELAY_ASTAR_HTTP_URL=http://127.0.0.1:9699/relay-astar/
+RELAY_MOONBEAM_HTTP_URL=http://127.0.0.1:9699/relay-moonbeam/
+RELAY_POLKADEX_HTTP_URL=http://127.0.0.1:9699/relay-polkadex/
+RELAY_STATEMINT_HTTP_URL=http://127.0.0.1:9699/relay-statemint/
+RELAY_UNIQUE_HTTP_URL=http://127.0.0.1:9699/relay-unique/
+RELAY_URL=ws://127.0.0.1:9699/relay/
+RELAY_ACALA_URL=ws://127.0.0.1:9699/relay-acala/
+RELAY_ASTAR_URL=ws://127.0.0.1:9699/relay-astar/
+RELAY_MOONBEAM_URL=ws://127.0.0.1:9699/relay-moonbeam/
+RELAY_POLKADEX_URL=ws://127.0.0.1:9699/relay-polkadex/
+RELAY_STATEMINT_URL=ws://127.0.0.1:9699/relay-statemint/
+RELAY_UNIQUE_URL=ws://127.0.0.1:9699/relay-unique/
tests/.vscode/settings.jsondiffbeforeafterboth--- a/tests/.vscode/settings.json
+++ b/tests/.vscode/settings.json
@@ -1,6 +1,10 @@
{
- "mocha.enabled": true,
- "mochaExplorer.files": "**/*.test.ts",
+ "mochaExplorer.env": {
+ "RUN_GOV_TESTS": "1",
+ "RUN_XCM_TESTS": "1"
+ },
+ "mochaExplorer.files": "src/**/*.test.ts",
+ "mochaExplorer.envPath": ".mocha.env",
"mochaExplorer.require": "ts-node/register",
"eslint.format.enable": true,
"[javascript]": {
tests/src/config.tsdiffbeforeafterboth--- a/tests/src/config.ts
+++ b/tests/src/config.ts
@@ -28,6 +28,7 @@
westmintUrl: process.env.RELAY_WESTMINT_URL || 'ws://127.0.0.1:9948',
statemineUrl: process.env.RELAY_STATEMINE_URL || 'ws://127.0.0.1:9948',
statemintUrl: process.env.RELAY_STATEMINT_URL || 'ws://127.0.0.1:9948',
+ polkadexUrl: process.env.RELAY_POLKADEX_URL || 'ws://127.0.0.1:9950',
};
export default config;
tests/src/util/index.tsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// SPDX-License-Identifier: Apache-2.034import * as path from 'path';5import * as crypto from 'crypto';6import {IKeyringPair} from '@polkadot/types/types/interfaces';7import chai from 'chai';8import chaiAsPromised from 'chai-as-promised';9import chaiSubset from 'chai-subset';10import {Context} from 'mocha';11import config from '../config';12import {ChainHelperBase} from './playgrounds/unique';13import {ILogger} from './playgrounds/types';14import {DevUniqueHelper, SilentLogger, SilentConsole, DevMoonbeamHelper, DevMoonriverHelper, DevAcalaHelper, DevKaruraHelper, DevRelayHelper, DevWestmintHelper, DevStatemineHelper, DevStatemintHelper, DevAstarHelper, DevShidenHelper} from './playgrounds/unique.dev';15import {dirname} from 'path';16import {fileURLToPath} from 'url';1718chai.config.truncateThreshold = 0;19chai.use(chaiAsPromised);20chai.use(chaiSubset);21export const expect = chai.expect;2223const getTestHash = (filename: string) => crypto.createHash('md5').update(filename).digest('hex');2425export const getTestSeed = (filename: string) => `//Alice+${getTestHash(filename)}`;2627async function usingPlaygroundsGeneral<T extends ChainHelperBase, R = void>(28 helperType: new (logger: ILogger) => T,29 url: string,30 code: (helper: T, privateKey: (seed: string | { filename?: string, url?: string, ignoreFundsPresence?: boolean }) => Promise<IKeyringPair>) => Promise<R>,31): Promise<R> {32 const silentConsole = new SilentConsole();33 silentConsole.enable();3435 const helper = new helperType(new SilentLogger());36 let result;37 try {38 await helper.connect(url);39 const ss58Format = helper.chain.getChainProperties().ss58Format;40 const privateKey = async (seed: string | {filename?: string, url?: string, ignoreFundsPresence?: boolean}) => {41 if(typeof seed === 'string') {42 return helper.util.fromSeed(seed, ss58Format);43 }44 if(seed.url) {45 const {filename} = makeNames(seed.url);46 seed.filename = filename;47 } else if(seed.filename) {48 // Pass49 } else {50 throw new Error('no url nor filename set');51 }52 const actualSeed = getTestSeed(seed.filename);53 let account = helper.util.fromSeed(actualSeed, ss58Format);54 // here's to hoping that no55 if(!seed.ignoreFundsPresence && ((helper as any)['balance'] == undefined || await (helper as any).balance.getSubstrate(account.address) < MINIMUM_DONOR_FUND)) {56 console.warn(`${path.basename(seed.filename)}: Not enough funds present on the filename account. Using the default one as the donor instead.`);57 account = helper.util.fromSeed('//Alice', ss58Format);58 }59 return account;60 };61 result = await code(helper, privateKey);62 }63 finally {64 await helper.disconnect();65 silentConsole.disable();66 }67 return result as any as R;68}6970export const usingPlaygrounds = <R = void>(code: (helper: DevUniqueHelper, privateKey: (seed: string | {filename?: string, url?: string, ignoreFundsPresence?: boolean}) => Promise<IKeyringPair>) => Promise<R>, url: string = config.substrateUrl) => usingPlaygroundsGeneral<DevUniqueHelper, R>(DevUniqueHelper, url, code);7172export const usingWestmintPlaygrounds = (url: string, code: (helper: DevWestmintHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevWestmintHelper>(DevWestmintHelper, url, code);7374export const usingStateminePlaygrounds = (url: string, code: (helper: DevWestmintHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevStatemineHelper>(DevWestmintHelper, url, code);7576export const usingStatemintPlaygrounds = (url: string, code: (helper: DevWestmintHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevStatemintHelper>(DevWestmintHelper, url, code);7778export const usingRelayPlaygrounds = (url: string, code: (helper: DevRelayHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevRelayHelper>(DevRelayHelper, url, code);7980export const usingAcalaPlaygrounds = (url: string, code: (helper: DevAcalaHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevAcalaHelper>(DevAcalaHelper, url, code);8182export const usingKaruraPlaygrounds = (url: string, code: (helper: DevKaruraHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevKaruraHelper>(DevAcalaHelper, url, code);8384export const usingMoonbeamPlaygrounds = (url: string, code: (helper: DevMoonbeamHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevMoonbeamHelper>(DevMoonbeamHelper, url, code);8586export const usingMoonriverPlaygrounds = (url: string, code: (helper: DevMoonbeamHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevMoonriverHelper>(DevMoonriverHelper, url, code);8788export const usingAstarPlaygrounds = (url: string, code: (helper: DevAstarHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevAstarHelper>(DevAstarHelper, url, code);8990export const usingShidenPlaygrounds = (url: string, code: (helper: DevShidenHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevShidenHelper>(DevShidenHelper, url, code);9192export const MINIMUM_DONOR_FUND = 4_000_000n;93export const DONOR_FUNDING = 4_000_000n;9495// App-promotion periods:96export const LOCKING_PERIOD = 12n; // 12 blocks of relay97export const UNLOCKING_PERIOD = 6n; // 6 blocks of parachain9899// Native contracts100export const COLLECTION_HELPER = '0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f';101export const CONTRACT_HELPER = '0x842899ECF380553E8a4de75bF534cdf6fBF64049';102103export enum Pallets {104 Inflation = 'inflation',105 ReFungible = 'refungible',106 Fungible = 'fungible',107 NFT = 'nonfungible',108 Scheduler = 'scheduler',109 UniqueScheduler = 'uniqueScheduler',110 AppPromotion = 'apppromotion',111 CollatorSelection = 'collatorselection',112 Session = 'session',113 Identity = 'identity',114 Democracy = 'democracy',115 Council = 'council',116 //CouncilMembership = 'councilmembership',117 TechnicalCommittee = 'technicalcommittee',118 Fellowship = 'fellowshipcollective',119 Preimage = 'preimage',120 Maintenance = 'maintenance',121 TestUtils = 'testutils',122}123124export function requirePalletsOrSkip(test: Context, helper: DevUniqueHelper, requiredPallets: readonly string[]) {125 const missingPallets = helper.fetchMissingPalletNames(requiredPallets);126127 if(missingPallets.length > 0) {128 const skipMsg = `\tSkipping test '${test.test?.title}'.\n\tThe following pallets are missing:\n\t- ${missingPallets.join('\n\t- ')}`;129 console.warn('\x1b[38:5:208m%s\x1b[0m', skipMsg);130 test.skip();131 }132}133134export function itSub(name: string, cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any, opts: { only?: boolean, skip?: boolean, requiredPallets?: readonly string[] } = {}) {135 (opts.only ? it.only :136 opts.skip ? it.skip : it)(name, async function () {137 await usingPlaygrounds(async (helper, privateKey) => {138 if(opts.requiredPallets) {139 requirePalletsOrSkip(this, helper, opts.requiredPallets);140 }141142 await cb({helper, privateKey});143 });144 });145}146export function itSubIfWithPallet(name: string, required: readonly string[], cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any, opts: { only?: boolean, skip?: boolean, requiredPallets?: readonly string[] } = {}) {147 return itSub(name, cb, {requiredPallets: required, ...opts});148}149itSub.only = (name: string, cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any) => itSub(name, cb, {only: true});150itSub.skip = (name: string, cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any) => itSub(name, cb, {skip: true});151152itSubIfWithPallet.only = (name: string, required: readonly string[], cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any) => itSubIfWithPallet(name, required, cb, {only: true});153itSubIfWithPallet.skip = (name: string, required: readonly string[], cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any) => itSubIfWithPallet(name, required, cb, {skip: true});154itSub.ifWithPallets = itSubIfWithPallet;155156export type SchedKind = 'anon' | 'named';157158export function itSched(159 name: string,160 cb: (schedKind: SchedKind, apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any,161 opts: { only?: boolean, skip?: boolean, requiredPallets?: string[] } = {},162) {163 itSub(name + ' (anonymous scheduling)', (apis) => cb('anon', apis), opts);164 itSub(name + ' (named scheduling)', (apis) => cb('named', apis), opts);165}166itSched.only = (name: string, cb: (schedKind: SchedKind, apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any) => itSched(name, cb, {only: true});167itSched.skip = (name: string, cb: (schedKind: SchedKind, apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any) => itSched(name, cb, {skip: true});168itSched.ifWithPallets = itSchedIfWithPallets;169170function itSchedIfWithPallets(name: string, required: string[], cb: (schedKind: SchedKind, apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any, opts: { only?: boolean, skip?: boolean, requiredPallets?: string[] } = {}) {171 return itSched(name, cb, {requiredPallets: required, ...opts});172}173174export function describeXCM(title: string, fn: (this: Mocha.Suite) => void, opts: {skip?: boolean} = {}) {175 (process.env.RUN_XCM_TESTS && !opts.skip176 ? describe177 : describe.skip)(title, fn);178}179180describeXCM.skip = (name: string, fn: (this: Mocha.Suite) => void) => describeXCM(name, fn, {skip: true});181182export function describeGov(title: string, fn: (this: Mocha.Suite) => void, opts: {skip?: boolean} = {}) {183 (process.env.RUN_GOV_TESTS && !opts.skip184 ? describe185 : describe.skip)(title, fn);186}187188describeGov.skip = (name: string, fn: (this: Mocha.Suite) => void) => describeGov(name, fn, {skip: true});189190export function sizeOfInt(i: number) {191 if(i < 0 || i > 0xffffffff) throw new Error('out of range');192 if(i < 0b11_1111) {193 return 1;194 } else if(i < 0b11_1111_1111_1111) {195 return 2;196 } else if(i < 0b11_1111_1111_1111_1111_1111_1111_1111) {197 return 4;198 } else {199 return 5;200 }201}202203const UTF8_ENCODER = new TextEncoder();204export function sizeOfEncodedStr(v: string) {205 const encoded = UTF8_ENCODER.encode(v);206 return sizeOfInt(encoded.length) + encoded.length;207}208209export function sizeOfProperty(prop: {key: string, value: string}) {210 return sizeOfEncodedStr(prop.key) + sizeOfEncodedStr(prop.value);211}212213export function makeNames(url: string) {214 const filename = fileURLToPath(url);215 return {216 filename,217 dirname: dirname(filename),218 };219}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// SPDX-License-Identifier: Apache-2.034import * as path from 'path';5import * as crypto from 'crypto';6import {IKeyringPair} from '@polkadot/types/types/interfaces';7import chai from 'chai';8import chaiAsPromised from 'chai-as-promised';9import chaiSubset from 'chai-subset';10import {Context} from 'mocha';11import config from '../config';12import {ChainHelperBase} from './playgrounds/unique';13import {ILogger} from './playgrounds/types';14import {DevUniqueHelper, SilentLogger, SilentConsole, DevMoonbeamHelper, DevMoonriverHelper, DevAcalaHelper, DevKaruraHelper, DevRelayHelper, DevWestmintHelper, DevStatemineHelper, DevStatemintHelper, DevAstarHelper, DevShidenHelper, DevPolkadexHelper} from './playgrounds/unique.dev';15import {dirname} from 'path';16import {fileURLToPath} from 'url';1718chai.config.truncateThreshold = 0;19chai.use(chaiAsPromised);20chai.use(chaiSubset);21export const expect = chai.expect;2223const getTestHash = (filename: string) => crypto.createHash('md5').update(filename).digest('hex');2425export const getTestSeed = (filename: string) => `//Alice+${getTestHash(filename)}`;2627async function usingPlaygroundsGeneral<T extends ChainHelperBase, R = void>(28 helperType: new (logger: ILogger) => T,29 url: string,30 code: (helper: T, privateKey: (seed: string | { filename?: string, url?: string, ignoreFundsPresence?: boolean }) => Promise<IKeyringPair>) => Promise<R>,31): Promise<R> {32 const silentConsole = new SilentConsole();33 silentConsole.enable();3435 const helper = new helperType(new SilentLogger());36 let result;37 try {38 await helper.connect(url);39 const ss58Format = helper.chain.getChainProperties().ss58Format;40 const privateKey = async (seed: string | {filename?: string, url?: string, ignoreFundsPresence?: boolean}) => {41 if(typeof seed === 'string') {42 return helper.util.fromSeed(seed, ss58Format);43 }44 if(seed.url) {45 const {filename} = makeNames(seed.url);46 seed.filename = filename;47 } else if(seed.filename) {48 // Pass49 } else {50 throw new Error('no url nor filename set');51 }52 const actualSeed = getTestSeed(seed.filename);53 let account = helper.util.fromSeed(actualSeed, ss58Format);54 // here's to hoping that no55 if(!seed.ignoreFundsPresence && ((helper as any)['balance'] == undefined || await (helper as any).balance.getSubstrate(account.address) < MINIMUM_DONOR_FUND)) {56 console.warn(`${path.basename(seed.filename)}: Not enough funds present on the filename account. Using the default one as the donor instead.`);57 account = helper.util.fromSeed('//Alice', ss58Format);58 }59 return account;60 };61 result = await code(helper, privateKey);62 }63 finally {64 await helper.disconnect();65 silentConsole.disable();66 }67 return result as any as R;68}6970export const usingPlaygrounds = <R = void>(code: (helper: DevUniqueHelper, privateKey: (seed: string | {filename?: string, url?: string, ignoreFundsPresence?: boolean}) => Promise<IKeyringPair>) => Promise<R>, url: string = config.substrateUrl) => usingPlaygroundsGeneral<DevUniqueHelper, R>(DevUniqueHelper, url, code);7172export const usingWestmintPlaygrounds = (url: string, code: (helper: DevWestmintHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevWestmintHelper>(DevWestmintHelper, url, code);7374export const usingStateminePlaygrounds = (url: string, code: (helper: DevWestmintHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevStatemineHelper>(DevWestmintHelper, url, code);7576export const usingStatemintPlaygrounds = (url: string, code: (helper: DevWestmintHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevStatemintHelper>(DevWestmintHelper, url, code);7778export const usingRelayPlaygrounds = (url: string, code: (helper: DevRelayHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevRelayHelper>(DevRelayHelper, url, code);7980export const usingAcalaPlaygrounds = (url: string, code: (helper: DevAcalaHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevAcalaHelper>(DevAcalaHelper, url, code);8182export const usingKaruraPlaygrounds = (url: string, code: (helper: DevKaruraHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevKaruraHelper>(DevAcalaHelper, url, code);8384export const usingMoonbeamPlaygrounds = (url: string, code: (helper: DevMoonbeamHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevMoonbeamHelper>(DevMoonbeamHelper, url, code);8586export const usingMoonriverPlaygrounds = (url: string, code: (helper: DevMoonbeamHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevMoonriverHelper>(DevMoonriverHelper, url, code);8788export const usingAstarPlaygrounds = (url: string, code: (helper: DevAstarHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevAstarHelper>(DevAstarHelper, url, code);8990export const usingShidenPlaygrounds = (url: string, code: (helper: DevShidenHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevShidenHelper>(DevShidenHelper, url, code);9192export const usingPolkadexPlaygrounds = (url: string, code: (helper: DevPolkadexHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevPolkadexHelper>(DevPolkadexHelper, url, code);9394export const MINIMUM_DONOR_FUND = 4_000_000n;95export const DONOR_FUNDING = 4_000_000n;9697// App-promotion periods:98export const LOCKING_PERIOD = 12n; // 12 blocks of relay99export const UNLOCKING_PERIOD = 6n; // 6 blocks of parachain100101// Native contracts102export const COLLECTION_HELPER = '0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f';103export const CONTRACT_HELPER = '0x842899ECF380553E8a4de75bF534cdf6fBF64049';104105export enum Pallets {106 Inflation = 'inflation',107 ReFungible = 'refungible',108 Fungible = 'fungible',109 NFT = 'nonfungible',110 Scheduler = 'scheduler',111 UniqueScheduler = 'uniqueScheduler',112 AppPromotion = 'apppromotion',113 CollatorSelection = 'collatorselection',114 Session = 'session',115 Identity = 'identity',116 Democracy = 'democracy',117 Council = 'council',118 //CouncilMembership = 'councilmembership',119 TechnicalCommittee = 'technicalcommittee',120 Fellowship = 'fellowshipcollective',121 Preimage = 'preimage',122 Maintenance = 'maintenance',123 TestUtils = 'testutils',124}125126export function requirePalletsOrSkip(test: Context, helper: DevUniqueHelper, requiredPallets: readonly string[]) {127 const missingPallets = helper.fetchMissingPalletNames(requiredPallets);128129 if(missingPallets.length > 0) {130 const skipMsg = `\tSkipping test '${test.test?.title}'.\n\tThe following pallets are missing:\n\t- ${missingPallets.join('\n\t- ')}`;131 console.warn('\x1b[38:5:208m%s\x1b[0m', skipMsg);132 test.skip();133 }134}135136export function itSub(name: string, cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any, opts: { only?: boolean, skip?: boolean, requiredPallets?: readonly string[] } = {}) {137 (opts.only ? it.only :138 opts.skip ? it.skip : it)(name, async function () {139 await usingPlaygrounds(async (helper, privateKey) => {140 if(opts.requiredPallets) {141 requirePalletsOrSkip(this, helper, opts.requiredPallets);142 }143144 await cb({helper, privateKey});145 });146 });147}148export function itSubIfWithPallet(name: string, required: readonly string[], cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any, opts: { only?: boolean, skip?: boolean, requiredPallets?: readonly string[] } = {}) {149 return itSub(name, cb, {requiredPallets: required, ...opts});150}151itSub.only = (name: string, cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any) => itSub(name, cb, {only: true});152itSub.skip = (name: string, cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any) => itSub(name, cb, {skip: true});153154itSubIfWithPallet.only = (name: string, required: readonly string[], cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any) => itSubIfWithPallet(name, required, cb, {only: true});155itSubIfWithPallet.skip = (name: string, required: readonly string[], cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any) => itSubIfWithPallet(name, required, cb, {skip: true});156itSub.ifWithPallets = itSubIfWithPallet;157158export type SchedKind = 'anon' | 'named';159160export function itSched(161 name: string,162 cb: (schedKind: SchedKind, apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any,163 opts: { only?: boolean, skip?: boolean, requiredPallets?: string[] } = {},164) {165 itSub(name + ' (anonymous scheduling)', (apis) => cb('anon', apis), opts);166 itSub(name + ' (named scheduling)', (apis) => cb('named', apis), opts);167}168itSched.only = (name: string, cb: (schedKind: SchedKind, apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any) => itSched(name, cb, {only: true});169itSched.skip = (name: string, cb: (schedKind: SchedKind, apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any) => itSched(name, cb, {skip: true});170itSched.ifWithPallets = itSchedIfWithPallets;171172function itSchedIfWithPallets(name: string, required: string[], cb: (schedKind: SchedKind, apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any, opts: { only?: boolean, skip?: boolean, requiredPallets?: string[] } = {}) {173 return itSched(name, cb, {requiredPallets: required, ...opts});174}175176export function describeXCM(title: string, fn: (this: Mocha.Suite) => void, opts: {skip?: boolean} = {}) {177 (process.env.RUN_XCM_TESTS && !opts.skip178 ? describe179 : describe.skip)(title, fn);180}181182describeXCM.skip = (name: string, fn: (this: Mocha.Suite) => void) => describeXCM(name, fn, {skip: true});183184export function describeGov(title: string, fn: (this: Mocha.Suite) => void, opts: {skip?: boolean} = {}) {185 (process.env.RUN_GOV_TESTS && !opts.skip186 ? describe187 : describe.skip)(title, fn);188}189190describeGov.skip = (name: string, fn: (this: Mocha.Suite) => void) => describeGov(name, fn, {skip: true});191192export function sizeOfInt(i: number) {193 if(i < 0 || i > 0xffffffff) throw new Error('out of range');194 if(i < 0b11_1111) {195 return 1;196 } else if(i < 0b11_1111_1111_1111) {197 return 2;198 } else if(i < 0b11_1111_1111_1111_1111_1111_1111_1111) {199 return 4;200 } else {201 return 5;202 }203}204205const UTF8_ENCODER = new TextEncoder();206export function sizeOfEncodedStr(v: string) {207 const encoded = UTF8_ENCODER.encode(v);208 return sizeOfInt(encoded.length) + encoded.length;209}210211export function sizeOfProperty(prop: {key: string, value: string}) {212 return sizeOfEncodedStr(prop.key) + sizeOfEncodedStr(prop.value);213}214215export function makeNames(url: string) {216 const filename = fileURLToPath(url);217 return {218 filename,219 dirname: dirname(filename),220 };221}tests/src/util/playgrounds/unique.dev.tsdiffbeforeafterboth--- a/tests/src/util/playgrounds/unique.dev.ts
+++ b/tests/src/util/playgrounds/unique.dev.ts
@@ -3,7 +3,7 @@
import {stringToU8a} from '@polkadot/util';
import {blake2AsHex, encodeAddress, mnemonicGenerate} from '@polkadot/util-crypto';
-import {UniqueHelper, MoonbeamHelper, ChainHelperBase, AcalaHelper, RelayHelper, WestmintHelper, AstarHelper} from './unique';
+import {UniqueHelper, MoonbeamHelper, ChainHelperBase, AcalaHelper, RelayHelper, WestmintHelper, AstarHelper, PolkadexHelper} from './unique';
import {ApiPromise, Keyring, WsProvider} from '@polkadot/api';
import * as defs from '../../interfaces/definitions';
import {IKeyringPair} from '@polkadot/types/types';
@@ -249,6 +249,10 @@
messageHash: eventJsonData(data, 0),
}));
+ static Success = this.Method('Success', data => ({
+ messageHash: eventJsonData(data, 0),
+ }));
+
static Fail = this.Method('Fail', data => ({
messageHash: eventJsonData(data, 0),
outcome: eventData<XcmV2TraitsError>(data, 1),
@@ -406,6 +410,16 @@
}
}
+export class DevPolkadexHelper extends PolkadexHelper {
+ wait: WaitGroup;
+ constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
+ options.helperBase = options.helperBase ?? PolkadexHelper;
+
+ super(logger, options);
+ this.wait = new WaitGroup(this);
+ }
+}
+
export class DevKaruraHelper extends DevAcalaHelper {}
export class ArrangeGroup {
tests/src/util/playgrounds/unique.tsdiffbeforeafterboth--- a/tests/src/util/playgrounds/unique.ts
+++ b/tests/src/util/playgrounds/unique.ts
@@ -3694,6 +3694,12 @@
}
}
+class PolkadexXcmHelperGroup<T extends ChainHelperBase> extends HelperGroup<T> {
+ async whitelistToken(signer: TSigner, assetId: any) {
+ await this.helper.executeExtrinsic(signer, 'api.tx.xcmHelper.whitelistToken', [assetId], true);
+ }
+}
+
class TokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {
async accounts(address: string, currencyId: any) {
const {free} = (await this.helper.callRpc('api.query.tokens.accounts', [address, currencyId])).toJSON() as any;
@@ -3977,6 +3983,30 @@
}
}
+export class PolkadexHelper extends XcmChainHelper {
+ assets: AssetsGroup<PolkadexHelper>;
+ balance: SubstrateBalanceGroup<PolkadexHelper>;
+ xTokens: XTokensGroup<PolkadexHelper>;
+ xcm: XcmGroup<PolkadexHelper>;
+ xcmHelper: PolkadexXcmHelperGroup<PolkadexHelper>;
+
+ constructor(logger?: ILogger, options: { [key: string]: any } = {}) {
+ super(logger, options.helperBase ?? PolkadexHelper);
+
+ this.assets = new AssetsGroup(this);
+ this.balance = new SubstrateBalanceGroup(this);
+ this.xTokens = new XTokensGroup(this);
+ this.xcm = new XcmGroup(this, 'polkadotXcm');
+ this.xcmHelper = new PolkadexXcmHelperGroup(this);
+ }
+
+ getSudo<T extends PolkadexHelper>() {
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ const SudoHelperType = SudoHelper(this.helperBase);
+ return this.clone(SudoHelperType) as T;
+ }
+}
+
// eslint-disable-next-line @typescript-eslint/naming-convention
function ScheduledUniqueHelper<T extends UniqueHelperConstructor>(Base: T) {
return class extends Base {
tests/src/xcm/xcmUnique.test.tsdiffbeforeafterboth--- a/tests/src/xcm/xcmUnique.test.ts
+++ b/tests/src/xcm/xcmUnique.test.ts
@@ -16,14 +16,16 @@
import {IKeyringPair} from '@polkadot/types/types';
import config from '../config';
-import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds, usingStatemintPlaygrounds, usingAstarPlaygrounds} from '../util';
+import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds, usingStatemintPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds} from '../util';
import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';
+import {nToBigInt} from '@polkadot/util';
const UNIQUE_CHAIN = +(process.env.RELAY_UNIQUE_ID || 2037);
const STATEMINT_CHAIN = +(process.env.RELAY_STATEMINT_ID || 1000);
const ACALA_CHAIN = +(process.env.RELAY_ACALA_ID || 2000);
const MOONBEAM_CHAIN = +(process.env.RELAY_MOONBEAM_ID || 2004);
const ASTAR_CHAIN = +(process.env.RELAY_ASTAR_ID || 2006);
+const POLKADEX_CHAIN = +(process.env.RELAY_POLKADEX_ID || 2040);
const STATEMINT_PALLET_INSTANCE = 50;
@@ -32,6 +34,7 @@
const acalaUrl = config.acalaUrl;
const moonbeamUrl = config.moonbeamUrl;
const astarUrl = config.astarUrl;
+const polkadexUrl = config.polkadexUrl;
const RELAY_DECIMALS = 12;
const STATEMINT_DECIMALS = 12;
@@ -791,6 +794,193 @@
});
});
+describeXCM('[XCM] Integration test: Exchanging tokens with Polkadex', () => {
+ let alice: IKeyringPair;
+ let randomAccount: IKeyringPair;
+ let unqFees: bigint;
+ let balanceUniqueTokenInit: bigint;
+ let balanceUniqueTokenMiddle: bigint;
+ let balanceUniqueTokenFinal: bigint;
+ const maxWaitBlocks = 6;
+
+ const uniqueAssetId = {
+ Concrete: {
+ parents: 1,
+ interior: {
+ X1: {
+ Parachain: UNIQUE_CHAIN,
+ },
+ },
+ },
+ };
+
+ before(async () => {
+ await usingPlaygrounds(async (helper, privateKey) => {
+ alice = await privateKey('//Alice');
+ [randomAccount] = await helper.arrange.createAccounts([0n], alice);
+
+ // Set the default version to wrap the first message to other chains.
+ await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);
+ });
+
+ await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {
+ const isWhitelisted = ((await helper.callRpc('api.query.xcmHelper.whitelistedTokens', [])).toJSON() as [])
+ .map(nToBigInt).length != 0;
+
+ if(!isWhitelisted) {
+ await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);
+ }
+
+ await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);
+ });
+
+ await usingPlaygrounds(async (helper) => {
+ await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);
+ balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);
+ });
+ });
+
+ itSub('Should connect and send UNQ to Polkadex', async ({helper}) => {
+
+ const destination = {
+ V2: {
+ parents: 1,
+ interior: {
+ X1: {
+ Parachain: POLKADEX_CHAIN,
+ },
+ },
+ },
+ };
+
+ const beneficiary = {
+ V2: {
+ parents: 0,
+ interior: {
+ X1: {
+ AccountId32: {
+ network: 'Any',
+ id: randomAccount.addressRaw,
+ },
+ },
+ },
+ },
+ };
+
+ const assets = {
+ V2: [
+ {
+ id: {
+ Concrete: {
+ parents: 0,
+ interior: 'Here',
+ },
+ },
+ fun: {
+ Fungible: TRANSFER_AMOUNT,
+ },
+ },
+ ],
+ };
+
+ const feeAssetItem = 0;
+
+ await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');
+ const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
+ balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);
+
+ unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;
+ console.log('[Unique -> Polkadex] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));
+ expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;
+
+ await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {
+ await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);
+ });
+ });
+
+
+ itSub('Should connect to Polkadex and send UNQ back', async ({helper}) => {
+
+ const uniqueMultilocation = {
+ V2: {
+ parents: 1,
+ interior: {
+ X1: {Parachain: UNIQUE_CHAIN},
+ },
+ },
+ };
+
+ const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(
+ randomAccount.addressRaw,
+ {
+ Concrete: {
+ parents: 0,
+ interior: 'Here',
+ },
+ },
+ TRANSFER_AMOUNT,
+ );
+
+ let xcmProgramSent: any;
+
+
+ await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {
+ await helper.getSudo().xcm.send(alice, uniqueMultilocation, xcmProgram);
+
+ xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
+ });
+
+ await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);
+
+ balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);
+
+ expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees);
+ });
+
+ itSub('Polkadex can send only up to its balance', async ({helper}) => {
+ const polkadexBalance = 10000n * (10n ** UNQ_DECIMALS);
+ const polkadexSovereignAccount = helper.address.paraSiblingSovereignAccount(POLKADEX_CHAIN);
+ await helper.getSudo().balance.setBalanceSubstrate(alice, polkadexSovereignAccount, polkadexBalance);
+ const moreThanPolkadexHas = 2n * polkadexBalance;
+
+ const targetAccount = helper.arrange.createEmptyAccount();
+
+ const uniqueMultilocation = {
+ V2: {
+ parents: 1,
+ interior: {
+ X1: {Parachain: UNIQUE_CHAIN},
+ },
+ },
+ };
+
+ const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(
+ targetAccount.addressRaw,
+ {
+ Concrete: {
+ parents: 0,
+ interior: 'Here',
+ },
+ },
+ moreThanPolkadexHas,
+ );
+
+ let maliciousXcmProgramSent: any;
+
+
+ await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {
+ await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);
+
+ maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
+ });
+
+ await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash);
+
+ const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);
+ expect(targetAccountBalance).to.be.equal(0n);
+ });
+});
+
// These tests are relevant only when
// the the corresponding foreign assets are not registered
describeXCM('[XCM] Integration test: Unique rejects non-native tokens', () => {
@@ -930,6 +1120,31 @@
await expectFailedToTransact(helper, messageSent);
});
+
+ itSub('Unique rejects PDX tokens from Polkadex', async ({helper}) => {
+
+ const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(
+ helper.arrange.createEmptyAccount().addressRaw,
+ {
+ Concrete: {
+ parents: 1,
+ interior: {
+ X1: {
+ Parachain: POLKADEX_CHAIN,
+ },
+ },
+ },
+ },
+ testAmount,
+ );
+
+ await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {
+ await helper.getSudo().xcm.send(alice, uniqueParachainMultilocation, maliciousXcmProgramFullId);
+ messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
+ });
+
+ await expectFailedToTransact(helper, messageSent);
+ });
});
describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {