From 683a1283827ba116d594d62fc563c376394f562b Mon Sep 17 00:00:00 2001 From: PraetorP Date: Mon, 11 Sep 2023 10:31:28 +0000 Subject: [PATCH] test: added CI for the Polkadex network --- --- /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/ --- 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]": { --- 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; --- a/tests/src/util/index.ts +++ b/tests/src/util/index.ts @@ -11,7 +11,7 @@ import config from '../config'; import {ChainHelperBase} from './playgrounds/unique'; import {ILogger} from './playgrounds/types'; -import {DevUniqueHelper, SilentLogger, SilentConsole, DevMoonbeamHelper, DevMoonriverHelper, DevAcalaHelper, DevKaruraHelper, DevRelayHelper, DevWestmintHelper, DevStatemineHelper, DevStatemintHelper, DevAstarHelper, DevShidenHelper} from './playgrounds/unique.dev'; +import {DevUniqueHelper, SilentLogger, SilentConsole, DevMoonbeamHelper, DevMoonriverHelper, DevAcalaHelper, DevKaruraHelper, DevRelayHelper, DevWestmintHelper, DevStatemineHelper, DevStatemintHelper, DevAstarHelper, DevShidenHelper, DevPolkadexHelper} from './playgrounds/unique.dev'; import {dirname} from 'path'; import {fileURLToPath} from 'url'; @@ -89,6 +89,8 @@ export const usingShidenPlaygrounds = (url: string, code: (helper: DevShidenHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevShidenHelper, url, code); +export const usingPolkadexPlaygrounds = (url: string, code: (helper: DevPolkadexHelper, privateKey: (seed: string) => Promise) => Promise) => usingPlaygroundsGeneral(DevPolkadexHelper, url, code); + export const MINIMUM_DONOR_FUND = 4_000_000n; export const DONOR_FUNDING = 4_000_000n; --- 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(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 { --- a/tests/src/util/playgrounds/unique.ts +++ b/tests/src/util/playgrounds/unique.ts @@ -3694,6 +3694,12 @@ } } +class PolkadexXcmHelperGroup extends HelperGroup { + async whitelistToken(signer: TSigner, assetId: any) { + await this.helper.executeExtrinsic(signer, 'api.tx.xcmHelper.whitelistToken', [assetId], true); + } +} + class TokensGroup extends HelperGroup { 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; + balance: SubstrateBalanceGroup; + xTokens: XTokensGroup; + xcm: XcmGroup; + xcmHelper: PolkadexXcmHelperGroup; + + 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() { + // 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(Base: T) { return class extends Base { --- 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', () => { -- gitstuff