git.delta.rocks / unique-network / refs/commits / 61fc26e2c8d2

difftreelog

test(xcm qtz) added `XcmTestHelper`

PraetorP2023-09-28parent: #dc6f0e2.patch.diff
in: master

5 files changed

modifiedtests/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",
addedtests/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);
+  });
+});
modifiedtests/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);
   });
 });
 
modifiedtests/src/xcm/xcm.types.tsdiffbeforeafterboth
before · tests/src/xcm/xcm.types.ts
1import {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}
after · tests/src/xcm/xcm.types.ts
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}
modifiedtests/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;