git.delta.rocks / unique-network / refs/commits / cafc9078ea8d

difftreelog

fix XCM tests

Daniel Shiposha2023-10-23parent: #05427f7.patch.diff
in: master

6 files changed

modifiedjs-packages/playgrounds/types.xcm.tsdiffbeforeafterboth
--- a/js-packages/playgrounds/types.xcm.ts
+++ b/js-packages/playgrounds/types.xcm.ts
@@ -27,10 +27,3 @@
     conviction: number,
   },
 }
-
-export interface IForeignAssetMetadata {
-  name?: number | Uint8Array,
-  symbol?: string,
-  decimals?: number,
-  minimalBalance?: bigint,
-}
\ No newline at end of file
modifiedjs-packages/playgrounds/unique.xcm.tsdiffbeforeafterboth
--- a/js-packages/playgrounds/unique.xcm.ts
+++ b/js-packages/playgrounds/unique.xcm.ts
@@ -2,7 +2,7 @@
 import type {IKeyringPair} from '@polkadot/types/types';
 import {ChainHelperBase, EthereumBalanceGroup, HelperGroup, SubstrateBalanceGroup, UniqueHelper} from './unique.js';
 import type {ILogger, TSigner, TSubstrateAccount} from './types.js';
-import type {AcalaAssetMetadata, DemocracyStandardAccountVote, IForeignAssetMetadata, MoonbeamAssetInfo} from './types.xcm.js';
+import type {AcalaAssetMetadata, DemocracyStandardAccountVote, MoonbeamAssetInfo} from './types.xcm.js';
 
 
 export class XcmChainHelper extends ChainHelperBase {
@@ -104,22 +104,17 @@
 }
 
 export class ForeignAssetsGroup extends HelperGroup<UniqueHelper> {
-  async register(signer: TSigner, ownerAddress: TSubstrateAccount, location: any, metadata: IForeignAssetMetadata) {
+  async register(signer: TSigner, location: any, name: string, tokenPrefix: string, mode: 'NFT' | { Fungible: number }) {
     await this.helper.executeExtrinsic(
       signer,
-      'api.tx.foreignAssets.registerForeignAsset',
-      [ownerAddress, location, metadata],
+      'api.tx.foreignAssets.forceRegisterForeignAsset',
+      [location, this.helper.util.str2vec(name), tokenPrefix, mode],
       true,
     );
   }
 
-  async update(signer: TSigner, foreignAssetId: number, location: any, metadata: IForeignAssetMetadata) {
-    await this.helper.executeExtrinsic(
-      signer,
-      'api.tx.foreignAssets.updateForeignAsset',
-      [foreignAssetId, location, metadata],
-      true,
-    );
+  async foreignCollectionId(location: any) {
+    return (await this.helper.callRpc('api.query.foreignAssets.foreignReserveLocationToCollection', [location])).toJSON();
   }
 }
 
@@ -256,6 +251,10 @@
     await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true);
   }
 
+  async assetInfo(assetId: number | bigint) {
+    return (await this.helper.callRpc('api.query.assets.asset', [assetId])).toJSON();
+  }
+
   async account(assetId: string | number | bigint, address: string) {
     const accountAsset = (
       await this.helper.callRpc('api.query.assets.account', [assetId, address])
modifiedjs-packages/tests/xcm/xcm.types.tsdiffbeforeafterboth
--- a/js-packages/tests/xcm/xcm.types.ts
+++ b/js-packages/tests/xcm/xcm.types.ts
@@ -505,4 +505,26 @@
       await expectFailedToTransact(helper, messageSent);
     });
   }
+
+  async registerRelayNativeTokenOnUnique(alice: IKeyringPair) {
+    return await usingPlaygrounds(async (helper) => {
+      const relayLocation = {
+        parents: 1,
+        interior: 'Here',
+      };
+
+      const relayCollectionId = await helper.foreignAssets.foreignCollectionId(relayLocation);
+      if(relayCollectionId == null) {
+        const name = 'Relay Tokens';
+        const tokenPrefix = 'xDOT';
+        const decimals = 10;
+        await helper.getSudo().foreignAssets.register(alice, relayLocation, name, tokenPrefix, {Fungible: decimals});
+
+        return await helper.foreignAssets.foreignCollectionId(relayLocation);
+      } else {
+        console.log('Relay foreign collection is already registered');
+        return relayCollectionId;
+      }
+    });
+  }
 }
modifiedjs-packages/tests/xcm/xcmOpal.test.tsdiffbeforeafterboth
--- a/js-packages/tests/xcm/xcmOpal.test.ts
+++ b/js-packages/tests/xcm/xcmOpal.test.ts
@@ -17,6 +17,7 @@
 import type {IKeyringPair} from '@polkadot/types/types';
 import config from '../config.js';
 import {itSub, expect, describeXCM, usingPlaygrounds, usingWestmintPlaygrounds, usingRelayPlaygrounds} from '../util/index.js';
+import {XcmTestHelper} from './xcm.types';
 
 const STATEMINE_CHAIN = +(process.env.RELAY_WESTMINT_ID || 1000);
 const UNIQUE_CHAIN = +(process.env.RELAY_OPAL_ID || 2095);
@@ -25,11 +26,11 @@
 const westmintUrl = config.westmintUrl;
 
 const STATEMINE_PALLET_INSTANCE = 50;
-const ASSET_ID = 100;
-const ASSET_METADATA_DECIMALS = 18;
-const ASSET_METADATA_NAME = 'USDT';
-const ASSET_METADATA_DESCRIPTION = 'USDT';
-const ASSET_METADATA_MINIMAL_BALANCE = 1n;
+const USDT_ASSET_ID = 100;
+const USDT_ASSET_METADATA_DECIMALS = 18;
+const USDT_ASSET_METADATA_NAME = 'USDT';
+const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';
+const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;
 
 const RELAY_DECIMALS = 12;
 const WESTMINT_DECIMALS = 12;
@@ -37,7 +38,9 @@
 const TRANSFER_AMOUNT = 1_000_000_000_000_000_000n;
 
 // 10,000.00 (ten thousands) USDT
-const ASSET_AMOUNT = 1_000_000_000_000_000_000_000n;
+const USDT_ASSET_AMOUNT = 1_000_000_000_000_000_000_000n;
+
+const testHelper = new XcmTestHelper('opal');
 
 describeXCM('[XCM] Integration test: Exchanging USDT with Westmint', () => {
   let alice: IKeyringPair;
@@ -57,57 +60,78 @@
   let balanceBobRelayTokenBefore: bigint;
   let balanceBobRelayTokenAfter: bigint;
 
+  let usdtCollectionId: number;
+  let relayCollectionId: number;
 
   before(async () => {
     await usingPlaygrounds(async (_helper, privateKey) => {
       alice = await privateKey('//Alice');
       bob = await privateKey('//Bob'); // funds donor
+
+      relayCollectionId = await testHelper.registerRelayNativeTokenOnUnique(alice);
     });
 
     await usingWestmintPlaygrounds(westmintUrl, async (helper) => {
-      // 350.00 (three hundred fifty) DOT
-      const fundingAmount = 3_500_000_000_000n;
+      const assetInfo = await helper.assets.assetInfo(USDT_ASSET_ID);
+      if(assetInfo == null) {
+        await helper.assets.create(
+          alice,
+          USDT_ASSET_ID,
+          alice.address,
+          USDT_ASSET_METADATA_MINIMAL_BALANCE,
+        );
+        await helper.assets.setMetadata(
+          alice,
+          USDT_ASSET_ID,
+          USDT_ASSET_METADATA_NAME,
+          USDT_ASSET_METADATA_DESCRIPTION,
+          USDT_ASSET_METADATA_DECIMALS,
+        );
+      } else {
+        console.log('The USDT asset is already registered on AssetHub');
+      }
 
-      await helper.assets.create(alice, ASSET_ID, alice.address, ASSET_METADATA_MINIMAL_BALANCE);
-      await helper.assets.setMetadata(alice, ASSET_ID, ASSET_METADATA_NAME, ASSET_METADATA_DESCRIPTION, ASSET_METADATA_DECIMALS);
-      await helper.assets.mint(alice, ASSET_ID, alice.address, ASSET_AMOUNT);
+      await helper.assets.mint(
+        alice,
+        USDT_ASSET_ID,
+        alice.address,
+        USDT_ASSET_AMOUNT,
+      );
+
+      const sovereignFundingAmount = 3_500_000_000n;
 
       // funding parachain sovereing account (Parachain: 2095)
       const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(UNIQUE_CHAIN);
-      await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, fundingAmount);
+      await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);
     });
-
 
     await usingPlaygrounds(async (helper) => {
       const location = {
-        V2: {
-          parents: 1,
-          interior: {X3: [
-            {
-              Parachain: STATEMINE_CHAIN,
-            },
-            {
-              PalletInstance: STATEMINE_PALLET_INSTANCE,
-            },
-            {
-              GeneralIndex: ASSET_ID,
-            },
-          ]},
-        },
+        parents: 1,
+        interior: {X3: [
+          {
+            Parachain: STATEMINE_CHAIN,
+          },
+          {
+            PalletInstance: STATEMINE_PALLET_INSTANCE,
+          },
+          {
+            GeneralIndex: USDT_ASSET_ID,
+          },
+        ]},
       };
 
-      const metadata =
-      {
-        name: ASSET_ID,
-        symbol: ASSET_METADATA_NAME,
-        decimals: ASSET_METADATA_DECIMALS,
-        minimalBalance: ASSET_METADATA_MINIMAL_BALANCE,
-      };
-      await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);
+      if(await helper.foreignAssets.foreignCollectionId(location) == null) {
+        const tokenPrefix = USDT_ASSET_METADATA_NAME;
+        await helper.getSudo().foreignAssets.register(alice, location, USDT_ASSET_METADATA_NAME, tokenPrefix, {Fungible: USDT_ASSET_METADATA_DECIMALS});
+      } else {
+        console.log('Foreign collection is already registered on Opal');
+      }
+
       balanceOpalBefore = await helper.balance.getSubstrate(alice.address);
+      usdtCollectionId = await helper.foreignAssets.foreignCollectionId(location);
     });
 
-
     // Providing the relay currency to the unique sender account
     await usingRelayPlaygrounds(relayUrl, async (helper) => {
       const destination = {
@@ -189,7 +213,7 @@
                       PalletInstance: STATEMINE_PALLET_INSTANCE,
                     },
                     {
-                      GeneralIndex: ASSET_ID,
+                      GeneralIndex: USDT_ASSET_ID,
                     },
                   ]},
               },
@@ -216,19 +240,17 @@
       expect(balanceStmnBefore > balanceStmnAfter).to.be.true;
 
     });
-
 
     // ensure that asset has been delivered
     await helper.wait.newBlocks(3);
 
-    // expext collection id will be with id 1
-    const free = await helper.ft.getBalance(1, {Substrate: alice.address});
+    const free = await helper.ft.getBalance(usdtCollectionId, {Substrate: alice.address});
 
     balanceOpalAfter = await helper.balance.getSubstrate(alice.address);
 
     console.log(
       '[Westmint -> Opal] transaction fees on Opal: %s USDT',
-      helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, ASSET_METADATA_DECIMALS),
+      helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),
     );
     console.log(
       '[Westmint -> Opal] transaction fees on Opal: %s OPL',
@@ -261,16 +283,11 @@
 
     const currencies: [any, bigint][] = [
       [
-        {
-          ForeignAssetId: 0,
-        },
-        //10_000_000_000_000_000n,
+        usdtCollectionId,
         TRANSFER_AMOUNT,
       ],
       [
-        {
-          NativeAssetId: 'Parent',
-        },
+        relayCollectionId,
         400_000_000_000_000n,
       ],
     ];
@@ -288,7 +305,7 @@
 
       // The USDT token never paid fees. Its amount not changed from begin value.
       // Also check that xcm transfer has been succeeded
-      expect((await helper.assets.account(ASSET_ID, alice.address))! == ASSET_AMOUNT).to.be.true;
+      expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;
     });
   });
 
@@ -296,7 +313,7 @@
     const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;
 
     balanceBobBefore = await helper.balance.getSubstrate(bob.address);
-    balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});
+    balanceBobRelayTokenBefore = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});
 
     // Providing the relay currency to the unique sender account
     await usingRelayPlaygrounds(relayUrl, async (helper) => {
@@ -345,7 +362,7 @@
     await helper.wait.newBlocks(3);
 
     balanceBobAfter = await helper.balance.getSubstrate(bob.address);
-    balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});
+    balanceBobRelayTokenAfter = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});
 
     const wndFee = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;
     console.log(
@@ -383,9 +400,7 @@
 
     const currencies: any = [
       [
-        {
-          NativeAssetId: 'Parent',
-        },
+        relayCollectionId,
         50_000_000_000_000_000n,
       ],
     ];
modifiedjs-packages/tests/xcm/xcmQuartz.test.tsdiffbeforeafterboth
after · js-packages/tests/xcm/xcmQuartz.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 type {IKeyringPair} from '@polkadot/types/types';18import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds, usingShidenPlaygrounds} from '../util/index.js';19import {DevUniqueHelper, Event} from '@unique/playgrounds/unique.dev.js';20import {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.js';21import {hexToString} from '@polkadot/util';22import {XcmTestHelper} from './xcm.types';2324const STATEMINE_PALLET_INSTANCE = 50;2526const TRANSFER_AMOUNT = 2000000000000000000000000n;2728const FUNDING_AMOUNT = 3_500_000_0000_000_000n;2930const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;3132const USDT_ASSET_ID = 100;33const USDT_ASSET_METADATA_DECIMALS = 18;34const USDT_ASSET_METADATA_NAME = 'USDT';35const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';36const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;37const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;3839const SAFE_XCM_VERSION = 2;4041const testHelper = new XcmTestHelper('quartz');4243describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => {44  let alice: IKeyringPair;45  let bob: IKeyringPair;4647  let balanceStmnBefore: bigint;48  let balanceStmnAfter: bigint;4950  let balanceQuartzBefore: bigint;51  let balanceQuartzAfter: bigint;52  let balanceQuartzFinal: bigint;5354  let balanceBobBefore: bigint;55  let balanceBobAfter: bigint;56  let balanceBobFinal: bigint;5758  let balanceBobRelayTokenBefore: bigint;59  let balanceBobRelayTokenAfter: bigint;6061  let usdtCollectionId: number;62  let relayCollectionId: number;6364  before(async () => {65    await usingPlaygrounds(async (helper, privateKey) => {66      alice = await privateKey('//Alice');67      bob = await privateKey('//Bob'); // sovereign account on Statemine(t) funds donor6869      // Set the default version to wrap the first message to other chains.70      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);7172      relayCollectionId = await testHelper.registerRelayNativeTokenOnUnique(alice);73    });7475    await usingRelayPlaygrounds(relayUrl, async (helper) => {76      // Fund accounts on Statemine(t)77      await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, alice.addressRaw, FUNDING_AMOUNT);78      await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, bob.addressRaw, FUNDING_AMOUNT);79    });8081    await usingStateminePlaygrounds(statemineUrl, async (helper) => {82      const assetInfo = await helper.assets.assetInfo(USDT_ASSET_ID);83      if(assetInfo == null) {84        await helper.assets.create(85          alice,86          USDT_ASSET_ID,87          alice.address,88          USDT_ASSET_METADATA_MINIMAL_BALANCE,89        );90        await helper.assets.setMetadata(91          alice,92          USDT_ASSET_ID,93          USDT_ASSET_METADATA_NAME,94          USDT_ASSET_METADATA_DESCRIPTION,95          USDT_ASSET_METADATA_DECIMALS,96        );97      } else {98        console.log('The USDT asset is already registered on AssetHub');99      }100101      await helper.assets.mint(102        alice,103        USDT_ASSET_ID,104        alice.address,105        USDT_ASSET_AMOUNT,106      );107108      const sovereignFundingAmount = 3_500_000_000n;109110      // funding parachain sovereing account on Statemine(t).111      // The sovereign account should be created before any action112      // (the assets pallet on Statemine(t) check if the sovereign account exists)113      const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(QUARTZ_CHAIN);114      await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);115    });116117118    await usingPlaygrounds(async (helper) => {119      const location = {120        parents: 1,121        interior: {X3: [122          {123            Parachain: STATEMINE_CHAIN,124          },125          {126            PalletInstance: STATEMINE_PALLET_INSTANCE,127          },128          {129            GeneralIndex: USDT_ASSET_ID,130          },131        ]},132      };133134      if(await helper.foreignAssets.foreignCollectionId(location) == null) {135        const tokenPrefix = USDT_ASSET_METADATA_NAME;136        await helper.getSudo().foreignAssets.register(alice, location, USDT_ASSET_METADATA_NAME, tokenPrefix, {Fungible: USDT_ASSET_METADATA_DECIMALS});137      } else {138        console.log('Foreign collection is already registered on Quartz');139      }140141      balanceQuartzBefore = await helper.balance.getSubstrate(alice.address);142      usdtCollectionId = await helper.foreignAssets.foreignCollectionId(location);143    });144145146    // Providing the relay currency to the quartz sender account147    // (fee for USDT XCM are paid in relay tokens)148    await usingRelayPlaygrounds(relayUrl, async (helper) => {149      const destination = {150        V2: {151          parents: 0,152          interior: {X1: {153            Parachain: QUARTZ_CHAIN,154          },155          },156        }};157158      const beneficiary = {159        V2: {160          parents: 0,161          interior: {X1: {162            AccountId32: {163              network: 'Any',164              id: alice.addressRaw,165            },166          }},167        },168      };169170      const assets = {171        V2: [172          {173            id: {174              Concrete: {175                parents: 0,176                interior: 'Here',177              },178            },179            fun: {180              Fungible: TRANSFER_AMOUNT_RELAY,181            },182          },183        ],184      };185186      const feeAssetItem = 0;187188      await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');189    });190191  });192193  itSub('Should connect and send USDT from Statemine to Quartz', async ({helper}) => {194    await usingStateminePlaygrounds(statemineUrl, async (helper) => {195      const dest = {196        V2: {197          parents: 1,198          interior: {X1: {199            Parachain: QUARTZ_CHAIN,200          },201          },202        }};203204      const beneficiary = {205        V2: {206          parents: 0,207          interior: {X1: {208            AccountId32: {209              network: 'Any',210              id: alice.addressRaw,211            },212          }},213        },214      };215216      const assets = {217        V2: [218          {219            id: {220              Concrete: {221                parents: 0,222                interior: {223                  X2: [224                    {225                      PalletInstance: STATEMINE_PALLET_INSTANCE,226                    },227                    {228                      GeneralIndex: USDT_ASSET_ID,229                    },230                  ]},231              },232            },233            fun: {234              Fungible: TRANSFER_AMOUNT,235            },236          },237        ],238      };239240      const feeAssetItem = 0;241242      balanceStmnBefore = await helper.balance.getSubstrate(alice.address);243      await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');244245      balanceStmnAfter = await helper.balance.getSubstrate(alice.address);246247      // common good parachain take commission in it native token248      console.log(249        '[Statemine -> Quartz] transaction fees on Statemine: %s WND',250        helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINE_DECIMALS),251      );252      expect(balanceStmnBefore > balanceStmnAfter).to.be.true;253254    });255256257    // ensure that asset has been delivered258    await helper.wait.newBlocks(3);259260    const free = await helper.ft.getBalance(usdtCollectionId, {Substrate: alice.address});261262    balanceQuartzAfter = await helper.balance.getSubstrate(alice.address);263264    console.log(265      '[Statemine -> Quartz] transaction fees on Quartz: %s USDT',266      helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),267    );268    console.log(269      '[Statemine -> Quartz] transaction fees on Quartz: %s QTZ',270      helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzBefore),271    );272    // commission has not paid in USDT token273    expect(free).to.be.equal(TRANSFER_AMOUNT);274    // ... and parachain native token275    expect(balanceQuartzAfter == balanceQuartzBefore).to.be.true;276  });277278  itSub('Should connect and send USDT from Quartz to Statemine back', async ({helper}) => {279    const destination = {280      V2: {281        parents: 1,282        interior: {X2: [283          {284            Parachain: STATEMINE_CHAIN,285          },286          {287            AccountId32: {288              network: 'Any',289              id: alice.addressRaw,290            },291          },292        ]},293      },294    };295296    const relayFee = 400_000_000_000_000n;297    const currencies: [any, bigint][] = [298      [299        usdtCollectionId,300        TRANSFER_AMOUNT,301      ],302      [303        relayCollectionId,304        relayFee,305      ],306    ];307308    const feeItem = 1;309310    await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');311312    // the commission has been paid in parachain native token313    balanceQuartzFinal = await helper.balance.getSubstrate(alice.address);314    console.log('[Quartz -> Statemine] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzFinal));315    expect(balanceQuartzAfter > balanceQuartzFinal).to.be.true;316317    await usingStateminePlaygrounds(statemineUrl, async (helper) => {318      await helper.wait.newBlocks(3);319320      // The USDT token never paid fees. Its amount not changed from begin value.321      // Also check that xcm transfer has been succeeded322      expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;323    });324  });325326  itSub('Should connect and send Relay token to Quartz', async ({helper}) => {327    balanceBobBefore = await helper.balance.getSubstrate(bob.address);328    balanceBobRelayTokenBefore = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});329330    await usingRelayPlaygrounds(relayUrl, async (helper) => {331      const destination = {332        V2: {333          parents: 0,334          interior: {X1: {335            Parachain: QUARTZ_CHAIN,336          },337          },338        }};339340      const beneficiary = {341        V2: {342          parents: 0,343          interior: {X1: {344            AccountId32: {345              network: 'Any',346              id: bob.addressRaw,347            },348          }},349        },350      };351352      const assets = {353        V2: [354          {355            id: {356              Concrete: {357                parents: 0,358                interior: 'Here',359              },360            },361            fun: {362              Fungible: TRANSFER_AMOUNT_RELAY,363            },364          },365        ],366      };367368      const feeAssetItem = 0;369370      await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');371    });372373    await helper.wait.newBlocks(3);374375    balanceBobAfter = await helper.balance.getSubstrate(bob.address);376    balanceBobRelayTokenAfter = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});377378    const wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;379    const wndDiffOnQuartz = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;380    console.log(381      '[Relay (Westend) -> Quartz] transaction fees: %s QTZ',382      helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),383    );384    console.log(385      '[Relay (Westend) -> Quartz] transaction fees: %s WND',386      helper.util.bigIntToDecimals(wndFeeOnQuartz, STATEMINE_DECIMALS),387    );388    console.log('[Relay (Westend) -> Quartz] actually delivered: %s WND', wndDiffOnQuartz);389    expect(wndFeeOnQuartz == 0n, 'No incoming WND fees should be taken').to.be.true;390    expect(balanceBobBefore == balanceBobAfter, 'No incoming QTZ fees should be taken').to.be.true;391  });392393  itSub('Should connect and send Relay token back', async ({helper}) => {394    let relayTokenBalanceBefore: bigint;395    let relayTokenBalanceAfter: bigint;396    await usingRelayPlaygrounds(relayUrl, async (helper) => {397      relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);398    });399400    const destination = {401      V2: {402        parents: 1,403        interior: {404          X1:{405            AccountId32: {406              network: 'Any',407              id: bob.addressRaw,408            },409          },410        },411      },412    };413414    const currencies: any = [415      [416        relayCollectionId,417        TRANSFER_AMOUNT_RELAY,418      ],419    ];420421    const feeItem = 0;422423    await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');424425    balanceBobFinal = await helper.balance.getSubstrate(bob.address);426    console.log('[Quartz -> Relay (Westend)] transaction fees: %s QTZ',  helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));427428    await usingRelayPlaygrounds(relayUrl, async (helper) => {429      await helper.wait.newBlocks(10);430      relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);431432      const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;433      console.log('[Quartz -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));434      expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;435    });436  });437});438439describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => {440  let alice: IKeyringPair;441  let randomAccount: IKeyringPair;442443  let balanceQuartzTokenInit: bigint;444  let balanceQuartzTokenMiddle: bigint;445  let balanceQuartzTokenFinal: bigint;446  let balanceKaruraTokenInit: bigint;447  let balanceKaruraTokenMiddle: bigint;448  let balanceKaruraTokenFinal: bigint;449  let balanceQuartzForeignTokenInit: bigint;450  let balanceQuartzForeignTokenMiddle: bigint;451  let balanceQuartzForeignTokenFinal: bigint;452453  // computed by a test transfer from prod Quartz to prod Karura.454  // 2 QTZ sent https://quartz.subscan.io/xcm_message/kusama-f60d821b049f8835a3005ce7102285006f5b61e9455  // 1.919176000000000000 QTZ received (you can check Karura's chain state in the corresponding block)456  const expectedKaruraIncomeFee = 2000000000000000000n - 1919176000000000000n;457  const karuraEps = 8n * 10n ** 16n;458459  let karuraBackwardTransferAmount: bigint;460461  before(async () => {462    await usingPlaygrounds(async (helper, privateKey) => {463      alice = await privateKey('//Alice');464      [randomAccount] = await helper.arrange.createAccounts([0n], alice);465466      // Set the default version to wrap the first message to other chains.467      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);468    });469470    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {471      const destination = {472        V2: {473          parents: 1,474          interior: {475            X1: {476              Parachain: QUARTZ_CHAIN,477            },478          },479        },480      };481482      const metadata = {483        name: 'Quartz',484        symbol: 'QTZ',485        decimals: 18,486        minimalBalance: 1000000000000000000n,487      };488489      const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v]: [any, any]) =>490        hexToString(v.toJSON()['symbol'])) as string[];491492      if(!assets.includes('QTZ')) {493        await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);494      } else {495        console.log('QTZ token already registered on Karura assetRegistry pallet');496      }497      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);498      balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);499      balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});500    });501502    await usingPlaygrounds(async (helper) => {503      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);504      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);505    });506  });507508  itSub('Should connect and send QTZ to Karura', async ({helper}) => {509    const destination = {510      V2: {511        parents: 1,512        interior: {513          X1: {514            Parachain: KARURA_CHAIN,515          },516        },517      },518    };519520    const beneficiary = {521      V2: {522        parents: 0,523        interior: {524          X1: {525            AccountId32: {526              network: 'Any',527              id: randomAccount.addressRaw,528            },529          },530        },531      },532    };533534    const assets = {535      V2: [536        {537          id: {538            Concrete: {539              parents: 0,540              interior: 'Here',541            },542          },543          fun: {544            Fungible: TRANSFER_AMOUNT,545          },546        },547      ],548    };549550    const feeAssetItem = 0;551552    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');553    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);554555    const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;556    expect(qtzFees > 0n, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;557    console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));558559    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {560      await helper.wait.newBlocks(3);561562      balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});563      balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);564565      const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;566      const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;567      karuraBackwardTransferAmount = qtzIncomeTransfer;568569      const karUnqFees = TRANSFER_AMOUNT - qtzIncomeTransfer;570571      console.log(572        '[Quartz -> Karura] transaction fees on Karura: %s KAR',573        helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),574      );575      console.log(576        '[Quartz -> Karura] transaction fees on Karura: %s QTZ',577        helper.util.bigIntToDecimals(karUnqFees),578      );579      console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));580      expect(karFees == 0n).to.be.true;581582      const bigintAbs = (n: bigint) => (n < 0n) ? -n : n;583584      expect(585        bigintAbs(karUnqFees - expectedKaruraIncomeFee) < karuraEps,586        'Karura took different income fee, check the Karura foreign asset config',587      ).to.be.true;588    });589  });590591  itSub('Should connect to Karura and send QTZ back', async ({helper}) => {592    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {593      const destination = {594        V2: {595          parents: 1,596          interior: {597            X2: [598              {Parachain: QUARTZ_CHAIN},599              {600                AccountId32: {601                  network: 'Any',602                  id: randomAccount.addressRaw,603                },604              },605            ],606          },607        },608      };609610      const id = {611        ForeignAsset: 0,612      };613614      await helper.xTokens.transfer(randomAccount, id, karuraBackwardTransferAmount, destination, 'Unlimited');615      balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);616      balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);617618      const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;619      const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;620621      console.log(622        '[Karura -> Quartz] transaction fees on Karura: %s KAR',623        helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),624      );625      console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));626627      expect(karFees > 0, 'Negative fees KAR, looks like nothing was transferred').to.be.true;628      expect(qtzOutcomeTransfer == karuraBackwardTransferAmount).to.be.true;629    });630631    await helper.wait.newBlocks(3);632633    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);634    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;635    expect(actuallyDelivered > 0).to.be.true;636637    console.log('[Karura -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));638639    const qtzFees = karuraBackwardTransferAmount - actuallyDelivered;640    console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));641    expect(qtzFees == 0n).to.be.true;642  });643644  itSub('Karura can send only up to its balance', async ({helper}) => {645    // set Karura's sovereign account's balance646    const karuraBalance = 10000n * (10n ** QTZ_DECIMALS);647    const karuraSovereignAccount = helper.address.paraSiblingSovereignAccount(KARURA_CHAIN);648    await helper.getSudo().balance.setBalanceSubstrate(alice, karuraSovereignAccount, karuraBalance);649650    const moreThanKaruraHas = karuraBalance * 2n;651652    let targetAccountBalance = 0n;653    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);654655    const quartzMultilocation = {656      V2: {657        parents: 1,658        interior: {659          X1: {Parachain: QUARTZ_CHAIN},660        },661      },662    };663664    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(665      targetAccount.addressRaw,666      {667        Concrete: {668          parents: 0,669          interior: 'Here',670        },671      },672      moreThanKaruraHas,673    );674675    let maliciousXcmProgramSent: any;676    const maxWaitBlocks = 5;677678    // Try to trick Quartz679    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {680      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);681682      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);683    });684685    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash686        && event.outcome.isFailedToTransactAsset);687688    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);689    expect(targetAccountBalance).to.be.equal(0n);690691    // But Karura still can send the correct amount692    const validTransferAmount = karuraBalance / 2n;693    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(694      targetAccount.addressRaw,695      {696        Concrete: {697          parents: 0,698          interior: 'Here',699        },700      },701      validTransferAmount,702    );703704    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {705      await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);706    });707708    await helper.wait.newBlocks(maxWaitBlocks);709710    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);711    expect(targetAccountBalance).to.be.equal(validTransferAmount);712  });713714  itSub('Should not accept reserve transfer of QTZ from Karura', async ({helper}) => {715    const testAmount = 10_000n * (10n ** QTZ_DECIMALS);716    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);717718    const quartzMultilocation = {719      V2: {720        parents: 1,721        interior: {722          X1: {723            Parachain: QUARTZ_CHAIN,724          },725        },726      },727    };728729    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(730      targetAccount.addressRaw,731      {732        Concrete: {733          parents: 1,734          interior: {735            X1: {736              Parachain: QUARTZ_CHAIN,737            },738          },739        },740      },741      testAmount,742    );743744    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(745      targetAccount.addressRaw,746      {747        Concrete: {748          parents: 0,749          interior: 'Here',750        },751      },752      testAmount,753    );754755    let maliciousXcmProgramFullIdSent: any;756    let maliciousXcmProgramHereIdSent: any;757    const maxWaitBlocks = 3;758759    // Try to trick Quartz using full QTZ identification760    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {761      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);762763      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);764    });765766    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash767        && event.outcome.isUntrustedReserveLocation);768769    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);770    expect(accountBalance).to.be.equal(0n);771772    // Try to trick Quartz using shortened QTZ identification773    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {774      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);775776      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);777    });778779    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash780        && event.outcome.isUntrustedReserveLocation);781782    accountBalance = await helper.balance.getSubstrate(targetAccount.address);783    expect(accountBalance).to.be.equal(0n);784  });785});786787// These tests are relevant only when788// the the corresponding foreign assets are not registered789describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => {790  let alice: IKeyringPair;791  let alith: IKeyringPair;792793  const testAmount = 100_000_000_000n;794  let quartzParachainJunction;795  let quartzAccountJunction;796797  let quartzParachainMultilocation: any;798  let quartzAccountMultilocation: any;799  let quartzCombinedMultilocation: any;800801  let messageSent: any;802803  const maxWaitBlocks = 3;804805  before(async () => {806    await usingPlaygrounds(async (helper, privateKey) => {807      alice = await privateKey('//Alice');808809      quartzParachainJunction = {Parachain: QUARTZ_CHAIN};810      quartzAccountJunction = {811        AccountId32: {812          network: 'Any',813          id: alice.addressRaw,814        },815      };816817      quartzParachainMultilocation = {818        V2: {819          parents: 1,820          interior: {821            X1: quartzParachainJunction,822          },823        },824      };825826      quartzAccountMultilocation = {827        V2: {828          parents: 0,829          interior: {830            X1: quartzAccountJunction,831          },832        },833      };834835      quartzCombinedMultilocation = {836        V2: {837          parents: 1,838          interior: {839            X2: [quartzParachainJunction, quartzAccountJunction],840          },841        },842      };843844      // Set the default version to wrap the first message to other chains.845      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);846    });847848    // eslint-disable-next-line require-await849    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {850      alith = helper.account.alithAccount();851    });852  });853854  const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {855    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash856        && event.outcome.isFailedToTransactAsset);857  };858859  itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {860    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {861      const id = {862        Token: 'KAR',863      };864      const destination = quartzCombinedMultilocation;865      await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');866867      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);868    });869870    await expectFailedToTransact(helper, messageSent);871  });872873  itSub('Quartz rejects MOVR tokens from Moonriver', async ({helper}) => {874    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {875      const id = 'SelfReserve';876      const destination = quartzCombinedMultilocation;877      await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');878879      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);880    });881882    await expectFailedToTransact(helper, messageSent);883  });884885  itSub('Quartz rejects SDN tokens from Shiden', async ({helper}) => {886    await usingShidenPlaygrounds(shidenUrl, async (helper) => {887      const destinationParachain = quartzParachainMultilocation;888      const beneficiary = quartzAccountMultilocation;889      const assets = {890        V2: [{891          id: {892            Concrete: {893              parents: 0,894              interior: 'Here',895            },896          },897          fun: {898            Fungible: testAmount,899          },900        }],901      };902      const feeAssetItem = 0;903904      await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [905        destinationParachain,906        beneficiary,907        assets,908        feeAssetItem,909      ]);910911      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);912    });913914    await expectFailedToTransact(helper, messageSent);915  });916});917918describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {919  // Quartz constants920  let alice: IKeyringPair;921  let quartzAssetLocation;922923  let randomAccountQuartz: IKeyringPair;924  let randomAccountMoonriver: IKeyringPair;925926  // Moonriver constants927  let assetId: string;928929  const quartzAssetMetadata = {930    name: 'xcQuartz',931    symbol: 'xcQTZ',932    decimals: 18,933    isFrozen: false,934    minimalBalance: 1n,935  };936937  let balanceQuartzTokenInit: bigint;938  let balanceQuartzTokenMiddle: bigint;939  let balanceQuartzTokenFinal: bigint;940  let balanceForeignQtzTokenInit: bigint;941  let balanceForeignQtzTokenMiddle: bigint;942  let balanceForeignQtzTokenFinal: bigint;943  let balanceMovrTokenInit: bigint;944  let balanceMovrTokenMiddle: bigint;945  let balanceMovrTokenFinal: bigint;946947  before(async () => {948    await usingPlaygrounds(async (helper, privateKey) => {949      alice = await privateKey('//Alice');950      [randomAccountQuartz] = await helper.arrange.createAccounts([0n], alice);951952      balanceForeignQtzTokenInit = 0n;953954      // Set the default version to wrap the first message to other chains.955      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);956    });957958    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {959      const alithAccount = helper.account.alithAccount();960      const baltatharAccount = helper.account.baltatharAccount();961      const dorothyAccount = helper.account.dorothyAccount();962963      randomAccountMoonriver = helper.account.create();964965      // >>> Sponsoring Dorothy >>>966      console.log('Sponsoring Dorothy.......');967      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);968      console.log('Sponsoring Dorothy.......DONE');969      // <<< Sponsoring Dorothy <<<970971      quartzAssetLocation = {972        XCM: {973          parents: 1,974          interior: {X1: {Parachain: QUARTZ_CHAIN}},975        },976      };977      const existentialDeposit = 1n;978      const isSufficient = true;979      const unitsPerSecond = 1n;980      const numAssetsWeightHint = 0;981982      if((await helper.assetManager.assetTypeId(quartzAssetLocation)).toJSON()) {983        console.log('Quartz asset already registered on Moonriver');984      } else {985        const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({986          location: quartzAssetLocation,987          metadata: quartzAssetMetadata,988          existentialDeposit,989          isSufficient,990          unitsPerSecond,991          numAssetsWeightHint,992        });993994        console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);995996        await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);997      }998      // >>> Acquire Quartz AssetId Info on Moonriver >>>999      console.log('Acquire Quartz AssetId Info on Moonriver.......');10001001      assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();10021003      console.log('QTZ asset ID is %s', assetId);1004      console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');1005      // >>> Acquire Quartz AssetId Info on Moonriver >>>10061007      // >>> Sponsoring random Account >>>1008      console.log('Sponsoring random Account.......');1009      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);1010      console.log('Sponsoring random Account.......DONE');1011      // <<< Sponsoring random Account <<<10121013      balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);1014    });10151016    await usingPlaygrounds(async (helper) => {1017      await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);1018      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);1019    });1020  });10211022  itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {1023    const currencyId = {1024      NativeAssetId: 'Here',1025    };1026    const dest = {1027      V2: {1028        parents: 1,1029        interior: {1030          X2: [1031            {Parachain: MOONRIVER_CHAIN},1032            {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},1033          ],1034        },1035      },1036    };1037    const amount = TRANSFER_AMOUNT;10381039    await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, 'Unlimited');10401041    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);1042    expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;10431044    const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;1045    console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));1046    expect(transactionFees > 0, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;10471048    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1049      await helper.wait.newBlocks(3);10501051      balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);10521053      const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;1054      console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));1055      expect(movrFees == 0n).to.be.true;10561057      balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; // BigInt(qtzRandomAccountAsset['balance']);1058      const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;1059      console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));1060      expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1061    });1062  });10631064  itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {1065    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1066      const asset = {1067        V2: {1068          id: {1069            Concrete: {1070              parents: 1,1071              interior: {1072                X1: {Parachain: QUARTZ_CHAIN},1073              },1074            },1075          },1076          fun: {1077            Fungible: TRANSFER_AMOUNT,1078          },1079        },1080      };1081      const destination = {1082        V2: {1083          parents: 1,1084          interior: {1085            X2: [1086              {Parachain: QUARTZ_CHAIN},1087              {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},1088            ],1089          },1090        },1091      };10921093      await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, 'Unlimited');10941095      balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);10961097      const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;1098      console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));1099      expect(movrFees > 0, 'Negative fees MOVR, looks like nothing was transferred').to.be.true;11001101      const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);11021103      expect(qtzRandomAccountAsset).to.be.null;11041105      balanceForeignQtzTokenFinal = 0n;11061107      const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;1108      console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));1109      expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1110    });11111112    await helper.wait.newBlocks(3);11131114    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);1115    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;1116    expect(actuallyDelivered > 0).to.be.true;11171118    console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));11191120    const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;1121    console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));1122    expect(qtzFees == 0n).to.be.true;1123  });11241125  itSub('Moonriver can send only up to its balance', async ({helper}) => {1126    // set Moonriver's sovereign account's balance1127    const moonriverBalance = 10000n * (10n ** QTZ_DECIMALS);1128    const moonriverSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONRIVER_CHAIN);1129    await helper.getSudo().balance.setBalanceSubstrate(alice, moonriverSovereignAccount, moonriverBalance);11301131    const moreThanMoonriverHas = moonriverBalance * 2n;11321133    let targetAccountBalance = 0n;1134    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);11351136    const quartzMultilocation = {1137      V2: {1138        parents: 1,1139        interior: {1140          X1: {Parachain: QUARTZ_CHAIN},1141        },1142      },1143    };11441145    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1146      targetAccount.addressRaw,1147      {1148        Concrete: {1149          parents: 0,1150          interior: 'Here',1151        },1152      },1153      moreThanMoonriverHas,1154    );11551156    let maliciousXcmProgramSent: any;1157    const maxWaitBlocks = 3;11581159    // Try to trick Quartz1160    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1161      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgram]);11621163      // Needed to bypass the call filter.1164      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1165      await helper.fastDemocracy.executeProposal('try to spend more QTZ than Moonriver has', batchCall);11661167      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1168    });11691170    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1171        && event.outcome.isFailedToTransactAsset);11721173    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1174    expect(targetAccountBalance).to.be.equal(0n);11751176    // But Moonriver still can send the correct amount1177    const validTransferAmount = moonriverBalance / 2n;1178    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1179      targetAccount.addressRaw,1180      {1181        Concrete: {1182          parents: 0,1183          interior: 'Here',1184        },1185      },1186      validTransferAmount,1187    );11881189    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1190      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, validXcmProgram]);11911192      // Needed to bypass the call filter.1193      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1194      await helper.fastDemocracy.executeProposal('Spend the correct amount of QTZ', batchCall);1195    });11961197    await helper.wait.newBlocks(maxWaitBlocks);11981199    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1200    expect(targetAccountBalance).to.be.equal(validTransferAmount);1201  });12021203  itSub('Should not accept reserve transfer of QTZ from Moonriver', async ({helper}) => {1204    const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1205    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);12061207    const quartzMultilocation = {1208      V2: {1209        parents: 1,1210        interior: {1211          X1: {1212            Parachain: QUARTZ_CHAIN,1213          },1214        },1215      },1216    };12171218    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1219      targetAccount.addressRaw,1220      {1221        Concrete: {1222          parents: 0,1223          interior: {1224            X1: {1225              Parachain: QUARTZ_CHAIN,1226            },1227          },1228        },1229      },1230      testAmount,1231    );12321233    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1234      targetAccount.addressRaw,1235      {1236        Concrete: {1237          parents: 0,1238          interior: 'Here',1239        },1240      },1241      testAmount,1242    );12431244    let maliciousXcmProgramFullIdSent: any;1245    let maliciousXcmProgramHereIdSent: any;1246    const maxWaitBlocks = 3;12471248    // Try to trick Quartz using full QTZ identification1249    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1250      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramFullId]);12511252      // Needed to bypass the call filter.1253      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1254      await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using path asset identification', batchCall);12551256      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1257    });12581259    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1260        && event.outcome.isUntrustedReserveLocation);12611262    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1263    expect(accountBalance).to.be.equal(0n);12641265    // Try to trick Quartz using shortened QTZ identification1266    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1267      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramHereId]);12681269      // Needed to bypass the call filter.1270      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1271      await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using "here" asset identification', batchCall);12721273      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1274    });12751276    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1277        && event.outcome.isUntrustedReserveLocation);12781279    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1280    expect(accountBalance).to.be.equal(0n);1281  });1282});12831284describeXCM('[XCM] Integration test: Exchanging tokens with Shiden', () => {1285  let alice: IKeyringPair;1286  let sender: IKeyringPair;12871288  const QTZ_ASSET_ID_ON_SHIDEN = 18_446_744_073_709_551_633n; // The value is taken from the live Shiden1289  const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n; // The value is taken from the live Shiden12901291  // Quartz -> Shiden1292  const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); // 1 SHD, existential deposit required to actually create the account on Shiden1293  const unitsPerSecond = 500_451_000_000_000_000_000n; // The value is taken from the live Shiden1294  const qtzToShidenTransferred = 10n * (10n ** QTZ_DECIMALS); // 10 QTZ1295  const qtzToShidenArrived = 7_998_196_000_000_000_000n; // 7.99 ... QTZ, Shiden takes a commision in foreign tokens12961297  // Shiden -> Quartz1298  const qtzFromShidenTransfered = 5n * (10n ** QTZ_DECIMALS); // 5 QTZ1299  const qtzOnShidenLeft = qtzToShidenArrived - qtzFromShidenTransfered; // 2.99 ... QTZ13001301  let balanceAfterQuartzToShidenXCM: bigint;13021303  before(async () => {1304    await usingPlaygrounds(async (helper, privateKey) => {1305      alice = await privateKey('//Alice');1306      [sender] = await helper.arrange.createAccounts([100n], alice);1307      console.log('sender', sender.address);13081309      // Set the default version to wrap the first message to other chains.1310      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1311    });13121313    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1314      if(!(await helper.callRpc('api.query.assets.asset', [QTZ_ASSET_ID_ON_SHIDEN])).toJSON()) {1315        console.log('1. Create foreign asset and metadata');1316        await helper.getSudo().assets.forceCreate(1317          alice,1318          QTZ_ASSET_ID_ON_SHIDEN,1319          alice.address,1320          QTZ_MINIMAL_BALANCE_ON_SHIDEN,1321        );13221323        await helper.assets.setMetadata(1324          alice,1325          QTZ_ASSET_ID_ON_SHIDEN,1326          'Quartz',1327          'QTZ',1328          Number(QTZ_DECIMALS),1329        );13301331        console.log('2. Register asset location on Shiden');1332        const assetLocation = {1333          V2: {1334            parents: 1,1335            interior: {1336              X1: {1337                Parachain: QUARTZ_CHAIN,1338              },1339            },1340          },1341        };13421343        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]);13441345        console.log('3. Set QTZ payment for XCM execution on Shiden');1346        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);1347      } else {1348        console.log('QTZ is already registered on Shiden');1349      }1350      console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)');1351      await helper.balance.transferToSubstrate(alice, sender.address, shidenInitialBalance);1352    });1353  });13541355  itSub('Should connect and send QTZ to Shiden', async ({helper}) => {1356    const destination = {1357      V2: {1358        parents: 1,1359        interior: {1360          X1: {1361            Parachain: SHIDEN_CHAIN,1362          },1363        },1364      },1365    };13661367    const beneficiary = {1368      V2: {1369        parents: 0,1370        interior: {1371          X1: {1372            AccountId32: {1373              network: 'Any',1374              id: sender.addressRaw,1375            },1376          },1377        },1378      },1379    };13801381    const assets = {1382      V2: [1383        {1384          id: {1385            Concrete: {1386              parents: 0,1387              interior: 'Here',1388            },1389          },1390          fun: {1391            Fungible: qtzToShidenTransferred,1392          },1393        },1394      ],1395    };13961397    // Initial balance is 100 QTZ1398    const balanceBefore = await helper.balance.getSubstrate(sender.address);1399    console.log(`Initial balance is: ${balanceBefore}`);14001401    const feeAssetItem = 0;1402    await helper.xcm.limitedReserveTransferAssets(sender, destination, beneficiary, assets, feeAssetItem, 'Unlimited');14031404    // Balance after reserve transfer is less than 901405    balanceAfterQuartzToShidenXCM = await helper.balance.getSubstrate(sender.address);1406    console.log(`QTZ Balance on Quartz after XCM is: ${balanceAfterQuartzToShidenXCM}`);1407    console.log(`Quartz's QTZ commission is: ${balanceBefore - balanceAfterQuartzToShidenXCM}`);1408    expect(balanceBefore - balanceAfterQuartzToShidenXCM > 0).to.be.true;14091410    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1411      await helper.wait.newBlocks(3);1412      const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1413      const shidenBalance = await helper.balance.getSubstrate(sender.address);14141415      console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);1416      console.log(`Shiden's QTZ commission is: ${qtzToShidenTransferred - xcQTZbalance!}`);14171418      expect(xcQTZbalance).to.eq(qtzToShidenArrived);1419      // SHD balance does not changed:1420      expect(shidenBalance).to.eq(shidenInitialBalance);1421    });1422  });14231424  itSub('Should connect to Shiden and send QTZ back', async ({helper}) => {1425    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1426      const destination = {1427        V2: {1428          parents: 1,1429          interior: {1430            X1: {1431              Parachain: QUARTZ_CHAIN,1432            },1433          },1434        },1435      };14361437      const beneficiary = {1438        V2: {1439          parents: 0,1440          interior: {1441            X1: {1442              AccountId32: {1443                network: 'Any',1444                id: sender.addressRaw,1445              },1446            },1447          },1448        },1449      };14501451      const assets = {1452        V2: [1453          {1454            id: {1455              Concrete: {1456                parents: 1,1457                interior: {1458                  X1: {1459                    Parachain: QUARTZ_CHAIN,1460                  },1461                },1462              },1463            },1464            fun: {1465              Fungible: qtzFromShidenTransfered,1466            },1467          },1468        ],1469      };14701471      // Initial balance is 1 SDN1472      const balanceSDNbefore = await helper.balance.getSubstrate(sender.address);1473      console.log(`SDN balance is: ${balanceSDNbefore}, it does not changed`);1474      expect(balanceSDNbefore).to.eq(shidenInitialBalance);14751476      const feeAssetItem = 0;1477      // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1478      await helper.executeExtrinsic(sender, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);14791480      // Balance after reserve transfer is less than 1 SDN1481      const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1482      const balanceSDN = await helper.balance.getSubstrate(sender.address);1483      console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);14841485      // Assert: xcQTZ balance correctly decreased1486      expect(xcQTZbalance).to.eq(qtzOnShidenLeft);1487      // Assert: SDN balance is 0.996...1488      expect(balanceSDN / (10n ** (SHIDEN_DECIMALS - 3n))).to.eq(996n);1489    });14901491    await helper.wait.newBlocks(3);1492    const balanceQTZ = await helper.balance.getSubstrate(sender.address);1493    console.log(`QTZ Balance on Quartz after XCM is: ${balanceQTZ}`);1494    expect(balanceQTZ).to.eq(balanceAfterQuartzToShidenXCM + qtzFromShidenTransfered);1495  });14961497  itSub('Shiden can send only up to its balance', async ({helper}) => {1498    // set Shiden's sovereign account's balance1499    const shidenBalance = 10000n * (10n ** QTZ_DECIMALS);1500    const shidenSovereignAccount = helper.address.paraSiblingSovereignAccount(SHIDEN_CHAIN);1501    await helper.getSudo().balance.setBalanceSubstrate(alice, shidenSovereignAccount, shidenBalance);15021503    const moreThanShidenHas = shidenBalance * 2n;15041505    let targetAccountBalance = 0n;1506    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);15071508    const quartzMultilocation = {1509      V2: {1510        parents: 1,1511        interior: {1512          X1: {Parachain: QUARTZ_CHAIN},1513        },1514      },1515    };15161517    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1518      targetAccount.addressRaw,1519      {1520        Concrete: {1521          parents: 0,1522          interior: 'Here',1523        },1524      },1525      moreThanShidenHas,1526    );15271528    let maliciousXcmProgramSent: any;1529    const maxWaitBlocks = 3;15301531    // Try to trick Quartz1532    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1533      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);15341535      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1536    });15371538    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1539        && event.outcome.isFailedToTransactAsset);15401541    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1542    expect(targetAccountBalance).to.be.equal(0n);15431544    // But Shiden still can send the correct amount1545    const validTransferAmount = shidenBalance / 2n;1546    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1547      targetAccount.addressRaw,1548      {1549        Concrete: {1550          parents: 0,1551          interior: 'Here',1552        },1553      },1554      validTransferAmount,1555    );15561557    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1558      await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);1559    });15601561    await helper.wait.newBlocks(maxWaitBlocks);15621563    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1564    expect(targetAccountBalance).to.be.equal(validTransferAmount);1565  });15661567  itSub('Should not accept reserve transfer of QTZ from Shiden', async ({helper}) => {1568    const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1569    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);15701571    const quartzMultilocation = {1572      V2: {1573        parents: 1,1574        interior: {1575          X1: {1576            Parachain: QUARTZ_CHAIN,1577          },1578        },1579      },1580    };15811582    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1583      targetAccount.addressRaw,1584      {1585        Concrete: {1586          parents: 1,1587          interior: {1588            X1: {1589              Parachain: QUARTZ_CHAIN,1590            },1591          },1592        },1593      },1594      testAmount,1595    );15961597    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1598      targetAccount.addressRaw,1599      {1600        Concrete: {1601          parents: 0,1602          interior: 'Here',1603        },1604      },1605      testAmount,1606    );16071608    let maliciousXcmProgramFullIdSent: any;1609    let maliciousXcmProgramHereIdSent: any;1610    const maxWaitBlocks = 3;16111612    // Try to trick Quartz using full QTZ identification1613    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1614      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);16151616      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1617    });16181619    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1620        && event.outcome.isUntrustedReserveLocation);16211622    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1623    expect(accountBalance).to.be.equal(0n);16241625    // Try to trick Quartz using shortened QTZ identification1626    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1627      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);16281629      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1630    });16311632    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1633        && event.outcome.isUntrustedReserveLocation);16341635    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1636    expect(accountBalance).to.be.equal(0n);1637  });1638});
modifiedjs-packages/tests/xcm/xcmUnique.test.tsdiffbeforeafterboth
--- a/js-packages/tests/xcm/xcmUnique.test.ts
+++ b/js-packages/tests/xcm/xcmUnique.test.ts
@@ -20,7 +20,7 @@
 import {Event} from '@unique/playgrounds/unique.dev.js';
 import {hexToString, nToBigInt} from '@polkadot/util';
 import {ACALA_CHAIN, ASTAR_CHAIN, MOONBEAM_CHAIN, POLKADEX_CHAIN, SAFE_XCM_VERSION, STATEMINT_CHAIN, UNIQUE_CHAIN, expectFailedToTransact, expectUntrustedReserveLocationFail, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types.js';
-
+import {XcmTestHelper} from './xcm.types';
 
 const STATEMINT_PALLET_INSTANCE = 50;
 
@@ -50,6 +50,8 @@
 const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;
 const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;
 
+const testHelper = new XcmTestHelper('unique');
+
 describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => {
   let alice: IKeyringPair;
   let bob: IKeyringPair;
@@ -68,6 +70,8 @@
   let balanceBobRelayTokenBefore: bigint;
   let balanceBobRelayTokenAfter: bigint;
 
+  let usdtCollectionId: number;
+  let relayCollectionId: number;
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
@@ -76,6 +80,8 @@
 
       // Set the default version to wrap the first message to other chains.
       await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);
+
+      relayCollectionId = await testHelper.registerRelayNativeTokenOnUnique(alice);
     });
 
     await usingRelayPlaygrounds(relayUrl, async (helper) => {
@@ -85,21 +91,25 @@
     });
 
     await usingStatemintPlaygrounds(statemintUrl, async (helper) => {
-      const sovereignFundingAmount = 3_500_000_000n;
+      const assetInfo = await helper.assets.assetInfo(USDT_ASSET_ID);
+      if(assetInfo == null) {
+        await helper.assets.create(
+          alice,
+          USDT_ASSET_ID,
+          alice.address,
+          USDT_ASSET_METADATA_MINIMAL_BALANCE,
+        );
+        await helper.assets.setMetadata(
+          alice,
+          USDT_ASSET_ID,
+          USDT_ASSET_METADATA_NAME,
+          USDT_ASSET_METADATA_DESCRIPTION,
+          USDT_ASSET_METADATA_DECIMALS,
+        );
+      } else {
+        console.log('The USDT asset is already registered on AssetHub');
+      }
 
-      await helper.assets.create(
-        alice,
-        USDT_ASSET_ID,
-        alice.address,
-        USDT_ASSET_METADATA_MINIMAL_BALANCE,
-      );
-      await helper.assets.setMetadata(
-        alice,
-        USDT_ASSET_ID,
-        USDT_ASSET_METADATA_NAME,
-        USDT_ASSET_METADATA_DESCRIPTION,
-        USDT_ASSET_METADATA_DECIMALS,
-      );
       await helper.assets.mint(
         alice,
         USDT_ASSET_ID,
@@ -107,6 +117,8 @@
         USDT_ASSET_AMOUNT,
       );
 
+      const sovereignFundingAmount = 3_500_000_000n;
+
       // funding parachain sovereing account on Statemint.
       // The sovereign account should be created before any action
       // (the assets pallet on Statemint check if the sovereign account exists)
@@ -117,31 +129,29 @@
 
     await usingPlaygrounds(async (helper) => {
       const location = {
-        V2: {
-          parents: 1,
-          interior: {X3: [
-            {
-              Parachain: STATEMINT_CHAIN,
-            },
-            {
-              PalletInstance: STATEMINT_PALLET_INSTANCE,
-            },
-            {
-              GeneralIndex: USDT_ASSET_ID,
-            },
-          ]},
-        },
+        parents: 1,
+        interior: {X3: [
+          {
+            Parachain: STATEMINT_CHAIN,
+          },
+          {
+            PalletInstance: STATEMINT_PALLET_INSTANCE,
+          },
+          {
+            GeneralIndex: USDT_ASSET_ID,
+          },
+        ]},
       };
 
-      const metadata =
-      {
-        name: USDT_ASSET_ID,
-        symbol: USDT_ASSET_METADATA_NAME,
-        decimals: USDT_ASSET_METADATA_DECIMALS,
-        minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,
-      };
-      await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);
+      if(await helper.foreignAssets.foreignCollectionId(location) == null) {
+        const tokenPrefix = USDT_ASSET_METADATA_NAME;
+        await helper.getSudo().foreignAssets.register(alice, location, USDT_ASSET_METADATA_NAME, tokenPrefix, {Fungible: USDT_ASSET_METADATA_DECIMALS});
+      } else {
+        console.log('Foreign collection is already registered on Unique');
+      }
+
       balanceUniqueBefore = await helper.balance.getSubstrate(alice.address);
+      usdtCollectionId = await helper.foreignAssets.foreignCollectionId(location);
     });
 
 
@@ -259,8 +269,7 @@
     // ensure that asset has been delivered
     await helper.wait.newBlocks(3);
 
-    // expext collection id will be with id 1
-    const free = await helper.ft.getBalance(1, {Substrate: alice.address});
+    const free = await helper.ft.getBalance(usdtCollectionId, {Substrate: alice.address});
 
     balanceUniqueAfter = await helper.balance.getSubstrate(alice.address);
 
@@ -299,15 +308,11 @@
     const relayFee = 400_000_000_000_000n;
     const currencies: [any, bigint][] = [
       [
-        {
-          ForeignAssetId: 0,
-        },
+        usdtCollectionId,
         TRANSFER_AMOUNT,
       ],
       [
-        {
-          NativeAssetId: 'Parent',
-        },
+        relayCollectionId,
         relayFee,
       ],
     ];
@@ -332,7 +337,7 @@
 
   itSub('Should connect and send Relay token to Unique', async ({helper}) => {
     balanceBobBefore = await helper.balance.getSubstrate(bob.address);
-    balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});
+    balanceBobRelayTokenBefore = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});
 
     await usingRelayPlaygrounds(relayUrl, async (helper) => {
       const destination = {
@@ -380,7 +385,7 @@
     await helper.wait.newBlocks(3);
 
     balanceBobAfter = await helper.balance.getSubstrate(bob.address);
-    balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});
+    balanceBobRelayTokenAfter = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});
 
     const wndFeeOnUnique = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;
     const wndDiffOnUnique = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;
@@ -420,9 +425,7 @@
 
     const currencies: any = [
       [
-        {
-          NativeAssetId: 'Parent',
-        },
+        relayCollectionId,
         TRANSFER_AMOUNT_RELAY,
       ],
     ];