git.delta.rocks / unique-network / refs/commits / 668280b1206d

difftreelog

feat(xcm tests) `XcmHelper` integraion for QTZ\UNQ

PraetorP2023-09-29parent: #06b395f.patch.diff
in: master

6 files changed

modified.baedeker/.gitignorediffbeforeafterboth
--- a/.baedeker/.gitignore
+++ b/.baedeker/.gitignore
@@ -1,4 +1,5 @@
 /.bdk-env
-/rewrites.jsonnet
+/rewrites*.jsonnet
 /vendor
 /baedeker-library
+!/rewrites.example.jsonnet
\ No newline at end of file
modified.github/workflows/xcm.ymldiffbeforeafterboth
--- a/.github/workflows/xcm.yml
+++ b/.github/workflows/xcm.yml
@@ -38,7 +38,7 @@
         with:
           matrix: |
             network {opal}, relay_branch {${{ env.UNIQUEWEST_MAINNET_BRANCH }}}, acala_version {${{ env.ACALA_BUILD_BRANCH }}}, moonbeam_version {${{ env.MOONBEAM_BUILD_BRANCH }}}, cumulus_version {${{ env.WESTMINT_BUILD_BRANCH }}}, astar_version {${{ env.ASTAR_BUILD_BRANCH }}}, polkadex_version {${{ env.POLKADEX_BUILD_BRANCH }}}, runtest {testXcmOpal}, runtime_features {opal-runtime}
-            network {quartz}, relay_branch {${{ env.KUSAMA_MAINNET_BRANCH }}}, acala_version {${{ env.KARURA_BUILD_BRANCH }}}, moonbeam_version {${{ env.MOONRIVER_BUILD_BRANCH }}}, cumulus_version {${{ env.STATEMINE_BUILD_BRANCH }}}, astar_version {${{ env.SHIDEN_BUILD_BRANCH }}}, polkadex_version {${{ env.POLKADEX_BUILD_BRANCH }}}, runtest {testXcmQuartz}, runtime_features {quartz-runtime}
+            network {quartz}, relay_branch {${{ env.KUSAMA_MAINNET_BRANCH }}}, acala_version {${{ env.KARURA_BUILD_BRANCH }}}, moonbeam_version {${{ env.MOONRIVER_BUILD_BRANCH }}}, cumulus_version {${{ env.STATEMINE_BUILD_BRANCH }}}, astar_version {${{ env.SHIDEN_BUILD_BRANCH }}}, polkadex_version {${{ env.POLKADEX_BUILD_BRANCH }}}, runtest {testFullXcmQuartz}, runtime_features {quartz-runtime}
             network {unique}, relay_branch {${{ env.POLKADOT_MAINNET_BRANCH }}}, acala_version {${{ env.ACALA_BUILD_BRANCH }}}, moonbeam_version {${{ env.MOONBEAM_BUILD_BRANCH }}}, cumulus_version {${{ env.STATEMINT_BUILD_BRANCH }}}, astar_version {${{ env.ASTAR_BUILD_BRANCH }}}, polkadex_version {${{ env.POLKADEX_BUILD_BRANCH }}}, runtest {testFullXcmUnique}, runtime_features {unique-runtime}
 
   xcm:
modifiedtests/src/xcm/lowLevelXcmQuartz.test.tsdiffbeforeafterboth
--- a/tests/src/xcm/lowLevelXcmQuartz.test.ts
+++ b/tests/src/xcm/lowLevelXcmQuartz.test.ts
@@ -15,35 +15,16 @@
 // 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, usingRelayPlaygrounds} 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';
-
+import {itSub, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds,  usingMoonriverPlaygrounds, usingShidenPlaygrounds, usingRelayPlaygrounds} from '../util';
+import {QUARTZ_CHAIN,  QTZ_DECIMALS,  SHIDEN_DECIMALS, karuraUrl, moonriverUrl,  shidenUrl,  SAFE_XCM_VERSION, XcmTestHelper, TRANSFER_AMOUNT, SENDER_BUDGET, relayUrl} from './xcm.types';
+import {hexToString} from '@polkadot/util';
 
 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');
@@ -72,15 +53,19 @@
         minimalBalance: 1000000000000000000n,
       };
 
-      await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);
+      const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v]: [any, any]) =>
+        hexToString(v.toJSON()['symbol'])) as string[];
+
+      if(!assets.includes('QTZ')) {
+        await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);
+      } else {
+        console.log('QTZ token already registered on Karura assetRegistry pallet');
+      }
       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);
+      await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);
     });
   });
 
@@ -100,67 +85,17 @@
 // 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 () => {
@@ -195,22 +130,12 @@
     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);
@@ -239,20 +164,22 @@
       const isSufficient = true;
       const unitsPerSecond = 1n;
       const numAssetsWeightHint = 0;
+      if((await helper.assetManager.assetTypeId(quartzAssetLocation)).toJSON()) {
+        console.log('Quartz asset already registered on Moonriver');
+      } else {
+        const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({
+          location: quartzAssetLocation,
+          metadata: quartzAssetMetadata,
+          existentialDeposit,
+          isSufficient,
+          unitsPerSecond,
+          numAssetsWeightHint,
+        });
 
-      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);
+        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.......');
 
@@ -267,13 +194,10 @@
       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);
     });
   });
 
@@ -296,7 +220,7 @@
 
 describeXCM('[XCMLL] Integration test: Exchanging tokens with Shiden', () => {
   let alice: IKeyringPair;
-  let sender: IKeyringPair;
+  let randomAccount: IKeyringPair;
 
   const QTZ_ASSET_ID_ON_SHIDEN = 1;
   const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n;
@@ -304,71 +228,69 @@
   // 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);
+      randomAccount = helper.arrange.createEmptyAccount();
+      await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);
+      console.log('sender: ', randomAccount.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,
-      );
+      if(!(await helper.callRpc('api.query.assets.asset', [QTZ_ASSET_ID_ON_SHIDEN])).toJSON()) {
+        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),
-      );
+        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,
+        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]);
+        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]);
+      } else {
+        console.log('QTZ is already registered on Shiden');
+      }
       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);
+      await helper.balance.transferToSubstrate(alice, randomAccount.address, shidenInitialBalance);
     });
   });
 
   itSub('Should connect and send QTZ to Shiden', async () => {
-    await testHelper.sendUnqTo('shiden', sender);
+    await testHelper.sendUnqTo('shiden', randomAccount);
   });
 
   itSub('Should connect to Shiden and send QTZ back', async () => {
-    await testHelper.sendUnqBack('shiden', alice, sender);
+    await testHelper.sendUnqBack('shiden', alice, randomAccount);
   });
 
   itSub('Shiden can send only up to its balance', async () => {
modifiedtests/src/xcm/lowLevelXcmUnique.test.tsdiffbeforeafterboth
before · tests/src/xcm/lowLevelXcmUnique.test.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {IKeyringPair} from '@polkadot/types/types';18import config from '../config';19import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds, requirePalletsOrSkip, Pallets} from '../util';20import {Event} from '../util/playgrounds/unique.dev';21import {nToBigInt} from '@polkadot/util';22import {hexToString} from '@polkadot/util';23import {ASTAR_DECIMALS, NETWORKS, SAFE_XCM_VERSION, UNIQUE_CHAIN, UNQ_DECIMALS, XcmTestHelper, acalaUrl, astarUrl, expectFailedToTransact, expectUntrustedReserveLocationFail, getDevPlayground, mapToChainId, mapToChainUrl, maxWaitBlocks, moonbeamUrl, polkadexUrl, relayUrl, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types';242526const TRANSFER_AMOUNT = 2000000_000_000_000_000_000_000n;27const SENDER_BUDGET = 2n * TRANSFER_AMOUNT;28const SENDBACK_AMOUNT = TRANSFER_AMOUNT / 2n;29const STAYED_ON_TARGET_CHAIN = TRANSFER_AMOUNT - SENDBACK_AMOUNT;30const TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT = 100_000_000_000n;3132let balanceUniqueTokenInit: bigint;33let balanceUniqueTokenMiddle: bigint;34let balanceUniqueTokenFinal: bigint;35let unqFees: bigint;3637const testHelper = new XcmTestHelper('unique');3839async function genericSendUnqTo(40  networkName: keyof typeof NETWORKS,41  randomAccount: IKeyringPair,42  randomAccountOnTargetChain = randomAccount,43) {44  const networkUrl = mapToChainUrl(networkName);45  const targetPlayground = getDevPlayground(networkName);46  await usingPlaygrounds(async (helper) => {47    balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);48    const destination = {49      V2: {50        parents: 1,51        interior: {52          X1: {53            Parachain: mapToChainId(networkName),54          },55        },56      },57    };5859    const beneficiary = {60      V2: {61        parents: 0,62        interior: {63          X1: (64            networkName == 'moonbeam' ?65              {66                AccountKey20: {67                  network: 'Any',68                  key: randomAccountOnTargetChain.address,69                },70              }71              :72              {73                AccountId32: {74                  network: 'Any',75                  id: randomAccountOnTargetChain.addressRaw,76                },77              }78          ),79        },80      },81    };8283    const assets = {84      V2: [85        {86          id: {87            Concrete: {88              parents: 0,89              interior: 'Here',90            },91          },92          fun: {93            Fungible: TRANSFER_AMOUNT,94          },95        },96      ],97    };98    const feeAssetItem = 0;99100    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');101    const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);102    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);103104    unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;105    console.log('[Unique -> %s] transaction fees on Unique: %s UNQ', networkName, helper.util.bigIntToDecimals(unqFees));106    expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;107108    await targetPlayground(networkUrl, async (helper) => {109      /*110        Since only the parachain part of the Polkadex111        infrastructure is launched (without their112        solochain validators), processing incoming113        assets will lead to an error.114        This error indicates that the Polkadex chain115        received a message from the Unique network,116        since the hash is being checked to ensure117        it matches what was sent.118      */119      if(networkName == 'polkadex') {120        await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);121      } else {122        await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == messageSent.messageHash);123      }124    });125126  });127}128129async function genericSendUnqBack(130  networkName: keyof typeof NETWORKS,131  sudoer: IKeyringPair,132  randomAccountOnUnq: IKeyringPair,133) {134  const networkUrl = mapToChainUrl(networkName);135136  const targetPlayground = getDevPlayground(networkName);137  await usingPlaygrounds(async (helper) => {138139    const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(140      randomAccountOnUnq.addressRaw,141      uniqueAssetId,142      SENDBACK_AMOUNT,143    );144145    let xcmProgramSent: any;146147148    await targetPlayground(networkUrl, async (helper) => {149      if('getSudo' in helper) {150        await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, xcmProgram);151        xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);152      } else if('fastDemocracy' in helper) {153        const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, xcmProgram]);154        // Needed to bypass the call filter.155        const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);156        await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);157        xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);158      }159    });160161    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);162163    balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountOnUnq.address);164165    expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees - STAYED_ON_TARGET_CHAIN);166167  });168}169170async function genericSendOnlyOwnedBalance(171  networkName: keyof typeof NETWORKS,172  sudoer: IKeyringPair,173) {174  const networkUrl = mapToChainUrl(networkName);175  const targetPlayground = getDevPlayground(networkName);176177  const targetChainBalance = 10000n * (10n ** UNQ_DECIMALS);178179  await usingPlaygrounds(async (helper) => {180    const targetChainSovereignAccount = helper.address.paraSiblingSovereignAccount(mapToChainId(networkName));181    await helper.getSudo().balance.setBalanceSubstrate(sudoer, targetChainSovereignAccount, targetChainBalance);182    const moreThanTargetChainHas = 2n * targetChainBalance;183184    const targetAccount = helper.arrange.createEmptyAccount();185186    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(187      targetAccount.addressRaw,188      {189        Concrete: {190          parents: 0,191          interior: 'Here',192        },193      },194      moreThanTargetChainHas,195    );196197    let maliciousXcmProgramSent: any;198199200    await targetPlayground(networkUrl, async (helper) => {201      if('getSudo' in helper) {202        await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgram);203        maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);204      } else if('fastDemocracy' in helper) {205        const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]);206        // Needed to bypass the call filter.207        const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);208        await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);209        maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);210      }211    });212213    await expectFailedToTransact(helper, maliciousXcmProgramSent);214215    const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);216    expect(targetAccountBalance).to.be.equal(0n);217  });218}219220async function genericReserveTransferUNQfrom(netwokrName: keyof typeof NETWORKS, sudoer: IKeyringPair) {221  const networkUrl = mapToChainUrl(netwokrName);222  const targetPlayground = getDevPlayground(netwokrName);223224  await usingPlaygrounds(async (helper) => {225    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);226    const targetAccount = helper.arrange.createEmptyAccount();227228    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(229      targetAccount.addressRaw,230      uniqueAssetId,231      testAmount,232    );233234    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(235      targetAccount.addressRaw,236      {237        Concrete: {238          parents: 0,239          interior: 'Here',240        },241      },242      testAmount,243    );244245    let maliciousXcmProgramFullIdSent: any;246    let maliciousXcmProgramHereIdSent: any;247    const maxWaitBlocks = 3;248249    // Try to trick Unique using full UNQ identification250    await targetPlayground(networkUrl, async (helper) => {251      if('getSudo' in helper) {252        await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramFullId);253        maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);254      }255      // Moonbeam case256      else if('fastDemocracy' in helper) {257        const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);258        // Needed to bypass the call filter.259        const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);260        await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using path asset identification`,batchCall);261262        maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);263      }264    });265266267    await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);268269    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);270    expect(accountBalance).to.be.equal(0n);271272    // Try to trick Unique using shortened UNQ identification273    await targetPlayground(networkUrl, async (helper) => {274      if('getSudo' in helper) {275        await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramHereId);276        maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);277      }278      else if('fastDemocracy' in helper) {279        const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]);280        // Needed to bypass the call filter.281        const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);282        await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using "here" asset identification`, batchCall);283284        maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);285      }286    });287288    await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);289290    accountBalance = await helper.balance.getSubstrate(targetAccount.address);291    expect(accountBalance).to.be.equal(0n);292  });293}294295async function genericRejectNativeTokensFrom(networkName: keyof typeof NETWORKS, sudoerOnTargetChain: IKeyringPair) {296  const networkUrl = mapToChainUrl(networkName);297  const targetPlayground = getDevPlayground(networkName);298  let messageSent: any;299300  await usingPlaygrounds(async (helper) => {301    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(302      helper.arrange.createEmptyAccount().addressRaw,303      {304        Concrete: {305          parents: 1,306          interior: {307            X1: {308              Parachain: mapToChainId(networkName),309            },310          },311        },312      },313      TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT,314    );315    await targetPlayground(networkUrl, async (helper) => {316      if('getSudo' in helper) {317        await helper.getSudo().xcm.send(sudoerOnTargetChain, uniqueVersionedMultilocation, maliciousXcmProgramFullId);318        messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);319      } else if('fastDemocracy' in helper) {320        const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);321        // Needed to bypass the call filter.322        const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);323        await helper.fastDemocracy.executeProposal(`${networkName} sending native tokens to the Unique via fast democracy`, batchCall);324325        messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);326      }327    });328    await expectFailedToTransact(helper, messageSent);329  });330}331332333describeXCM('[XCMLL] Integration test: Exchanging tokens with Acala', () => {334  let alice: IKeyringPair;335  let randomAccount: IKeyringPair;336337  before(async () => {338    await usingPlaygrounds(async (helper, privateKey) => {339      alice = await privateKey('//Alice');340      console.log(config.acalaUrl);341      randomAccount = helper.arrange.createEmptyAccount();342343      // Set the default version to wrap the first message to other chains.344      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);345    });346347    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {348      const destination = {349        V2: {350          parents: 1,351          interior: {352            X1: {353              Parachain: UNIQUE_CHAIN,354            },355          },356        },357      };358359      const metadata = {360        name: 'Unique Network',361        symbol: 'UNQ',362        decimals: 18,363        minimalBalance: 1250_000_000_000_000_000n,364      };365      const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v] : [any, any]) =>366        hexToString(v.toJSON()['symbol'])) as string[];367368      if(!assets.includes('UNQ')) {369        await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);370      } else {371        console.log('UNQ token already registered on Acala assetRegistry pallet');372      }373      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);374    });375376    await usingPlaygrounds(async (helper) => {377      await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);378      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);379    });380  });381382  itSub('Should connect and send UNQ to Acala', async () => {383    await genericSendUnqTo('acala', randomAccount);384  });385386  itSub('Should connect to Acala and send UNQ back', async () => {387    await genericSendUnqBack('acala', alice, randomAccount);388  });389390  itSub('Acala can send only up to its balance', async () => {391    await genericSendOnlyOwnedBalance('acala', alice);392  });393394  itSub('Should not accept reserve transfer of UNQ from Acala', async () => {395    await genericReserveTransferUNQfrom('acala', alice);396  });397});398399describeXCM('[XCMLL] Integration test: Exchanging tokens with Polkadex', () => {400  let alice: IKeyringPair;401  let randomAccount: IKeyringPair;402403  before(async () => {404    await usingPlaygrounds(async (helper, privateKey) => {405      alice = await privateKey('//Alice');406      randomAccount = helper.arrange.createEmptyAccount();407408      // Set the default version to wrap the first message to other chains.409      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);410    });411412    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {413      const isWhitelisted = ((await helper.callRpc('api.query.xcmHelper.whitelistedTokens', []))414        .toJSON() as [])415        .map(nToBigInt).length != 0;416      /*417      Check whether the Unique token has been added418      to the whitelist, since an error will occur419      if it is added again. Needed for debugging420      when this test is run multiple times.421      */422      if(isWhitelisted) {423        console.log('UNQ token is already whitelisted on Polkadex');424      } else {425        await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);426      }427428      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);429    });430431    await usingPlaygrounds(async (helper) => {432      await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);433      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);434    });435  });436437  itSub('Should connect and send UNQ to Polkadex', async () => {438    await testHelper.sendUnqTo('polkadex', randomAccount);439  });440441442  itSub('Should connect to Polkadex and send UNQ back', async () => {443    await testHelper.sendUnqBack('polkadex', alice, randomAccount);444  });445446  itSub('Polkadex can send only up to its balance', async () => {447    await testHelper.sendOnlyOwnedBalance('polkadex', alice);448  });449450  itSub('Should not accept reserve transfer of UNQ from Polkadex', async () => {451    await testHelper.reserveTransferUNQfrom('polkadex', alice);452  });453});454455// These tests are relevant only when456// the the corresponding foreign assets are not registered457describeXCM('[XCMLL] Integration test: Unique rejects non-native tokens', () => {458  let alice: IKeyringPair;459460  before(async () => {461    await usingPlaygrounds(async (helper, privateKey) => {462      alice = await privateKey('//Alice');463464      // Set the default version to wrap the first message to other chains.465      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);466    });467  });468469  itSub('Unique rejects ACA tokens from Acala', async () => {470    await genericRejectNativeTokensFrom('acala', alice);471  });472473  itSub('Unique rejects GLMR tokens from Moonbeam', async () => {474    await genericRejectNativeTokensFrom('moonbeam', alice);475  });476477  itSub('Unique rejects ASTR tokens from Astar', async () => {478    await genericRejectNativeTokensFrom('astar', alice);479  });480481  itSub('Unique rejects PDX tokens from Polkadex', async () => {482    await genericRejectNativeTokensFrom('polkadex', alice);483  });484});485486describeXCM('[XCMLL] Integration test: Exchanging UNQ with Moonbeam', () => {487  // Unique constants488  let alice: IKeyringPair;489  let uniqueAssetLocation;490491  let randomAccountUnique: IKeyringPair;492  let randomAccountMoonbeam: IKeyringPair;493494  // Moonbeam constants495  let assetId: string;496497  const uniqueAssetMetadata = {498    name: 'xcUnique',499    symbol: 'xcUNQ',500    decimals: 18,501    isFrozen: false,502    minimalBalance: 1n,503  };504505506  before(async () => {507    await usingPlaygrounds(async (helper, privateKey) => {508      alice = await privateKey('//Alice');509      [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);510511512      // Set the default version to wrap the first message to other chains.513      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);514    });515516    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {517      const alithAccount = helper.account.alithAccount();518      const baltatharAccount = helper.account.baltatharAccount();519      const dorothyAccount = helper.account.dorothyAccount();520521      randomAccountMoonbeam = helper.account.create();522523      // >>> Sponsoring Dorothy >>>524      console.log('Sponsoring Dorothy.......');525      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);526      console.log('Sponsoring Dorothy.......DONE');527      // <<< Sponsoring Dorothy <<<528      uniqueAssetLocation = {529        XCM: {530          parents: 1,531          interior: {X1: {Parachain: UNIQUE_CHAIN}},532        },533      };534      const existentialDeposit = 1n;535      const isSufficient = true;536      const unitsPerSecond = 1n;537      const numAssetsWeightHint = 0;538539      if((await helper.assetManager.assetTypeId(uniqueAssetLocation)).toJSON()) {540        console.log('Unique asset already registered on Moonbeam');541      } else {542        const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({543          location: uniqueAssetLocation,544          metadata: uniqueAssetMetadata,545          existentialDeposit,546          isSufficient,547          unitsPerSecond,548          numAssetsWeightHint,549        });550551        console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);552553        await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);554      }555556      // >>> Acquire Unique AssetId Info on Moonbeam >>>557      console.log('Acquire Unique AssetId Info on Moonbeam.......');558559      assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();560561      console.log('UNQ asset ID is %s', assetId);562      console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');563564      // >>> Sponsoring random Account >>>565      console.log('Sponsoring random Account.......');566      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);567      console.log('Sponsoring random Account.......DONE');568      // <<< Sponsoring random Account <<<569    });570571    await usingPlaygrounds(async (helper) => {572      await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, SENDER_BUDGET);573      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);574    });575  });576577  itSub('Should connect and send UNQ to Moonbeam', async () => {578    await testHelper.sendUnqTo('moonbeam', randomAccountUnique, randomAccountMoonbeam);579  });580581  itSub('Should connect to Moonbeam and send UNQ back', async () => {582    await testHelper.sendUnqBack('moonbeam', alice, randomAccountUnique);583  });584585  itSub('Moonbeam can send only up to its balance', async () => {586    await testHelper.sendOnlyOwnedBalance('moonbeam', alice);587  });588589  itSub('Should not accept reserve transfer of UNQ from Moonbeam', async () => {590    await testHelper.reserveTransferUNQfrom('moonbeam', alice);591  });592});593594describeXCM('[XCMLL] Integration test: Exchanging tokens with Astar', () => {595  let alice: IKeyringPair;596  let randomAccount: IKeyringPair;597598  const UNQ_ASSET_ID_ON_ASTAR = 1;599  const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n;600601  // Unique -> Astar602  const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.603  const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?604605  before(async () => {606    await usingPlaygrounds(async (helper, privateKey) => {607      alice = await privateKey('//Alice');608      randomAccount = helper.arrange.createEmptyAccount();609      await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);610      console.log('randomAccount', randomAccount.address);611612      // Set the default version to wrap the first message to other chains.613      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);614    });615616    await usingAstarPlaygrounds(astarUrl, async (helper) => {617      if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {618        console.log('1. Create foreign asset and metadata');619        // TODO update metadata with values from production620        await helper.assets.create(621          alice,622          UNQ_ASSET_ID_ON_ASTAR,623          alice.address,624          UNQ_MINIMAL_BALANCE_ON_ASTAR,625        );626627        await helper.assets.setMetadata(628          alice,629          UNQ_ASSET_ID_ON_ASTAR,630          'Cross chain UNQ',631          'xcUNQ',632          Number(UNQ_DECIMALS),633        );634635        console.log('2. Register asset location on Astar');636        const assetLocation = {637          V2: {638            parents: 1,639            interior: {640              X1: {641                Parachain: UNIQUE_CHAIN,642              },643            },644          },645        };646647        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);648649        console.log('3. Set UNQ payment for XCM execution on Astar');650        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);651      } else {652        console.log('UNQ is already registered on Astar');653      }654      console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');655      await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);656    });657  });658659  itSub('Should connect and send UNQ to Astar', async () => {660    await genericSendUnqTo('astar', randomAccount);661  });662663  itSub('Should connect to Astar and send UNQ back', async () => {664    await genericSendUnqBack('astar', alice, randomAccount);665  });666667  itSub('Astar can send only up to its balance', async () => {668    await genericSendOnlyOwnedBalance('astar', alice);669  });670671  itSub('Should not accept reserve transfer of UNQ from Astar', async () => {672    await genericReserveTransferUNQfrom('astar', alice);673  });674});675676describeXCM('[XCMLL] Integration test: The relay can do some root ops', () => {677  let sudoer: IKeyringPair;678679  before(async function () {680    await usingRelayPlaygrounds(relayUrl, async (_, privateKey) => {681      sudoer = await privateKey('//Alice');682    });683  });684685  // At the moment there is no reliable way686  // to establish the correspondence between the `ExecutedDownward` event687  // and the relay's sent message due to `SetTopic` instruction688  // containing an unpredictable topic silently added by the relay on the router level.689  // This changes the message hash on arrival to our chain.690  //691  // See:692  // * The relay's router: https://github.com/paritytech/polkadot-sdk/blob/f60318f68687e601c47de5ad5ca88e2c3f8139a7/polkadot/runtime/westend/src/xcm_config.rs#L83693  // * The `WithUniqueTopic` helper: https://github.com/paritytech/polkadot-sdk/blob/945ebbbcf66646be13d5b1d1bc26c8b0d3296d9e/polkadot/xcm/xcm-builder/src/routing.rs#L36694  //695  // Because of this, we insert time gaps between tests so696  // different `ExecutedDownward` events won't interfere with each other.697  afterEach(async () => {698    await usingPlaygrounds(async (helper) => {699      await helper.wait.newBlocks(3);700    });701  });702703  itSub('The relay can set storage', async () => {704    await testHelper.relayIsPermittedToSetStorage(sudoer, 'plain');705  });706707  itSub('The relay can batch set storage', async () => {708    await testHelper.relayIsPermittedToSetStorage(sudoer, 'batch');709  });710711  itSub('The relay can batchAll set storage', async () => {712    await testHelper.relayIsPermittedToSetStorage(sudoer, 'batchAll');713  });714715  itSub('The relay can forceBatch set storage', async () => {716    await testHelper.relayIsPermittedToSetStorage(sudoer, 'forceBatch');717  });718719  itSub('[negative] The relay cannot set balance', async () => {720    await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'plain');721  });722723  itSub('[negative] The relay cannot set balance via batch', async () => {724    await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'batch');725  });726727  itSub('[negative] The relay cannot set balance via batchAll', async () => {728    await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'batchAll');729  });730731  itSub('[negative] The relay cannot set balance via forceBatch', async () => {732    await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'forceBatch');733  });734735  itSub('[negative] The relay cannot set balance via dispatchAs', async () => {736    await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'dispatchAs');737  });738});
after · tests/src/xcm/lowLevelXcmUnique.test.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {IKeyringPair} from '@polkadot/types/types';18import config from '../config';19import {itSub, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds} from '../util';20import {nToBigInt} from '@polkadot/util';21import {hexToString} from '@polkadot/util';22import {ASTAR_DECIMALS, SAFE_XCM_VERSION, SENDER_BUDGET, UNIQUE_CHAIN, UNQ_DECIMALS, XcmTestHelper, acalaUrl, astarUrl,  moonbeamUrl, polkadexUrl, relayUrl, uniqueAssetId} from './xcm.types';2324const testHelper = new XcmTestHelper('unique');2526272829describeXCM('[XCMLL] Integration test: Exchanging tokens with Acala', () => {30  let alice: IKeyringPair;31  let randomAccount: IKeyringPair;3233  before(async () => {34    await usingPlaygrounds(async (helper, privateKey) => {35      alice = await privateKey('//Alice');36      console.log(config.acalaUrl);37      randomAccount = helper.arrange.createEmptyAccount();3839      // Set the default version to wrap the first message to other chains.40      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);41    });4243    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {44      const destination = {45        V2: {46          parents: 1,47          interior: {48            X1: {49              Parachain: UNIQUE_CHAIN,50            },51          },52        },53      };5455      const metadata = {56        name: 'Unique Network',57        symbol: 'UNQ',58        decimals: 18,59        minimalBalance: 1250_000_000_000_000_000n,60      };61      const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v] : [any, any]) =>62        hexToString(v.toJSON()['symbol'])) as string[];6364      if(!assets.includes('UNQ')) {65        await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);66      } else {67        console.log('UNQ token already registered on Acala assetRegistry pallet');68      }69      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);70    });7172    await usingPlaygrounds(async (helper) => {73      await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);74    });75  });7677  itSub('Should connect and send UNQ to Acala', async () => {78    await testHelper.sendUnqTo('acala', randomAccount);79  });8081  itSub('Should connect to Acala and send UNQ back', async () => {82    await testHelper.sendUnqBack('acala', alice, randomAccount);83  });8485  itSub('Acala can send only up to its balance', async () => {86    await testHelper.sendOnlyOwnedBalance('acala', alice);87  });8889  itSub('Should not accept reserve transfer of UNQ from Acala', async () => {90    await testHelper.reserveTransferUNQfrom('acala', alice);91  });92});9394describeXCM('[XCMLL] Integration test: Exchanging tokens with Polkadex', () => {95  let alice: IKeyringPair;96  let randomAccount: IKeyringPair;9798  before(async () => {99    await usingPlaygrounds(async (helper, privateKey) => {100      alice = await privateKey('//Alice');101      randomAccount = helper.arrange.createEmptyAccount();102103      // Set the default version to wrap the first message to other chains.104      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);105    });106107    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {108      const isWhitelisted = ((await helper.callRpc('api.query.xcmHelper.whitelistedTokens', []))109        .toJSON() as [])110        .map(nToBigInt).length != 0;111      /*112      Check whether the Unique token has been added113      to the whitelist, since an error will occur114      if it is added again. Needed for debugging115      when this test is run multiple times.116      */117      if(isWhitelisted) {118        console.log('UNQ token is already whitelisted on Polkadex');119      } else {120        await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);121      }122123      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);124    });125126    await usingPlaygrounds(async (helper) => {127      await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);128    });129  });130131  itSub('Should connect and send UNQ to Polkadex', async () => {132    await testHelper.sendUnqTo('polkadex', randomAccount);133  });134135136  itSub('Should connect to Polkadex and send UNQ back', async () => {137    await testHelper.sendUnqBack('polkadex', alice, randomAccount);138  });139140  itSub('Polkadex can send only up to its balance', async () => {141    await testHelper.sendOnlyOwnedBalance('polkadex', alice);142  });143144  itSub('Should not accept reserve transfer of UNQ from Polkadex', async () => {145    await testHelper.reserveTransferUNQfrom('polkadex', alice);146  });147});148149// These tests are relevant only when150// the the corresponding foreign assets are not registered151describeXCM('[XCMLL] Integration test: Unique rejects non-native tokens', () => {152  let alice: IKeyringPair;153154  before(async () => {155    await usingPlaygrounds(async (helper, privateKey) => {156      alice = await privateKey('//Alice');157158      // Set the default version to wrap the first message to other chains.159      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);160    });161  });162163  itSub('Unique rejects ACA tokens from Acala', async () => {164    await testHelper.rejectNativeTokensFrom('acala', alice);165  });166167  itSub('Unique rejects GLMR tokens from Moonbeam', async () => {168    await testHelper.rejectNativeTokensFrom('moonbeam', alice);169  });170171  itSub('Unique rejects ASTR tokens from Astar', async () => {172    await testHelper.rejectNativeTokensFrom('astar', alice);173  });174175  itSub('Unique rejects PDX tokens from Polkadex', async () => {176    await testHelper.rejectNativeTokensFrom('polkadex', alice);177  });178});179180describeXCM('[XCMLL] Integration test: Exchanging UNQ with Moonbeam', () => {181  // Unique constants182  let alice: IKeyringPair;183  let uniqueAssetLocation;184185  let randomAccountUnique: IKeyringPair;186  let randomAccountMoonbeam: IKeyringPair;187188  // Moonbeam constants189  let assetId: string;190191  const uniqueAssetMetadata = {192    name: 'xcUnique',193    symbol: 'xcUNQ',194    decimals: 18,195    isFrozen: false,196    minimalBalance: 1n,197  };198199200  before(async () => {201    await usingPlaygrounds(async (helper, privateKey) => {202      alice = await privateKey('//Alice');203      [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);204205206      // Set the default version to wrap the first message to other chains.207      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);208    });209210    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {211      const alithAccount = helper.account.alithAccount();212      const baltatharAccount = helper.account.baltatharAccount();213      const dorothyAccount = helper.account.dorothyAccount();214215      randomAccountMoonbeam = helper.account.create();216217      // >>> Sponsoring Dorothy >>>218      console.log('Sponsoring Dorothy.......');219      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);220      console.log('Sponsoring Dorothy.......DONE');221      // <<< Sponsoring Dorothy <<<222      uniqueAssetLocation = {223        XCM: {224          parents: 1,225          interior: {X1: {Parachain: UNIQUE_CHAIN}},226        },227      };228      const existentialDeposit = 1n;229      const isSufficient = true;230      const unitsPerSecond = 1n;231      const numAssetsWeightHint = 0;232233      if((await helper.assetManager.assetTypeId(uniqueAssetLocation)).toJSON()) {234        console.log('Unique asset already registered on Moonbeam');235      } else {236        const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({237          location: uniqueAssetLocation,238          metadata: uniqueAssetMetadata,239          existentialDeposit,240          isSufficient,241          unitsPerSecond,242          numAssetsWeightHint,243        });244245        console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);246247        await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);248      }249250      // >>> Acquire Unique AssetId Info on Moonbeam >>>251      console.log('Acquire Unique AssetId Info on Moonbeam.......');252253      assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();254255      console.log('UNQ asset ID is %s', assetId);256      console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');257258      // >>> Sponsoring random Account >>>259      console.log('Sponsoring random Account.......');260      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);261      console.log('Sponsoring random Account.......DONE');262      // <<< Sponsoring random Account <<<263    });264265    await usingPlaygrounds(async (helper) => {266      await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, SENDER_BUDGET);267    });268  });269270  itSub('Should connect and send UNQ to Moonbeam', async () => {271    await testHelper.sendUnqTo('moonbeam', randomAccountUnique, randomAccountMoonbeam);272  });273274  itSub('Should connect to Moonbeam and send UNQ back', async () => {275    await testHelper.sendUnqBack('moonbeam', alice, randomAccountUnique);276  });277278  itSub('Moonbeam can send only up to its balance', async () => {279    await testHelper.sendOnlyOwnedBalance('moonbeam', alice);280  });281282  itSub('Should not accept reserve transfer of UNQ from Moonbeam', async () => {283    await testHelper.reserveTransferUNQfrom('moonbeam', alice);284  });285});286287describeXCM('[XCMLL] Integration test: Exchanging tokens with Astar', () => {288  let alice: IKeyringPair;289  let randomAccount: IKeyringPair;290291  const UNQ_ASSET_ID_ON_ASTAR = 1;292  const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n;293294  // Unique -> Astar295  const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.296  const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?297298  before(async () => {299    await usingPlaygrounds(async (helper, privateKey) => {300      alice = await privateKey('//Alice');301      randomAccount = helper.arrange.createEmptyAccount();302      await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);303      console.log('randomAccount', randomAccount.address);304305      // Set the default version to wrap the first message to other chains.306      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);307    });308309    await usingAstarPlaygrounds(astarUrl, async (helper) => {310      if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {311        console.log('1. Create foreign asset and metadata');312        // TODO update metadata with values from production313        await helper.assets.create(314          alice,315          UNQ_ASSET_ID_ON_ASTAR,316          alice.address,317          UNQ_MINIMAL_BALANCE_ON_ASTAR,318        );319320        await helper.assets.setMetadata(321          alice,322          UNQ_ASSET_ID_ON_ASTAR,323          'Cross chain UNQ',324          'xcUNQ',325          Number(UNQ_DECIMALS),326        );327328        console.log('2. Register asset location on Astar');329        const assetLocation = {330          V2: {331            parents: 1,332            interior: {333              X1: {334                Parachain: UNIQUE_CHAIN,335              },336            },337          },338        };339340        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);341342        console.log('3. Set UNQ payment for XCM execution on Astar');343        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);344      } else {345        console.log('UNQ is already registered on Astar');346      }347      console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');348      await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);349    });350  });351352  itSub('Should connect and send UNQ to Astar', async () => {353    await testHelper.sendUnqTo('astar', randomAccount);354  });355356  itSub('Should connect to Astar and send UNQ back', async () => {357    await testHelper.sendUnqBack('astar', alice, randomAccount);358  });359360  itSub('Astar can send only up to its balance', async () => {361    await testHelper.sendOnlyOwnedBalance('astar', alice);362  });363364  itSub('Should not accept reserve transfer of UNQ from Astar', async () => {365    await testHelper.reserveTransferUNQfrom('astar', alice);366  });367});368369describeXCM('[XCMLL] Integration test: The relay can do some root ops', () => {370  let sudoer: IKeyringPair;371372  before(async function () {373    await usingRelayPlaygrounds(relayUrl, async (_, privateKey) => {374      sudoer = await privateKey('//Alice');375    });376  });377378  // At the moment there is no reliable way379  // to establish the correspondence between the `ExecutedDownward` event380  // and the relay's sent message due to `SetTopic` instruction381  // containing an unpredictable topic silently added by the relay on the router level.382  // This changes the message hash on arrival to our chain.383  //384  // See:385  // * The relay's router: https://github.com/paritytech/polkadot-sdk/blob/f60318f68687e601c47de5ad5ca88e2c3f8139a7/polkadot/runtime/westend/src/xcm_config.rs#L83386  // * The `WithUniqueTopic` helper: https://github.com/paritytech/polkadot-sdk/blob/945ebbbcf66646be13d5b1d1bc26c8b0d3296d9e/polkadot/xcm/xcm-builder/src/routing.rs#L36387  //388  // Because of this, we insert time gaps between tests so389  // different `ExecutedDownward` events won't interfere with each other.390  afterEach(async () => {391    await usingPlaygrounds(async (helper) => {392      await helper.wait.newBlocks(3);393    });394  });395396  itSub('The relay can set storage', async () => {397    await testHelper.relayIsPermittedToSetStorage(sudoer, 'plain');398  });399400  itSub('The relay can batch set storage', async () => {401    await testHelper.relayIsPermittedToSetStorage(sudoer, 'batch');402  });403404  itSub('The relay can batchAll set storage', async () => {405    await testHelper.relayIsPermittedToSetStorage(sudoer, 'batchAll');406  });407408  itSub('The relay can forceBatch set storage', async () => {409    await testHelper.relayIsPermittedToSetStorage(sudoer, 'forceBatch');410  });411412  itSub('[negative] The relay cannot set balance', async () => {413    await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'plain');414  });415416  itSub('[negative] The relay cannot set balance via batch', async () => {417    await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'batch');418  });419420  itSub('[negative] The relay cannot set balance via batchAll', async () => {421    await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'batchAll');422  });423424  itSub('[negative] The relay cannot set balance via forceBatch', async () => {425    await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'forceBatch');426  });427428  itSub('[negative] The relay cannot set balance via dispatchAs', async () => {429    await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'dispatchAs');430  });431});
modifiedtests/src/xcm/xcm.types.tsdiffbeforeafterboth
--- a/tests/src/xcm/xcm.types.ts
+++ b/tests/src/xcm/xcm.types.ts
@@ -3,7 +3,6 @@
 import {expect, usingAcalaPlaygrounds, usingAstarPlaygrounds, usingKaruraPlaygrounds, usingMoonbeamPlaygrounds, usingMoonriverPlaygrounds, usingPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds, usingShidenPlaygrounds} from '../util';
 import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';
 import config from '../config';
-import {blake2AsHex} from '@polkadot/util-crypto';
 
 export const UNIQUE_CHAIN = +(process.env.RELAY_UNIQUE_ID || 2037);
 export const STATEMINT_CHAIN = +(process.env.RELAY_STATEMINT_ID || 1000);
@@ -136,10 +135,10 @@
 }
 
 export const TRANSFER_AMOUNT = 2000000_000_000_000_000_000_000n;
-const SENDER_BUDGET = 2n * TRANSFER_AMOUNT;
-const SENDBACK_AMOUNT = TRANSFER_AMOUNT / 2n;
-const STAYED_ON_TARGET_CHAIN = TRANSFER_AMOUNT - SENDBACK_AMOUNT;
-const TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT = 100_000_000_000n;
+export const SENDER_BUDGET = 2n * TRANSFER_AMOUNT;
+export const SENDBACK_AMOUNT = TRANSFER_AMOUNT / 2n;
+export const STAYED_ON_TARGET_CHAIN = TRANSFER_AMOUNT - SENDBACK_AMOUNT;
+export const TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT = 100_000_000_000n;
 
 export class XcmTestHelper {
   private _balanceUniqueTokenInit: bigint = 0n;
@@ -163,6 +162,7 @@
         return UNIQUE_CHAIN;
     }
   }
+
   private _isAddress20FormatFor(network: NetworkNames) {
     switch (network) {
       case 'moonbeam':
@@ -173,6 +173,19 @@
     }
   }
 
+  private _runtimeVersionedMultilocation() {
+    return {
+      V3: {
+        parents: 1,
+        interior: {
+          X1: {
+            Parachain: this._getNativeId(),
+          },
+        },
+      },
+    };
+  }
+
   uniqueChainMultilocationForRelay() {
     return {
       V3: {
@@ -250,8 +263,8 @@
       this._balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);
 
       this._unqFees = this._balanceUniqueTokenInit - this._balanceUniqueTokenMiddle - TRANSFER_AMOUNT;
-      console.log('[Unique -> %s] transaction fees on Unique: %s UNQ', networkName, helper.util.bigIntToDecimals(this._unqFees));
-      expect(this._unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;
+      console.log('[%s -> %s] transaction fees: %s', this._nativeRuntime, networkName, helper.util.bigIntToDecimals(this._unqFees));
+      expect(this._unqFees > 0n, 'Negative fees, looks like nothing was transferred').to.be.true;
 
       await targetPlayground(networkUrl, async (helper) => {
       /*
@@ -302,10 +315,10 @@
 
       await targetPlayground(networkUrl, async (helper) => {
         if('getSudo' in helper) {
-          await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, xcmProgram);
+          await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), xcmProgram);
           xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
         } else if('fastDemocracy' in helper) {
-          const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, xcmProgram]);
+          const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), xcmProgram]);
           // Needed to bypass the call filter.
           const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
           await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);
@@ -354,10 +367,10 @@
 
       await targetPlayground(networkUrl, async (helper) => {
         if('getSudo' in helper) {
-          await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgram);
+          await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), maliciousXcmProgram);
           maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
         } else if('fastDemocracy' in helper) {
-          const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]);
+          const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgram]);
           // Needed to bypass the call filter.
           const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
           await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);
@@ -413,12 +426,12 @@
       // Try to trick Unique using full UNQ identification
       await targetPlayground(networkUrl, async (helper) => {
         if('getSudo' in helper) {
-          await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramFullId);
+          await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId);
           maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
         }
         // Moonbeam case
         else if('fastDemocracy' in helper) {
-          const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);
+          const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId]);
           // Needed to bypass the call filter.
           const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
           await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using path asset identification`,batchCall);
@@ -436,11 +449,11 @@
       // Try to trick Unique using shortened UNQ identification
       await targetPlayground(networkUrl, async (helper) => {
         if('getSudo' in helper) {
-          await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramHereId);
+          await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), maliciousXcmProgramHereId);
           maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
         }
         else if('fastDemocracy' in helper) {
-          const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]);
+          const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgramHereId]);
           // Needed to bypass the call filter.
           const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
           await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using "here" asset identification`, batchCall);
@@ -478,10 +491,10 @@
       );
       await targetPlayground(networkUrl, async (helper) => {
         if('getSudo' in helper) {
-          await helper.getSudo().xcm.send(sudoerOnTargetChain, uniqueVersionedMultilocation, maliciousXcmProgramFullId);
+          await helper.getSudo().xcm.send(sudoerOnTargetChain, this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId);
           messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
         } else if('fastDemocracy' in helper) {
-          const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);
+          const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId]);
           // Needed to bypass the call filter.
           const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
           await helper.fastDemocracy.executeProposal(`${networkName} sending native tokens to the Unique via fast democracy`, batchCall);
modifiedtests/src/xcm/xcmQuartz.test.tsdiffbeforeafterboth
--- a/tests/src/xcm/xcmQuartz.test.ts
+++ b/tests/src/xcm/xcmQuartz.test.ts
@@ -18,6 +18,7 @@
 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';
+import {hexToString} from '@polkadot/util';
 
 
 
@@ -483,7 +484,14 @@
         minimalBalance: 1000000000000000000n,
       };
 
-      await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);
+      const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v]: [any, any]) =>
+        hexToString(v.toJSON()['symbol'])) as string[];
+
+      if(!assets.includes('QTZ')) {
+        await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);
+      } else {
+        console.log('QTZ token already registered on Karura assetRegistry pallet');
+      }
       await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);
       balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);
       balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});
@@ -969,19 +977,22 @@
       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);
+      if((await helper.assetManager.assetTypeId(quartzAssetLocation)).toJSON()) {
+        console.log('Quartz asset already registered on Moonriver');
+      } else {
+        const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({
+          location: quartzAssetLocation,
+          metadata: quartzAssetMetadata,
+          existentialDeposit,
+          isSufficient,
+          unitsPerSecond,
+          numAssetsWeightHint,
+        });
 
-      await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);
+        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.......');
 
@@ -1298,40 +1309,43 @@
     });
 
     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,
-      );
+      if(!(await helper.callRpc('api.query.assets.asset', [QTZ_ASSET_ID_ON_SHIDEN])).toJSON()) {
+        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),
-      );
+        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,
+        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]);
+        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]);
+      } else {
+        console.log('QTZ is already registered on Shiden');
+      }
       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);
     });