difftreelog
test(xcm qtz) added `XcmTestHelper`
in: master
5 files changed
tests/package.jsondiffbeforeafterboth--- a/tests/package.json
+++ b/tests/package.json
@@ -116,6 +116,8 @@
"testXcmUnique": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/xcmUnique.test.ts",
"testFullXcmUnique": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/*Unique.test.ts",
"testXcmQuartz": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/xcmQuartz.test.ts",
+ "testLowLevelXcmQuartz": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/lowLevelXcmQuartz.test.ts",
+ "testFullXcmQuartz": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/*Quartz.test.ts",
"testXcmOpal": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/xcmOpal.test.ts",
"testXcmTransferAcala": "yarn _test ./**/xcm/xcmTransferAcala.test.ts acalaId=2000 uniqueId=5000",
"testXcmTransferStatemine": "yarn _test ./**/xcm/xcmTransferStatemine.test.ts statemineId=1000 uniqueId=5000",
tests/src/xcm/lowLevelXcmQuartz.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/xcm/lowLevelXcmQuartz.test.ts
@@ -0,0 +1,381 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {IKeyringPair} from '@polkadot/types/types';
+import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingMoonriverPlaygrounds, usingShidenPlaygrounds} from '../util';
+import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';
+import {STATEMINE_CHAIN, QUARTZ_CHAIN, KARURA_CHAIN, MOONRIVER_CHAIN, SHIDEN_CHAIN, STATEMINE_DECIMALS, KARURA_DECIMALS, QTZ_DECIMALS, RELAY_DECIMALS, SHIDEN_DECIMALS, karuraUrl, moonriverUrl, relayUrl, shidenUrl, statemineUrl, SAFE_XCM_VERSION, XcmTestHelper, TRANSFER_AMOUNT} from './xcm.types';
+
+
+const testHelper = new XcmTestHelper('quartz');
+
+describeXCM('[XCMLL] Integration test: Exchanging tokens with Karura', () => {
+ let alice: IKeyringPair;
+ let randomAccount: IKeyringPair;
+
+ let balanceQuartzTokenInit: bigint;
+ let balanceQuartzTokenMiddle: bigint;
+ let balanceQuartzTokenFinal: bigint;
+ let balanceKaruraTokenInit: bigint;
+ let balanceKaruraTokenMiddle: bigint;
+ let balanceKaruraTokenFinal: bigint;
+ let balanceQuartzForeignTokenInit: bigint;
+ let balanceQuartzForeignTokenMiddle: bigint;
+ let balanceQuartzForeignTokenFinal: bigint;
+
+ // computed by a test transfer from prod Quartz to prod Karura.
+ // 2 QTZ sent https://quartz.subscan.io/xcm_message/kusama-f60d821b049f8835a3005ce7102285006f5b61e9
+ // 1.919176000000000000 QTZ received (you can check Karura's chain state in the corresponding block)
+ const expectedKaruraIncomeFee = 2000000000000000000n - 1919176000000000000n;
+ const karuraEps = 8n * 10n ** 16n;
+
+ let karuraBackwardTransferAmount: bigint;
+
+ 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 usingKaruraPlaygrounds(karuraUrl, async (helper) => {
+ const destination = {
+ V2: {
+ parents: 1,
+ interior: {
+ X1: {
+ Parachain: QUARTZ_CHAIN,
+ },
+ },
+ },
+ };
+
+ const metadata = {
+ name: 'Quartz',
+ symbol: 'QTZ',
+ decimals: 18,
+ minimalBalance: 1000000000000000000n,
+ };
+
+ await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);
+ await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);
+ balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);
+ balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});
+ });
+
+ await usingPlaygrounds(async (helper) => {
+ await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);
+ balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);
+ });
+ });
+
+ itSub('Should connect and send QTZ to Karura', async () => {
+ await testHelper.sendUnqTo('karura', randomAccount);
+ });
+
+ itSub('Should connect to Karura and send QTZ back', async () => {
+ await testHelper.sendUnqBack('karura', alice, randomAccount);
+ });
+
+ itSub('Karura can send only up to its balance', async () => {
+ await testHelper.sendOnlyOwnedBalance('karura', alice);
+ });
+});
+// These tests are relevant only when
+// the the corresponding foreign assets are not registered
+describeXCM('[XCMLL] Integration test: Quartz rejects non-native tokens', () => {
+ let alice: IKeyringPair;
+ let alith: IKeyringPair;
+
+ const testAmount = 100_000_000_000n;
+ let quartzParachainJunction;
+ let quartzAccountJunction;
+
+ let quartzParachainMultilocation: any;
+ let quartzAccountMultilocation: any;
+ let quartzCombinedMultilocation: any;
+
+ let messageSent: any;
+
+ const maxWaitBlocks = 3;
+
+ before(async () => {
+ await usingPlaygrounds(async (helper, privateKey) => {
+ alice = await privateKey('//Alice');
+
+ quartzParachainJunction = {Parachain: QUARTZ_CHAIN};
+ quartzAccountJunction = {
+ AccountId32: {
+ network: 'Any',
+ id: alice.addressRaw,
+ },
+ };
+
+ quartzParachainMultilocation = {
+ V2: {
+ parents: 1,
+ interior: {
+ X1: quartzParachainJunction,
+ },
+ },
+ };
+
+ quartzAccountMultilocation = {
+ V2: {
+ parents: 0,
+ interior: {
+ X1: quartzAccountJunction,
+ },
+ },
+ };
+
+ quartzCombinedMultilocation = {
+ V2: {
+ parents: 1,
+ interior: {
+ X2: [quartzParachainJunction, quartzAccountJunction],
+ },
+ },
+ };
+
+ // Set the default version to wrap the first message to other chains.
+ await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);
+ });
+
+ // eslint-disable-next-line require-await
+ await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {
+ alith = helper.account.alithAccount();
+ });
+ });
+
+ itSub('Quartz rejects KAR tokens from Karura', async () => {
+ await testHelper.rejectNativeTokensFrom('karura', alice);
+ });
+
+ itSub('Quartz rejects MOVR tokens from Moonriver', async () => {
+ await testHelper.rejectNativeTokensFrom('moonriver', alice);
+ });
+
+ itSub('Quartz rejects SDN tokens from Shiden', async () => {
+ await testHelper.rejectNativeTokensFrom('shiden', alice);
+ });
+});
+
+describeXCM('[XCMLL] Integration test: Exchanging QTZ with Moonriver', () => {
+ // Quartz constants
+ let alice: IKeyringPair;
+ let quartzAssetLocation;
+
+ let randomAccountQuartz: IKeyringPair;
+ let randomAccountMoonriver: IKeyringPair;
+
+ // Moonriver constants
+ let assetId: string;
+
+ const quartzAssetMetadata = {
+ name: 'xcQuartz',
+ symbol: 'xcQTZ',
+ decimals: 18,
+ isFrozen: false,
+ minimalBalance: 1n,
+ };
+
+ let balanceQuartzTokenInit: bigint;
+ let balanceQuartzTokenMiddle: bigint;
+ let balanceQuartzTokenFinal: bigint;
+ let balanceForeignQtzTokenInit: bigint;
+ let balanceForeignQtzTokenMiddle: bigint;
+ let balanceForeignQtzTokenFinal: bigint;
+ let balanceMovrTokenInit: bigint;
+ let balanceMovrTokenMiddle: bigint;
+ let balanceMovrTokenFinal: bigint;
+
+ before(async () => {
+ await usingPlaygrounds(async (helper, privateKey) => {
+ alice = await privateKey('//Alice');
+ [randomAccountQuartz] = await helper.arrange.createAccounts([0n], alice);
+
+ balanceForeignQtzTokenInit = 0n;
+
+ // Set the default version to wrap the first message to other chains.
+ await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);
+ });
+
+ await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {
+ const alithAccount = helper.account.alithAccount();
+ const baltatharAccount = helper.account.baltatharAccount();
+ const dorothyAccount = helper.account.dorothyAccount();
+
+ randomAccountMoonriver = helper.account.create();
+
+ // >>> Sponsoring Dorothy >>>
+ console.log('Sponsoring Dorothy.......');
+ await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);
+ console.log('Sponsoring Dorothy.......DONE');
+ // <<< Sponsoring Dorothy <<<
+
+ quartzAssetLocation = {
+ XCM: {
+ parents: 1,
+ interior: {X1: {Parachain: QUARTZ_CHAIN}},
+ },
+ };
+ const existentialDeposit = 1n;
+ const isSufficient = true;
+ const unitsPerSecond = 1n;
+ const numAssetsWeightHint = 0;
+
+ const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({
+ location: quartzAssetLocation,
+ metadata: quartzAssetMetadata,
+ existentialDeposit,
+ isSufficient,
+ unitsPerSecond,
+ numAssetsWeightHint,
+ });
+
+ console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);
+
+ await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);
+
+ // >>> Acquire Quartz AssetId Info on Moonriver >>>
+ console.log('Acquire Quartz AssetId Info on Moonriver.......');
+
+ assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();
+
+ console.log('QTZ asset ID is %s', assetId);
+ console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');
+ // >>> Acquire Quartz AssetId Info on Moonriver >>>
+
+ // >>> Sponsoring random Account >>>
+ console.log('Sponsoring random Account.......');
+ await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);
+ console.log('Sponsoring random Account.......DONE');
+ // <<< Sponsoring random Account <<<
+
+ balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);
+ });
+
+ await usingPlaygrounds(async (helper) => {
+ await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);
+ balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);
+ });
+ });
+
+ itSub('Should connect and send QTZ to Moonriver', async () => {
+ await testHelper.sendUnqTo('moonriver', randomAccountQuartz, randomAccountMoonriver);
+ });
+
+ itSub('Should connect to Moonriver and send QTZ back', async () => {
+ await testHelper.sendUnqBack('moonriver', alice, randomAccountQuartz);
+ });
+
+ itSub('Moonriver can send only up to its balance', async () => {
+ await testHelper.sendOnlyOwnedBalance('moonriver', alice);
+ });
+
+ itSub('Should not accept reserve transfer of QTZ from Moonriver', async () => {
+ await testHelper.reserveTransferUNQfrom('moonriver', alice);
+ });
+});
+
+describeXCM('[XCMLL] Integration test: Exchanging tokens with Shiden', () => {
+ let alice: IKeyringPair;
+ let sender: IKeyringPair;
+
+ const QTZ_ASSET_ID_ON_SHIDEN = 1;
+ const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n;
+
+ // Quartz -> Shiden
+ const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); // 1 SHD, existential deposit required to actually create the account on Shiden
+ const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?
+ const qtzToShidenTransferred = 10n * (10n ** QTZ_DECIMALS); // 10 QTZ
+ const qtzToShidenArrived = 9_999_999_999_088_000_000n; // 9.999 ... QTZ, Shiden takes a commision in foreign tokens
+
+ // Shiden -> Quartz
+ const qtzFromShidenTransfered = 5n * (10n ** QTZ_DECIMALS); // 5 QTZ
+ const qtzOnShidenLeft = qtzToShidenArrived - qtzFromShidenTransfered; // 4.999_999_999_088_000_000n QTZ
+
+ let balanceAfterQuartzToShidenXCM: bigint;
+
+ before(async () => {
+ await usingPlaygrounds(async (helper, privateKey) => {
+ alice = await privateKey('//Alice');
+ [sender] = await helper.arrange.createAccounts([100n], alice);
+ console.log('sender', sender.address);
+
+ // Set the default version to wrap the first message to other chains.
+ await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);
+ });
+
+ await usingShidenPlaygrounds(shidenUrl, async (helper) => {
+ console.log('1. Create foreign asset and metadata');
+ // TODO update metadata with values from production
+ await helper.assets.create(
+ alice,
+ QTZ_ASSET_ID_ON_SHIDEN,
+ alice.address,
+ QTZ_MINIMAL_BALANCE_ON_SHIDEN,
+ );
+
+ await helper.assets.setMetadata(
+ alice,
+ QTZ_ASSET_ID_ON_SHIDEN,
+ 'Cross chain QTZ',
+ 'xcQTZ',
+ Number(QTZ_DECIMALS),
+ );
+
+ console.log('2. Register asset location on Shiden');
+ const assetLocation = {
+ V2: {
+ parents: 1,
+ interior: {
+ X1: {
+ Parachain: QUARTZ_CHAIN,
+ },
+ },
+ },
+ };
+
+ await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]);
+
+ console.log('3. Set QTZ payment for XCM execution on Shiden');
+ await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);
+
+ console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)');
+ await helper.balance.transferToSubstrate(alice, sender.address, shidenInitialBalance);
+ });
+ });
+
+ itSub('Should connect and send QTZ to Shiden', async () => {
+ await testHelper.sendUnqTo('shiden', sender);
+ });
+
+ itSub('Should connect to Shiden and send QTZ back', async () => {
+ await testHelper.sendUnqBack('shiden', alice, sender);
+ });
+
+ itSub('Shiden can send only up to its balance', async () => {
+ await testHelper.sendOnlyOwnedBalance('shiden', alice);
+ });
+
+ itSub('Should not accept reserve transfer of QTZ from Shiden', async () => {
+ await testHelper.reserveTransferUNQfrom('shiden', alice);
+ });
+});
tests/src/xcm/lowLevelXcmUnique.test.tsdiffbeforeafterboth--- a/tests/src/xcm/lowLevelXcmUnique.test.ts
+++ b/tests/src/xcm/lowLevelXcmUnique.test.ts
@@ -20,7 +20,7 @@
import {Event} from '../util/playgrounds/unique.dev';
import {nToBigInt} from '@polkadot/util';
import {hexToString} from '@polkadot/util';
-import {ASTAR_DECIMALS, NETWORKS, SAFE_XCM_VERSION, UNIQUE_CHAIN, UNQ_DECIMALS, acalaUrl, astarUrl, expectFailedToTransact, expectUntrustedReserveLocationFail, getDevPlayground, mapToChainId, mapToChainUrl, maxWaitBlocks, moonbeamUrl, polkadexUrl, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types';
+import {ASTAR_DECIMALS, NETWORKS, SAFE_XCM_VERSION, UNIQUE_CHAIN, UNQ_DECIMALS, XcmTestHelper, acalaUrl, astarUrl, expectFailedToTransact, expectUntrustedReserveLocationFail, getDevPlayground, mapToChainId, mapToChainUrl, maxWaitBlocks, moonbeamUrl, polkadexUrl, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types';
const TRANSFER_AMOUNT = 2000000_000_000_000_000_000_000n;
@@ -34,6 +34,7 @@
let balanceUniqueTokenFinal: bigint;
let unqFees: bigint;
+const testHelper = new XcmTestHelper('unique');
async function genericSendUnqTo(
networkName: keyof typeof NETWORKS,
@@ -434,20 +435,20 @@
});
itSub('Should connect and send UNQ to Polkadex', async () => {
- await genericSendUnqTo('polkadex', randomAccount);
+ await testHelper.sendUnqTo('polkadex', randomAccount);
});
itSub('Should connect to Polkadex and send UNQ back', async () => {
- await genericSendUnqBack('polkadex', alice, randomAccount);
+ await testHelper.sendUnqBack('polkadex', alice, randomAccount);
});
itSub('Polkadex can send only up to its balance', async () => {
- await genericSendOnlyOwnedBalance('polkadex', alice);
+ await testHelper.sendOnlyOwnedBalance('polkadex', alice);
});
itSub('Should not accept reserve transfer of UNQ from Polkadex', async () => {
- await genericReserveTransferUNQfrom('polkadex', alice);
+ await testHelper.reserveTransferUNQfrom('polkadex', alice);
});
});
@@ -574,19 +575,19 @@
});
itSub('Should connect and send UNQ to Moonbeam', async () => {
- await genericSendUnqTo('moonbeam', randomAccountUnique, randomAccountMoonbeam);
+ await testHelper.sendUnqTo('moonbeam', randomAccountUnique, randomAccountMoonbeam);
});
itSub('Should connect to Moonbeam and send UNQ back', async () => {
- await genericSendUnqBack('moonbeam', alice, randomAccountUnique);
+ await testHelper.sendUnqBack('moonbeam', alice, randomAccountUnique);
});
itSub('Moonbeam can send only up to its balance', async () => {
- await genericSendOnlyOwnedBalance('moonbeam', alice);
+ await testHelper.sendOnlyOwnedBalance('moonbeam', alice);
});
itSub('Should not accept reserve transfer of UNQ from Moonbeam', async () => {
- await genericReserveTransferUNQfrom('moonbeam', alice);
+ await testHelper.reserveTransferUNQfrom('moonbeam', alice);
});
});
tests/src/xcm/xcm.types.tsdiffbeforeafterboth1import {usingAcalaPlaygrounds, usingAstarPlaygrounds, usingMoonbeamPlaygrounds, usingPolkadexPlaygrounds} from '../util';2import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';3import config from '../config';45export const UNIQUE_CHAIN = +(process.env.RELAY_UNIQUE_ID || 2037);6export const STATEMINT_CHAIN = +(process.env.RELAY_STATEMINT_ID || 1000);7export const ACALA_CHAIN = +(process.env.RELAY_ACALA_ID || 2000);8export const MOONBEAM_CHAIN = +(process.env.RELAY_MOONBEAM_ID || 2004);9export const ASTAR_CHAIN = +(process.env.RELAY_ASTAR_ID || 2006);10export const POLKADEX_CHAIN = +(process.env.RELAY_POLKADEX_ID || 2040);1112export const acalaUrl = config.acalaUrl;13export const moonbeamUrl = config.moonbeamUrl;14export const astarUrl = config.astarUrl;15export const polkadexUrl = config.polkadexUrl;1617export const SAFE_XCM_VERSION = 3;1819export const maxWaitBlocks = 6;202122export const ASTAR_DECIMALS = 18n;23export const UNQ_DECIMALS = 18n;2425export const uniqueMultilocation = {26 parents: 1,27 interior: {28 X1: {29 Parachain: UNIQUE_CHAIN,30 },31 },32};33export const uniqueVersionedMultilocation = {34 V3: uniqueMultilocation,35};3637export const uniqueAssetId = {38 Concrete: uniqueMultilocation,39};4041export const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {42 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash43 && event.outcome.isFailedToTransactAsset);44};45export const expectUntrustedReserveLocationFail = async (helper: DevUniqueHelper, messageSent: any) => {46 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash47 && event.outcome.isUntrustedReserveLocation);48};4950export const NETWORKS = {51 acala: usingAcalaPlaygrounds,52 astar: usingAstarPlaygrounds,53 polkadex: usingPolkadexPlaygrounds,54 moonbeam: usingMoonbeamPlaygrounds,55} as const;5657export function mapToChainId(networkName: keyof typeof NETWORKS) {58 switch (networkName) {59 case 'acala':60 return ACALA_CHAIN;61 case 'astar':62 return ASTAR_CHAIN;63 case 'moonbeam':64 return MOONBEAM_CHAIN;65 case 'polkadex':66 return POLKADEX_CHAIN;67 }68}6970export function mapToChainUrl(networkName: keyof typeof NETWORKS): string {71 switch (networkName) {72 case 'acala':73 return acalaUrl;74 case 'astar':75 return astarUrl;76 case 'moonbeam':77 return moonbeamUrl;78 case 'polkadex':79 return polkadexUrl;80 }81}8283export function getDevPlayground<T extends keyof typeof NETWORKS>(name: T) {84 return NETWORKS[name];85}1import {IKeyringPair} from '@polkadot/types/types';2import {expect, usingAcalaPlaygrounds, usingAstarPlaygrounds, usingKaruraPlaygrounds, usingMoonbeamPlaygrounds, usingMoonriverPlaygrounds, usingPlaygrounds, usingPolkadexPlaygrounds, usingShidenPlaygrounds} from '../util';3import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';4import config from '../config';56export const UNIQUE_CHAIN = +(process.env.RELAY_UNIQUE_ID || 2037);7export const STATEMINT_CHAIN = +(process.env.RELAY_STATEMINT_ID || 1000);8export const ACALA_CHAIN = +(process.env.RELAY_ACALA_ID || 2000);9export const MOONBEAM_CHAIN = +(process.env.RELAY_MOONBEAM_ID || 2004);10export const ASTAR_CHAIN = +(process.env.RELAY_ASTAR_ID || 2006);11export const POLKADEX_CHAIN = +(process.env.RELAY_POLKADEX_ID || 2040);1213export const QUARTZ_CHAIN = +(process.env.RELAY_QUARTZ_ID || 2095);14export const STATEMINE_CHAIN = +(process.env.RELAY_STATEMINE_ID || 1000);15export const KARURA_CHAIN = +(process.env.RELAY_KARURA_ID || 2000);16export const MOONRIVER_CHAIN = +(process.env.RELAY_MOONRIVER_ID || 2023);17export const SHIDEN_CHAIN = +(process.env.RELAY_SHIDEN_ID || 2007);1819export const relayUrl = config.relayUrl;20export const statemintUrl = config.statemintUrl;21export const statemineUrl = config.statemineUrl;2223export const acalaUrl = config.acalaUrl;24export const moonbeamUrl = config.moonbeamUrl;25export const astarUrl = config.astarUrl;26export const polkadexUrl = config.polkadexUrl;2728export const karuraUrl = config.karuraUrl;29export const moonriverUrl = config.moonriverUrl;30export const shidenUrl = config.shidenUrl;3132export const SAFE_XCM_VERSION = 3;333435export const RELAY_DECIMALS = 12;36export const STATEMINE_DECIMALS = 12;37export const KARURA_DECIMALS = 12;38export const SHIDEN_DECIMALS = 18n;39export const QTZ_DECIMALS = 18n;4041export const ASTAR_DECIMALS = 18n;42export const UNQ_DECIMALS = 18n;4344export const maxWaitBlocks = 6;4546export const uniqueMultilocation = {47 parents: 1,48 interior: {49 X1: {50 Parachain: UNIQUE_CHAIN,51 },52 },53};54export const uniqueVersionedMultilocation = {55 V3: uniqueMultilocation,56};5758export const uniqueAssetId = {59 Concrete: uniqueMultilocation,60};6162export const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {63 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash64 && event.outcome.isFailedToTransactAsset);65};66export const expectUntrustedReserveLocationFail = async (helper: DevUniqueHelper, messageSent: any) => {67 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash68 && event.outcome.isUntrustedReserveLocation);69};7071export const NETWORKS = {72 acala: usingAcalaPlaygrounds,73 astar: usingAstarPlaygrounds,74 polkadex: usingPolkadexPlaygrounds,75 moonbeam: usingMoonbeamPlaygrounds,76 moonriver: usingMoonriverPlaygrounds,77 karura: usingKaruraPlaygrounds,78 shiden: usingShidenPlaygrounds,79} as const;80type NetworkNames = keyof typeof NETWORKS;8182type NativeRuntime = 'opal' | 'quartz' | 'unique';8384export function mapToChainId(networkName: keyof typeof NETWORKS): number {85 switch (networkName) {86 case 'acala':87 return ACALA_CHAIN;88 case 'astar':89 return ASTAR_CHAIN;90 case 'moonbeam':91 return MOONBEAM_CHAIN;92 case 'polkadex':93 return POLKADEX_CHAIN;94 case 'moonriver':95 return MOONRIVER_CHAIN;96 case 'karura':97 return KARURA_CHAIN;98 case 'shiden':99 return SHIDEN_CHAIN;100 }101}102103export function mapToChainUrl(networkName: NetworkNames): string {104 switch (networkName) {105 case 'acala':106 return acalaUrl;107 case 'astar':108 return astarUrl;109 case 'moonbeam':110 return moonbeamUrl;111 case 'polkadex':112 return polkadexUrl;113 case 'moonriver':114 return moonriverUrl;115 case 'karura':116 return karuraUrl;117 case 'shiden':118 return shidenUrl;119 }120}121122export function getDevPlayground(name: NetworkNames) {123 return NETWORKS[name];124}125126export const TRANSFER_AMOUNT = 2000000_000_000_000_000_000_000n;127const SENDER_BUDGET = 2n * TRANSFER_AMOUNT;128const SENDBACK_AMOUNT = TRANSFER_AMOUNT / 2n;129const STAYED_ON_TARGET_CHAIN = TRANSFER_AMOUNT - SENDBACK_AMOUNT;130const TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT = 100_000_000_000n;131132export class XcmTestHelper {133 private _balanceUniqueTokenInit: bigint = 0n;134 private _balanceUniqueTokenMiddle: bigint = 0n;135 private _balanceUniqueTokenFinal: bigint = 0n;136 private _unqFees: bigint = 0n;137 private _nativeRuntime: NativeRuntime;138139 constructor(runtime: NativeRuntime) {140 this._nativeRuntime = runtime;141 }142143 private _getNativeId() {144 switch (this._nativeRuntime) {145 case 'opal':146 // To-Do147 return 10;148 case 'quartz':149 return QUARTZ_CHAIN;150 case 'unique':151 return UNIQUE_CHAIN;152 }153 }154 private _isAddress20FormatFor(network: NetworkNames) {155 switch (network) {156 case 'moonbeam':157 case 'moonriver':158 return true;159 default:160 return false;161 }162 }163164 async sendUnqTo(165 networkName: keyof typeof NETWORKS,166 randomAccount: IKeyringPair,167 randomAccountOnTargetChain = randomAccount,168 ) {169 const networkUrl = mapToChainUrl(networkName);170 const targetPlayground = getDevPlayground(networkName);171 await usingPlaygrounds(async (helper) => {172 this._balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);173 const destination = {174 V2: {175 parents: 1,176 interior: {177 X1: {178 Parachain: mapToChainId(networkName),179 },180 },181 },182 };183184 const beneficiary = {185 V2: {186 parents: 0,187 interior: {188 X1: (189 this._isAddress20FormatFor(networkName) ?190 {191 AccountKey20: {192 network: 'Any',193 key: randomAccountOnTargetChain.address,194 },195 }196 :197 {198 AccountId32: {199 network: 'Any',200 id: randomAccountOnTargetChain.addressRaw,201 },202 }203 ),204 },205 },206 };207208 const assets = {209 V2: [210 {211 id: {212 Concrete: {213 parents: 0,214 interior: 'Here',215 },216 },217 fun: {218 Fungible: TRANSFER_AMOUNT,219 },220 },221 ],222 };223 const feeAssetItem = 0;224225 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');226 const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);227 this._balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);228229 this._unqFees = this._balanceUniqueTokenInit - this._balanceUniqueTokenMiddle - TRANSFER_AMOUNT;230 console.log('[Unique -> %s] transaction fees on Unique: %s UNQ', networkName, helper.util.bigIntToDecimals(this._unqFees));231 expect(this._unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;232233 await targetPlayground(networkUrl, async (helper) => {234 /*235 Since only the parachain part of the Polkadex236 infrastructure is launched (without their237 solochain validators), processing incoming238 assets will lead to an error.239 This error indicates that the Polkadex chain240 received a message from the Unique network,241 since the hash is being checked to ensure242 it matches what was sent.243 */244 if(networkName == 'polkadex') {245 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);246 } else {247 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == messageSent.messageHash);248 }249 });250251 });252 }253254 async sendUnqBack(255 networkName: keyof typeof NETWORKS,256 sudoer: IKeyringPair,257 randomAccountOnUnq: IKeyringPair,258 ) {259 const networkUrl = mapToChainUrl(networkName);260261 const targetPlayground = getDevPlayground(networkName);262 await usingPlaygrounds(async (helper) => {263264 const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(265 randomAccountOnUnq.addressRaw,266 {267 Concrete: {268 parents: 1,269 interior: {270 X1: {Parachain: this._getNativeId()},271 },272 },273 },274 SENDBACK_AMOUNT,275 );276277 let xcmProgramSent: any;278279280 await targetPlayground(networkUrl, async (helper) => {281 if('getSudo' in helper) {282 await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, xcmProgram);283 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);284 } else if('fastDemocracy' in helper) {285 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, xcmProgram]);286 // Needed to bypass the call filter.287 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);288 await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);289 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);290 }291 });292293 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);294295 this._balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountOnUnq.address);296297 expect(this._balanceUniqueTokenFinal).to.be.equal(this._balanceUniqueTokenInit - this._unqFees - STAYED_ON_TARGET_CHAIN);298299 });300 }301302 async sendOnlyOwnedBalance(303 networkName: keyof typeof NETWORKS,304 sudoer: IKeyringPair,305 ) {306 const networkUrl = mapToChainUrl(networkName);307 const targetPlayground = getDevPlayground(networkName);308309 const targetChainBalance = 10000n * (10n ** UNQ_DECIMALS);310311 await usingPlaygrounds(async (helper) => {312 const targetChainSovereignAccount = helper.address.paraSiblingSovereignAccount(mapToChainId(networkName));313 await helper.getSudo().balance.setBalanceSubstrate(sudoer, targetChainSovereignAccount, targetChainBalance);314 const moreThanTargetChainHas = 2n * targetChainBalance;315316 const targetAccount = helper.arrange.createEmptyAccount();317318 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(319 targetAccount.addressRaw,320 {321 Concrete: {322 parents: 0,323 interior: 'Here',324 },325 },326 moreThanTargetChainHas,327 );328329 let maliciousXcmProgramSent: any;330331332 await targetPlayground(networkUrl, async (helper) => {333 if('getSudo' in helper) {334 await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgram);335 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);336 } else if('fastDemocracy' in helper) {337 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]);338 // Needed to bypass the call filter.339 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);340 await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);341 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);342 }343 });344345 await expectFailedToTransact(helper, maliciousXcmProgramSent);346347 const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);348 expect(targetAccountBalance).to.be.equal(0n);349 });350 }351352 async reserveTransferUNQfrom(netwokrName: keyof typeof NETWORKS, sudoer: IKeyringPair) {353 const networkUrl = mapToChainUrl(netwokrName);354 const targetPlayground = getDevPlayground(netwokrName);355356 await usingPlaygrounds(async (helper) => {357 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);358 const targetAccount = helper.arrange.createEmptyAccount();359360 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(361 targetAccount.addressRaw,362 {363 Concrete: {364 parents: 1,365 interior: {366 X1: {367 Parachain: this._getNativeId(),368 },369 },370 },371 },372 testAmount,373 );374375 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(376 targetAccount.addressRaw,377 {378 Concrete: {379 parents: 0,380 interior: 'Here',381 },382 },383 testAmount,384 );385386 let maliciousXcmProgramFullIdSent: any;387 let maliciousXcmProgramHereIdSent: any;388 const maxWaitBlocks = 3;389390 // Try to trick Unique using full UNQ identification391 await targetPlayground(networkUrl, async (helper) => {392 if('getSudo' in helper) {393 await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramFullId);394 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);395 }396 // Moonbeam case397 else if('fastDemocracy' in helper) {398 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);399 // Needed to bypass the call filter.400 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);401 await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using path asset identification`,batchCall);402403 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);404 }405 });406407408 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);409410 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);411 expect(accountBalance).to.be.equal(0n);412413 // Try to trick Unique using shortened UNQ identification414 await targetPlayground(networkUrl, async (helper) => {415 if('getSudo' in helper) {416 await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramHereId);417 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);418 }419 else if('fastDemocracy' in helper) {420 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]);421 // Needed to bypass the call filter.422 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);423 await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using "here" asset identification`, batchCall);424425 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);426 }427 });428429 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);430431 accountBalance = await helper.balance.getSubstrate(targetAccount.address);432 expect(accountBalance).to.be.equal(0n);433 });434 }435436 async rejectNativeTokensFrom(networkName: keyof typeof NETWORKS, sudoerOnTargetChain: IKeyringPair) {437 const networkUrl = mapToChainUrl(networkName);438 const targetPlayground = getDevPlayground(networkName);439 let messageSent: any;440441 await usingPlaygrounds(async (helper) => {442 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(443 helper.arrange.createEmptyAccount().addressRaw,444 {445 Concrete: {446 parents: 1,447 interior: {448 X1: {449 Parachain: mapToChainId(networkName),450 },451 },452 },453 },454 TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT,455 );456 await targetPlayground(networkUrl, async (helper) => {457 if('getSudo' in helper) {458 await helper.getSudo().xcm.send(sudoerOnTargetChain, uniqueVersionedMultilocation, maliciousXcmProgramFullId);459 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);460 } else if('fastDemocracy' in helper) {461 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);462 // Needed to bypass the call filter.463 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);464 await helper.fastDemocracy.executeProposal(`${networkName} sending native tokens to the Unique via fast democracy`, batchCall);465466 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);467 }468 });469 await expectFailedToTransact(helper, messageSent);470 });471 }472473}tests/src/xcm/xcmQuartz.test.tsdiffbeforeafterboth--- a/tests/src/xcm/xcmQuartz.test.ts
+++ b/tests/src/xcm/xcmQuartz.test.ts
@@ -15,29 +15,13 @@
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
import {IKeyringPair} from '@polkadot/types/types';
-import config from '../config';
import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds, usingShidenPlaygrounds} from '../util';
import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';
+import {STATEMINE_CHAIN, QUARTZ_CHAIN, KARURA_CHAIN, MOONRIVER_CHAIN, SHIDEN_CHAIN, STATEMINE_DECIMALS, KARURA_DECIMALS, QTZ_DECIMALS, RELAY_DECIMALS, SHIDEN_DECIMALS, karuraUrl, moonriverUrl, relayUrl, shidenUrl, statemineUrl} from './xcm.types';
+
-const QUARTZ_CHAIN = +(process.env.RELAY_QUARTZ_ID || 2095);
-const STATEMINE_CHAIN = +(process.env.RELAY_STATEMINE_ID || 1000);
-const KARURA_CHAIN = +(process.env.RELAY_KARURA_ID || 2000);
-const MOONRIVER_CHAIN = +(process.env.RELAY_MOONRIVER_ID || 2023);
-const SHIDEN_CHAIN = +(process.env.RELAY_SHIDEN_ID || 2007);
const STATEMINE_PALLET_INSTANCE = 50;
-
-const relayUrl = config.relayUrl;
-const statemineUrl = config.statemineUrl;
-const karuraUrl = config.karuraUrl;
-const moonriverUrl = config.moonriverUrl;
-const shidenUrl = config.shidenUrl;
-
-const RELAY_DECIMALS = 12;
-const STATEMINE_DECIMALS = 12;
-const KARURA_DECIMALS = 12;
-const SHIDEN_DECIMALS = 18n;
-const QTZ_DECIMALS = 18n;
const TRANSFER_AMOUNT = 2000000000000000000000000n;