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
--- a/tests/src/xcm/lowLevelXcmUnique.test.ts
+++ b/tests/src/xcm/lowLevelXcmUnique.test.ts
@@ -16,320 +16,16 @@
 
 import {IKeyringPair} from '@polkadot/types/types';
 import config from '../config';
-import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds, requirePalletsOrSkip, Pallets} from '../util';
-import {Event} from '../util/playgrounds/unique.dev';
+import {itSub, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds} from '../util';
 import {nToBigInt} from '@polkadot/util';
 import {hexToString} from '@polkadot/util';
-import {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';
-
-
-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;
-
-let balanceUniqueTokenInit: bigint;
-let balanceUniqueTokenMiddle: bigint;
-let balanceUniqueTokenFinal: bigint;
-let unqFees: bigint;
+import {ASTAR_DECIMALS, SAFE_XCM_VERSION, SENDER_BUDGET, UNIQUE_CHAIN, UNQ_DECIMALS, XcmTestHelper, acalaUrl, astarUrl,  moonbeamUrl, polkadexUrl, relayUrl, uniqueAssetId} from './xcm.types';
 
 const testHelper = new XcmTestHelper('unique');
-
-async function genericSendUnqTo(
-  networkName: keyof typeof NETWORKS,
-  randomAccount: IKeyringPair,
-  randomAccountOnTargetChain = randomAccount,
-) {
-  const networkUrl = mapToChainUrl(networkName);
-  const targetPlayground = getDevPlayground(networkName);
-  await usingPlaygrounds(async (helper) => {
-    balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);
-    const destination = {
-      V2: {
-        parents: 1,
-        interior: {
-          X1: {
-            Parachain: mapToChainId(networkName),
-          },
-        },
-      },
-    };
-
-    const beneficiary = {
-      V2: {
-        parents: 0,
-        interior: {
-          X1: (
-            networkName == 'moonbeam' ?
-              {
-                AccountKey20: {
-                  network: 'Any',
-                  key: randomAccountOnTargetChain.address,
-                },
-              }
-              :
-              {
-                AccountId32: {
-                  network: 'Any',
-                  id: randomAccountOnTargetChain.addressRaw,
-                },
-              }
-          ),
-        },
-      },
-    };
-
-    const assets = {
-      V2: [
-        {
-          id: {
-            Concrete: {
-              parents: 0,
-              interior: 'Here',
-            },
-          },
-          fun: {
-            Fungible: TRANSFER_AMOUNT,
-          },
-        },
-      ],
-    };
-    const feeAssetItem = 0;
-
-    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');
-    const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
-    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);
-
-    unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;
-    console.log('[Unique -> %s] transaction fees on Unique: %s UNQ', networkName, helper.util.bigIntToDecimals(unqFees));
-    expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;
-
-    await targetPlayground(networkUrl, async (helper) => {
-      /*
-        Since only the parachain part of the Polkadex
-        infrastructure is launched (without their
-        solochain validators), processing incoming
-        assets will lead to an error.
-        This error indicates that the Polkadex chain
-        received a message from the Unique network,
-        since the hash is being checked to ensure
-        it matches what was sent.
-      */
-      if(networkName == 'polkadex') {
-        await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);
-      } else {
-        await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == messageSent.messageHash);
-      }
-    });
-
-  });
-}
-
-async function genericSendUnqBack(
-  networkName: keyof typeof NETWORKS,
-  sudoer: IKeyringPair,
-  randomAccountOnUnq: IKeyringPair,
-) {
-  const networkUrl = mapToChainUrl(networkName);
-
-  const targetPlayground = getDevPlayground(networkName);
-  await usingPlaygrounds(async (helper) => {
-
-    const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(
-      randomAccountOnUnq.addressRaw,
-      uniqueAssetId,
-      SENDBACK_AMOUNT,
-    );
-
-    let xcmProgramSent: any;
-
 
-    await targetPlayground(networkUrl, async (helper) => {
-      if('getSudo' in helper) {
-        await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, 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]);
-        // 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);
-        xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
-      }
-    });
-
-    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);
-
-    balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountOnUnq.address);
-
-    expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees - STAYED_ON_TARGET_CHAIN);
-
-  });
-}
 
-async function genericSendOnlyOwnedBalance(
-  networkName: keyof typeof NETWORKS,
-  sudoer: IKeyringPair,
-) {
-  const networkUrl = mapToChainUrl(networkName);
-  const targetPlayground = getDevPlayground(networkName);
 
-  const targetChainBalance = 10000n * (10n ** UNQ_DECIMALS);
 
-  await usingPlaygrounds(async (helper) => {
-    const targetChainSovereignAccount = helper.address.paraSiblingSovereignAccount(mapToChainId(networkName));
-    await helper.getSudo().balance.setBalanceSubstrate(sudoer, targetChainSovereignAccount, targetChainBalance);
-    const moreThanTargetChainHas = 2n * targetChainBalance;
-
-    const targetAccount = helper.arrange.createEmptyAccount();
-
-    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(
-      targetAccount.addressRaw,
-      {
-        Concrete: {
-          parents: 0,
-          interior: 'Here',
-        },
-      },
-      moreThanTargetChainHas,
-    );
-
-    let maliciousXcmProgramSent: any;
-
-
-    await targetPlayground(networkUrl, async (helper) => {
-      if('getSudo' in helper) {
-        await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, 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]);
-        // 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);
-        maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
-      }
-    });
-
-    await expectFailedToTransact(helper, maliciousXcmProgramSent);
-
-    const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);
-    expect(targetAccountBalance).to.be.equal(0n);
-  });
-}
-
-async function genericReserveTransferUNQfrom(netwokrName: keyof typeof NETWORKS, sudoer: IKeyringPair) {
-  const networkUrl = mapToChainUrl(netwokrName);
-  const targetPlayground = getDevPlayground(netwokrName);
-
-  await usingPlaygrounds(async (helper) => {
-    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);
-    const targetAccount = helper.arrange.createEmptyAccount();
-
-    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(
-      targetAccount.addressRaw,
-      uniqueAssetId,
-      testAmount,
-    );
-
-    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(
-      targetAccount.addressRaw,
-      {
-        Concrete: {
-          parents: 0,
-          interior: 'Here',
-        },
-      },
-      testAmount,
-    );
-
-    let maliciousXcmProgramFullIdSent: any;
-    let maliciousXcmProgramHereIdSent: any;
-    const maxWaitBlocks = 3;
-
-    // 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);
-        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]);
-        // 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);
-
-        maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
-      }
-    });
-
-
-    await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);
-
-    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);
-    expect(accountBalance).to.be.equal(0n);
-
-    // 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);
-        maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
-      }
-      else if('fastDemocracy' in helper) {
-        const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, 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);
-
-        maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
-      }
-    });
-
-    await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);
-
-    accountBalance = await helper.balance.getSubstrate(targetAccount.address);
-    expect(accountBalance).to.be.equal(0n);
-  });
-}
-
-async function genericRejectNativeTokensFrom(networkName: keyof typeof NETWORKS, sudoerOnTargetChain: IKeyringPair) {
-  const networkUrl = mapToChainUrl(networkName);
-  const targetPlayground = getDevPlayground(networkName);
-  let messageSent: any;
-
-  await usingPlaygrounds(async (helper) => {
-    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(
-      helper.arrange.createEmptyAccount().addressRaw,
-      {
-        Concrete: {
-          parents: 1,
-          interior: {
-            X1: {
-              Parachain: mapToChainId(networkName),
-            },
-          },
-        },
-      },
-      TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT,
-    );
-    await targetPlayground(networkUrl, async (helper) => {
-      if('getSudo' in helper) {
-        await helper.getSudo().xcm.send(sudoerOnTargetChain, uniqueVersionedMultilocation, 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]);
-        // 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);
-
-        messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
-      }
-    });
-    await expectFailedToTransact(helper, messageSent);
-  });
-}
-
-
 describeXCM('[XCMLL] Integration test: Exchanging tokens with Acala', () => {
   let alice: IKeyringPair;
   let randomAccount: IKeyringPair;
@@ -375,24 +71,23 @@
 
     await usingPlaygrounds(async (helper) => {
       await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);
-      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);
     });
   });
 
   itSub('Should connect and send UNQ to Acala', async () => {
-    await genericSendUnqTo('acala', randomAccount);
+    await testHelper.sendUnqTo('acala', randomAccount);
   });
 
   itSub('Should connect to Acala and send UNQ back', async () => {
-    await genericSendUnqBack('acala', alice, randomAccount);
+    await testHelper.sendUnqBack('acala', alice, randomAccount);
   });
 
   itSub('Acala can send only up to its balance', async () => {
-    await genericSendOnlyOwnedBalance('acala', alice);
+    await testHelper.sendOnlyOwnedBalance('acala', alice);
   });
 
   itSub('Should not accept reserve transfer of UNQ from Acala', async () => {
-    await genericReserveTransferUNQfrom('acala', alice);
+    await testHelper.reserveTransferUNQfrom('acala', alice);
   });
 });
 
@@ -430,7 +125,6 @@
 
     await usingPlaygrounds(async (helper) => {
       await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);
-      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);
     });
   });
 
@@ -467,19 +161,19 @@
   });
 
   itSub('Unique rejects ACA tokens from Acala', async () => {
-    await genericRejectNativeTokensFrom('acala', alice);
+    await testHelper.rejectNativeTokensFrom('acala', alice);
   });
 
   itSub('Unique rejects GLMR tokens from Moonbeam', async () => {
-    await genericRejectNativeTokensFrom('moonbeam', alice);
+    await testHelper.rejectNativeTokensFrom('moonbeam', alice);
   });
 
   itSub('Unique rejects ASTR tokens from Astar', async () => {
-    await genericRejectNativeTokensFrom('astar', alice);
+    await testHelper.rejectNativeTokensFrom('astar', alice);
   });
 
   itSub('Unique rejects PDX tokens from Polkadex', async () => {
-    await genericRejectNativeTokensFrom('polkadex', alice);
+    await testHelper.rejectNativeTokensFrom('polkadex', alice);
   });
 });
 
@@ -570,7 +264,6 @@
 
     await usingPlaygrounds(async (helper) => {
       await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, SENDER_BUDGET);
-      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);
     });
   });
 
@@ -657,19 +350,19 @@
   });
 
   itSub('Should connect and send UNQ to Astar', async () => {
-    await genericSendUnqTo('astar', randomAccount);
+    await testHelper.sendUnqTo('astar', randomAccount);
   });
 
   itSub('Should connect to Astar and send UNQ back', async () => {
-    await genericSendUnqBack('astar', alice, randomAccount);
+    await testHelper.sendUnqBack('astar', alice, randomAccount);
   });
 
   itSub('Astar can send only up to its balance', async () => {
-    await genericSendOnlyOwnedBalance('astar', alice);
+    await testHelper.sendOnlyOwnedBalance('astar', alice);
   });
 
   itSub('Should not accept reserve transfer of UNQ from Astar', async () => {
-    await genericReserveTransferUNQfrom('astar', alice);
+    await testHelper.reserveTransferUNQfrom('astar', alice);
   });
 });
 
modifiedtests/src/xcm/xcm.types.tsdiffbeforeafterboth
before · tests/src/xcm/xcm.types.ts
1import {IKeyringPair} from '@polkadot/types/types';2import {hexToString} from '@polkadot/util';3import {expect, usingAcalaPlaygrounds, usingAstarPlaygrounds, usingKaruraPlaygrounds, usingMoonbeamPlaygrounds, usingMoonriverPlaygrounds, usingPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds, usingShidenPlaygrounds} from '../util';4import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';5import config from '../config';6import {blake2AsHex} from '@polkadot/util-crypto';78export const UNIQUE_CHAIN = +(process.env.RELAY_UNIQUE_ID || 2037);9export const STATEMINT_CHAIN = +(process.env.RELAY_STATEMINT_ID || 1000);10export const ACALA_CHAIN = +(process.env.RELAY_ACALA_ID || 2000);11export const MOONBEAM_CHAIN = +(process.env.RELAY_MOONBEAM_ID || 2004);12export const ASTAR_CHAIN = +(process.env.RELAY_ASTAR_ID || 2006);13export const POLKADEX_CHAIN = +(process.env.RELAY_POLKADEX_ID || 2040);1415export const QUARTZ_CHAIN = +(process.env.RELAY_QUARTZ_ID || 2095);16export const STATEMINE_CHAIN = +(process.env.RELAY_STATEMINE_ID || 1000);17export const KARURA_CHAIN = +(process.env.RELAY_KARURA_ID || 2000);18export const MOONRIVER_CHAIN = +(process.env.RELAY_MOONRIVER_ID || 2023);19export const SHIDEN_CHAIN = +(process.env.RELAY_SHIDEN_ID || 2007);2021export const relayUrl = config.relayUrl;22export const statemintUrl = config.statemintUrl;23export const statemineUrl = config.statemineUrl;2425export const acalaUrl = config.acalaUrl;26export const moonbeamUrl = config.moonbeamUrl;27export const astarUrl = config.astarUrl;28export const polkadexUrl = config.polkadexUrl;2930export const karuraUrl = config.karuraUrl;31export const moonriverUrl = config.moonriverUrl;32export const shidenUrl = config.shidenUrl;3334export const SAFE_XCM_VERSION = 3;353637export const RELAY_DECIMALS = 12;38export const STATEMINE_DECIMALS = 12;39export const KARURA_DECIMALS = 12;40export const SHIDEN_DECIMALS = 18n;41export const QTZ_DECIMALS = 18n;4243export const ASTAR_DECIMALS = 18n;44export const UNQ_DECIMALS = 18n;4546export const maxWaitBlocks = 6;4748export const uniqueMultilocation = {49  parents: 1,50  interior: {51    X1: {52      Parachain: UNIQUE_CHAIN,53    },54  },55};56export const uniqueVersionedMultilocation = {57  V3: uniqueMultilocation,58};5960export const uniqueAssetId = {61  Concrete: uniqueMultilocation,62};6364export const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {65  await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash66        && event.outcome.isFailedToTransactAsset);67};68export const expectUntrustedReserveLocationFail = async (helper: DevUniqueHelper, messageSent: any) => {69  await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash70         && event.outcome.isUntrustedReserveLocation);71};7273export const expectDownwardXcmNoPermission = async (helper: DevUniqueHelper) => {74  // The correct messageHash for downward messages can't be reliably obtained75  await helper.wait.expectEvent(maxWaitBlocks, Event.DmpQueue.ExecutedDownward, event => event.outcome.asIncomplete[1].isNoPermission);76};7778export const expectDownwardXcmComplete = async (helper: DevUniqueHelper) => {79  // The correct messageHash for downward messages can't be reliably obtained80  await helper.wait.expectEvent(maxWaitBlocks, Event.DmpQueue.ExecutedDownward, event => event.outcome.isComplete);81};8283export const NETWORKS = {84  acala: usingAcalaPlaygrounds,85  astar: usingAstarPlaygrounds,86  polkadex: usingPolkadexPlaygrounds,87  moonbeam: usingMoonbeamPlaygrounds,88  moonriver: usingMoonriverPlaygrounds,89  karura: usingKaruraPlaygrounds,90  shiden: usingShidenPlaygrounds,91} as const;92type NetworkNames = keyof typeof NETWORKS;9394type NativeRuntime = 'opal' | 'quartz' | 'unique';9596export function mapToChainId(networkName: keyof typeof NETWORKS): number {97  switch (networkName) {98    case 'acala':99      return ACALA_CHAIN;100    case 'astar':101      return ASTAR_CHAIN;102    case 'moonbeam':103      return MOONBEAM_CHAIN;104    case 'polkadex':105      return POLKADEX_CHAIN;106    case 'moonriver':107      return MOONRIVER_CHAIN;108    case 'karura':109      return KARURA_CHAIN;110    case 'shiden':111      return SHIDEN_CHAIN;112  }113}114115export function mapToChainUrl(networkName: NetworkNames): string {116  switch (networkName) {117    case 'acala':118      return acalaUrl;119    case 'astar':120      return astarUrl;121    case 'moonbeam':122      return moonbeamUrl;123    case 'polkadex':124      return polkadexUrl;125    case 'moonriver':126      return moonriverUrl;127    case 'karura':128      return karuraUrl;129    case 'shiden':130      return shidenUrl;131  }132}133134export function getDevPlayground(name: NetworkNames) {135  return NETWORKS[name];136}137138export const TRANSFER_AMOUNT = 2000000_000_000_000_000_000_000n;139const SENDER_BUDGET = 2n * TRANSFER_AMOUNT;140const SENDBACK_AMOUNT = TRANSFER_AMOUNT / 2n;141const STAYED_ON_TARGET_CHAIN = TRANSFER_AMOUNT - SENDBACK_AMOUNT;142const TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT = 100_000_000_000n;143144export class XcmTestHelper {145  private _balanceUniqueTokenInit: bigint = 0n;146  private _balanceUniqueTokenMiddle: bigint = 0n;147  private _balanceUniqueTokenFinal: bigint = 0n;148  private _unqFees: bigint = 0n;149  private _nativeRuntime: NativeRuntime;150151  constructor(runtime: NativeRuntime) {152    this._nativeRuntime = runtime;153  }154155  private _getNativeId() {156    switch (this._nativeRuntime) {157      case 'opal':158        // To-Do159        return 10;160      case 'quartz':161        return QUARTZ_CHAIN;162      case 'unique':163        return UNIQUE_CHAIN;164    }165  }166  private _isAddress20FormatFor(network: NetworkNames) {167    switch (network) {168      case 'moonbeam':169      case 'moonriver':170        return true;171      default:172        return false;173    }174  }175176  uniqueChainMultilocationForRelay() {177    return {178      V3: {179        parents: 0,180        interior: {181          X1: {Parachain: this._getNativeId()},182        },183      },184    };185  }186187  async sendUnqTo(188    networkName: keyof typeof NETWORKS,189    randomAccount: IKeyringPair,190    randomAccountOnTargetChain = randomAccount,191  ) {192    const networkUrl = mapToChainUrl(networkName);193    const targetPlayground = getDevPlayground(networkName);194    await usingPlaygrounds(async (helper) => {195      this._balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);196      const destination = {197        V2: {198          parents: 1,199          interior: {200            X1: {201              Parachain: mapToChainId(networkName),202            },203          },204        },205      };206207      const beneficiary = {208        V2: {209          parents: 0,210          interior: {211            X1: (212              this._isAddress20FormatFor(networkName) ?213                {214                  AccountKey20: {215                    network: 'Any',216                    key: randomAccountOnTargetChain.address,217                  },218                }219                :220                {221                  AccountId32: {222                    network: 'Any',223                    id: randomAccountOnTargetChain.addressRaw,224                  },225                }226            ),227          },228        },229      };230231      const assets = {232        V2: [233          {234            id: {235              Concrete: {236                parents: 0,237                interior: 'Here',238              },239            },240            fun: {241              Fungible: TRANSFER_AMOUNT,242            },243          },244        ],245      };246      const feeAssetItem = 0;247248      await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');249      const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);250      this._balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);251252      this._unqFees = this._balanceUniqueTokenInit - this._balanceUniqueTokenMiddle - TRANSFER_AMOUNT;253      console.log('[Unique -> %s] transaction fees on Unique: %s UNQ', networkName, helper.util.bigIntToDecimals(this._unqFees));254      expect(this._unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;255256      await targetPlayground(networkUrl, async (helper) => {257      /*258        Since only the parachain part of the Polkadex259        infrastructure is launched (without their260        solochain validators), processing incoming261        assets will lead to an error.262        This error indicates that the Polkadex chain263        received a message from the Unique network,264        since the hash is being checked to ensure265        it matches what was sent.266      */267        if(networkName == 'polkadex') {268          await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);269        } else {270          await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == messageSent.messageHash);271        }272      });273274    });275  }276277  async sendUnqBack(278    networkName: keyof typeof NETWORKS,279    sudoer: IKeyringPair,280    randomAccountOnUnq: IKeyringPair,281  ) {282    const networkUrl = mapToChainUrl(networkName);283284    const targetPlayground = getDevPlayground(networkName);285    await usingPlaygrounds(async (helper) => {286287      const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(288        randomAccountOnUnq.addressRaw,289        {290          Concrete: {291            parents: 1,292            interior: {293              X1: {Parachain: this._getNativeId()},294            },295          },296        },297        SENDBACK_AMOUNT,298      );299300      let xcmProgramSent: any;301302303      await targetPlayground(networkUrl, async (helper) => {304        if('getSudo' in helper) {305          await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, xcmProgram);306          xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);307        } else if('fastDemocracy' in helper) {308          const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, xcmProgram]);309          // Needed to bypass the call filter.310          const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);311          await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);312          xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);313        }314      });315316      await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);317318      this._balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountOnUnq.address);319320      expect(this._balanceUniqueTokenFinal).to.be.equal(this._balanceUniqueTokenInit - this._unqFees - STAYED_ON_TARGET_CHAIN);321322    });323  }324325  async sendOnlyOwnedBalance(326    networkName: keyof typeof NETWORKS,327    sudoer: IKeyringPair,328  ) {329    const networkUrl = mapToChainUrl(networkName);330    const targetPlayground = getDevPlayground(networkName);331332    const targetChainBalance = 10000n * (10n ** UNQ_DECIMALS);333334    await usingPlaygrounds(async (helper) => {335      const targetChainSovereignAccount = helper.address.paraSiblingSovereignAccount(mapToChainId(networkName));336      await helper.getSudo().balance.setBalanceSubstrate(sudoer, targetChainSovereignAccount, targetChainBalance);337      const moreThanTargetChainHas = 2n * targetChainBalance;338339      const targetAccount = helper.arrange.createEmptyAccount();340341      const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(342        targetAccount.addressRaw,343        {344          Concrete: {345            parents: 0,346            interior: 'Here',347          },348        },349        moreThanTargetChainHas,350      );351352      let maliciousXcmProgramSent: any;353354355      await targetPlayground(networkUrl, async (helper) => {356        if('getSudo' in helper) {357          await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgram);358          maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);359        } else if('fastDemocracy' in helper) {360          const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]);361          // Needed to bypass the call filter.362          const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);363          await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);364          maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);365        }366      });367368      await expectFailedToTransact(helper, maliciousXcmProgramSent);369370      const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);371      expect(targetAccountBalance).to.be.equal(0n);372    });373  }374375  async reserveTransferUNQfrom(netwokrName: keyof typeof NETWORKS, sudoer: IKeyringPair) {376    const networkUrl = mapToChainUrl(netwokrName);377    const targetPlayground = getDevPlayground(netwokrName);378379    await usingPlaygrounds(async (helper) => {380      const testAmount = 10_000n * (10n ** UNQ_DECIMALS);381      const targetAccount = helper.arrange.createEmptyAccount();382383      const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(384        targetAccount.addressRaw,385        {386          Concrete: {387            parents: 1,388            interior: {389              X1: {390                Parachain: this._getNativeId(),391              },392            },393          },394        },395        testAmount,396      );397398      const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(399        targetAccount.addressRaw,400        {401          Concrete: {402            parents: 0,403            interior: 'Here',404          },405        },406        testAmount,407      );408409      let maliciousXcmProgramFullIdSent: any;410      let maliciousXcmProgramHereIdSent: any;411      const maxWaitBlocks = 3;412413      // Try to trick Unique using full UNQ identification414      await targetPlayground(networkUrl, async (helper) => {415        if('getSudo' in helper) {416          await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramFullId);417          maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);418        }419        // Moonbeam case420        else if('fastDemocracy' in helper) {421          const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);422          // Needed to bypass the call filter.423          const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);424          await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using path asset identification`,batchCall);425426          maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);427        }428      });429430431      await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);432433      let accountBalance = await helper.balance.getSubstrate(targetAccount.address);434      expect(accountBalance).to.be.equal(0n);435436      // Try to trick Unique using shortened UNQ identification437      await targetPlayground(networkUrl, async (helper) => {438        if('getSudo' in helper) {439          await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramHereId);440          maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);441        }442        else if('fastDemocracy' in helper) {443          const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]);444          // Needed to bypass the call filter.445          const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);446          await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using "here" asset identification`, batchCall);447448          maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);449        }450      });451452      await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);453454      accountBalance = await helper.balance.getSubstrate(targetAccount.address);455      expect(accountBalance).to.be.equal(0n);456    });457  }458459  async rejectNativeTokensFrom(networkName: keyof typeof NETWORKS, sudoerOnTargetChain: IKeyringPair) {460    const networkUrl = mapToChainUrl(networkName);461    const targetPlayground = getDevPlayground(networkName);462    let messageSent: any;463464    await usingPlaygrounds(async (helper) => {465      const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(466        helper.arrange.createEmptyAccount().addressRaw,467        {468          Concrete: {469            parents: 1,470            interior: {471              X1: {472                Parachain: mapToChainId(networkName),473              },474            },475          },476        },477        TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT,478      );479      await targetPlayground(networkUrl, async (helper) => {480        if('getSudo' in helper) {481          await helper.getSudo().xcm.send(sudoerOnTargetChain, uniqueVersionedMultilocation, maliciousXcmProgramFullId);482          messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);483        } else if('fastDemocracy' in helper) {484          const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);485          // Needed to bypass the call filter.486          const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);487          await helper.fastDemocracy.executeProposal(`${networkName} sending native tokens to the Unique via fast democracy`, batchCall);488489          messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);490        }491      });492      await expectFailedToTransact(helper, messageSent);493    });494  }495496  private async _relayXcmTransactSetStorage(variant: 'plain' | 'batch' | 'batchAll' | 'forceBatch') {497    // eslint-disable-next-line require-await498    return await usingPlaygrounds(async (helper) => {499      const relayForceKV = () => {500        const random = Math.random();501        const key = `relay-forced-key (instance: ${random})`;502        const val = `relay-forced-value (instance: ${random})`;503        const call = helper.constructApiCall('api.tx.system.setStorage', [[[key, val]]]).method.toHex();504505        return {506          call,507          key,508          val,509        };510      };511512      if(variant == 'plain') {513        const kv = relayForceKV();514        return {515          program: helper.arrange.makeTransactProgram({516            weightMultiplier: 1,517            call: kv.call,518          }),519          kvs: [kv],520        };521      } else {522        const kv0 = relayForceKV();523        const kv1 = relayForceKV();524525        const batchCall = helper.constructApiCall(`api.tx.utility.${variant}`, [[kv0.call, kv1.call]]).method.toHex();526        return {527          program: helper.arrange.makeTransactProgram({528            weightMultiplier: 2,529            call: batchCall,530          }),531          kvs: [kv0, kv1],532        };533      }534    });535  }536537  async relayIsPermittedToSetStorage(relaySudoer: IKeyringPair, variant: 'plain' | 'batch' | 'batchAll' | 'forceBatch') {538    const {program, kvs} = await this._relayXcmTransactSetStorage(variant);539540    await usingRelayPlaygrounds(relayUrl, async (helper) => {541      await helper.getSudo().executeExtrinsic(relaySudoer, 'api.tx.xcmPallet.send', [542        this.uniqueChainMultilocationForRelay(),543        program,544      ]);545    });546547    await usingPlaygrounds(async (helper) => {548      await expectDownwardXcmComplete(helper);549550      for(const kv of kvs) {551        const forcedValue = await helper.callRpc('api.rpc.state.getStorage', [kv.key]);552        expect(hexToString(forcedValue.toHex())).to.be.equal(kv.val);553      }554    });555  }556557  private async _relayXcmTransactSetBalance(variant: 'plain' | 'batch' | 'batchAll' | 'forceBatch' | 'dispatchAs') {558    // eslint-disable-next-line require-await559    return await usingPlaygrounds(async (helper) => {560      const emptyAccount = helper.arrange.createEmptyAccount().address;561562      const forceSetBalanceCall = helper.constructApiCall('api.tx.balances.forceSetBalance', [emptyAccount, 10_000n]).method.toHex();563564      let call;565566      if(variant == 'plain') {567        call = forceSetBalanceCall;568569      } else if(variant == 'dispatchAs') {570        call = helper.constructApiCall('api.tx.utility.dispatchAs', [571          {572            system: 'Root',573          },574          forceSetBalanceCall,575        ]).method.toHex();576      } else {577        call = helper.constructApiCall(`api.tx.utility.${variant}`, [[forceSetBalanceCall]]).method.toHex();578      }579580      return {581        program: helper.arrange.makeTransactProgram({582          weightMultiplier: 1,583          call,584        }),585        emptyAccount,586      };587    });588  }589590  async relayIsNotPermittedToSetBalance(591    relaySudoer: IKeyringPair,592    variant: 'plain' | 'batch' | 'batchAll' | 'forceBatch' | 'dispatchAs',593  ) {594    const {program, emptyAccount} = await this._relayXcmTransactSetBalance(variant);595596    await usingRelayPlaygrounds(relayUrl, async (helper) => {597      await helper.getSudo().executeExtrinsic(relaySudoer, 'api.tx.xcmPallet.send', [598        this.uniqueChainMultilocationForRelay(),599        program,600      ]);601    });602603    await usingPlaygrounds(async (helper) => {604      await expectDownwardXcmNoPermission(helper);605      expect(await helper.balance.getSubstrate(emptyAccount)).to.be.equal(0n);606    });607  }608}
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);
     });