git.delta.rocks / unique-network / refs/commits / 74b1ef40cf52

difftreelog

fix xcm tests

Daniel Shiposha2023-11-23parent: #c16a17a.patch.diff
in: master

5 files changed

modifiedjs-packages/playgrounds/unique.xcm.tsdiffbeforeafterboth
--- a/js-packages/playgrounds/unique.xcm.ts
+++ b/js-packages/playgrounds/unique.xcm.ts
@@ -104,17 +104,17 @@
 }
 
 export class ForeignAssetsGroup extends HelperGroup<UniqueHelper> {
-  async register(signer: TSigner, location: any, name: string, tokenPrefix: string, mode: 'NFT' | { Fungible: number }) {
+  async register(signer: TSigner, assetId: any, name: string, tokenPrefix: string, mode: 'NFT' | { Fungible: number }) {
     await this.helper.executeExtrinsic(
       signer,
       'api.tx.foreignAssets.forceRegisterForeignAsset',
-      [location, this.helper.util.str2vec(name), tokenPrefix, mode],
+      [assetId, this.helper.util.str2vec(name), tokenPrefix, mode],
       true,
     );
   }
 
-  async foreignCollectionId(location: any) {
-    return (await this.helper.callRpc('api.query.foreignAssets.foreignReserveLocationToCollection', [location])).toJSON();
+  async foreignCollectionId(assetId: any) {
+    return (await this.helper.callRpc('api.query.foreignAssets.foreignAssetToCollection', [assetId])).toJSON();
   }
 }
 
modifiedjs-packages/tests/xcm/xcm.types.tsdiffbeforeafterboth
--- a/js-packages/tests/xcm/xcm.types.ts
+++ b/js-packages/tests/xcm/xcm.types.ts
@@ -512,15 +512,16 @@
         parents: 1,
         interior: 'Here',
       };
+      const relayAssetId = {Concrete: relayLocation};
 
-      const relayCollectionId = await helper.foreignAssets.foreignCollectionId(relayLocation);
+      const relayCollectionId = await helper.foreignAssets.foreignCollectionId(relayAssetId);
       if(relayCollectionId == null) {
         const name = 'Relay Tokens';
         const tokenPrefix = 'xDOT';
         const decimals = 10;
-        await helper.getSudo().foreignAssets.register(alice, relayLocation, name, tokenPrefix, {Fungible: decimals});
+        await helper.getSudo().foreignAssets.register(alice, relayAssetId, name, tokenPrefix, {Fungible: decimals});
 
-        return await helper.foreignAssets.foreignCollectionId(relayLocation);
+        return await helper.foreignAssets.foreignCollectionId(relayAssetId);
       } 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,7 +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';
+import {XcmTestHelper} from './xcm.types.js';
 
 const STATEMINE_CHAIN = +(process.env.RELAY_WESTMINT_ID || 1000);
 const UNIQUE_CHAIN = +(process.env.RELAY_OPAL_ID || 2095);
@@ -120,16 +120,23 @@
           },
         ]},
       };
+      const assetId = {Concrete: location};
 
-      if(await helper.foreignAssets.foreignCollectionId(location) == null) {
+      if(await helper.foreignAssets.foreignCollectionId(assetId) == null) {
         const tokenPrefix = USDT_ASSET_METADATA_NAME;
-        await helper.getSudo().foreignAssets.register(alice, location, USDT_ASSET_METADATA_NAME, tokenPrefix, {Fungible: USDT_ASSET_METADATA_DECIMALS});
+        await helper.getSudo().foreignAssets.register(
+          alice,
+          assetId,
+          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);
+      usdtCollectionId = await helper.foreignAssets.foreignCollectionId(assetId);
     });
 
     // Providing the relay currency to the unique sender account
modifiedjs-packages/tests/xcm/xcmQuartz.test.tsdiffbeforeafterboth
before · 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 = 0;1024    const dest = {1025      V2: {1026        parents: 1,1027        interior: {1028          X2: [1029            {Parachain: MOONRIVER_CHAIN},1030            {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},1031          ],1032        },1033      },1034    };1035    const amount = TRANSFER_AMOUNT;10361037    await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, 'Unlimited');10381039    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);1040    expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;10411042    const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;1043    console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));1044    expect(transactionFees > 0, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;10451046    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1047      await helper.wait.newBlocks(3);10481049      balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);10501051      const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;1052      console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));1053      expect(movrFees == 0n).to.be.true;10541055      balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; // BigInt(qtzRandomAccountAsset['balance']);1056      const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;1057      console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));1058      expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1059    });1060  });10611062  itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {1063    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1064      const asset = {1065        V2: {1066          id: {1067            Concrete: {1068              parents: 1,1069              interior: {1070                X1: {Parachain: QUARTZ_CHAIN},1071              },1072            },1073          },1074          fun: {1075            Fungible: TRANSFER_AMOUNT,1076          },1077        },1078      };1079      const destination = {1080        V2: {1081          parents: 1,1082          interior: {1083            X2: [1084              {Parachain: QUARTZ_CHAIN},1085              {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},1086            ],1087          },1088        },1089      };10901091      await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, 'Unlimited');10921093      balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);10941095      const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;1096      console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));1097      expect(movrFees > 0, 'Negative fees MOVR, looks like nothing was transferred').to.be.true;10981099      const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);11001101      expect(qtzRandomAccountAsset).to.be.null;11021103      balanceForeignQtzTokenFinal = 0n;11041105      const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;1106      console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));1107      expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1108    });11091110    await helper.wait.newBlocks(3);11111112    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);1113    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;1114    expect(actuallyDelivered > 0).to.be.true;11151116    console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));11171118    const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;1119    console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));1120    expect(qtzFees == 0n).to.be.true;1121  });11221123  itSub('Moonriver can send only up to its balance', async ({helper}) => {1124    // set Moonriver's sovereign account's balance1125    const moonriverBalance = 10000n * (10n ** QTZ_DECIMALS);1126    const moonriverSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONRIVER_CHAIN);1127    await helper.getSudo().balance.setBalanceSubstrate(alice, moonriverSovereignAccount, moonriverBalance);11281129    const moreThanMoonriverHas = moonriverBalance * 2n;11301131    let targetAccountBalance = 0n;1132    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);11331134    const quartzMultilocation = {1135      V2: {1136        parents: 1,1137        interior: {1138          X1: {Parachain: QUARTZ_CHAIN},1139        },1140      },1141    };11421143    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1144      targetAccount.addressRaw,1145      {1146        Concrete: {1147          parents: 0,1148          interior: 'Here',1149        },1150      },1151      moreThanMoonriverHas,1152    );11531154    let maliciousXcmProgramSent: any;1155    const maxWaitBlocks = 3;11561157    // Try to trick Quartz1158    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1159      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgram]);11601161      // Needed to bypass the call filter.1162      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1163      await helper.fastDemocracy.executeProposal('try to spend more QTZ than Moonriver has', batchCall);11641165      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1166    });11671168    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1169        && event.outcome.isFailedToTransactAsset);11701171    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1172    expect(targetAccountBalance).to.be.equal(0n);11731174    // But Moonriver still can send the correct amount1175    const validTransferAmount = moonriverBalance / 2n;1176    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1177      targetAccount.addressRaw,1178      {1179        Concrete: {1180          parents: 0,1181          interior: 'Here',1182        },1183      },1184      validTransferAmount,1185    );11861187    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1188      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, validXcmProgram]);11891190      // Needed to bypass the call filter.1191      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1192      await helper.fastDemocracy.executeProposal('Spend the correct amount of QTZ', batchCall);1193    });11941195    await helper.wait.newBlocks(maxWaitBlocks);11961197    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1198    expect(targetAccountBalance).to.be.equal(validTransferAmount);1199  });12001201  itSub('Should not accept reserve transfer of QTZ from Moonriver', async ({helper}) => {1202    const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1203    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);12041205    const quartzMultilocation = {1206      V2: {1207        parents: 1,1208        interior: {1209          X1: {1210            Parachain: QUARTZ_CHAIN,1211          },1212        },1213      },1214    };12151216    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1217      targetAccount.addressRaw,1218      {1219        Concrete: {1220          parents: 0,1221          interior: {1222            X1: {1223              Parachain: QUARTZ_CHAIN,1224            },1225          },1226        },1227      },1228      testAmount,1229    );12301231    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1232      targetAccount.addressRaw,1233      {1234        Concrete: {1235          parents: 0,1236          interior: 'Here',1237        },1238      },1239      testAmount,1240    );12411242    let maliciousXcmProgramFullIdSent: any;1243    let maliciousXcmProgramHereIdSent: any;1244    const maxWaitBlocks = 3;12451246    // Try to trick Quartz using full QTZ identification1247    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1248      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramFullId]);12491250      // Needed to bypass the call filter.1251      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1252      await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using path asset identification', batchCall);12531254      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1255    });12561257    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1258        && event.outcome.isUntrustedReserveLocation);12591260    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1261    expect(accountBalance).to.be.equal(0n);12621263    // Try to trick Quartz using shortened QTZ identification1264    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1265      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramHereId]);12661267      // Needed to bypass the call filter.1268      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1269      await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using "here" asset identification', batchCall);12701271      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1272    });12731274    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1275        && event.outcome.isUntrustedReserveLocation);12761277    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1278    expect(accountBalance).to.be.equal(0n);1279  });1280});12811282describeXCM('[XCM] Integration test: Exchanging tokens with Shiden', () => {1283  let alice: IKeyringPair;1284  let sender: IKeyringPair;12851286  const QTZ_ASSET_ID_ON_SHIDEN = 18_446_744_073_709_551_633n; // The value is taken from the live Shiden1287  const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n; // The value is taken from the live Shiden12881289  // Quartz -> Shiden1290  const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); // 1 SHD, existential deposit required to actually create the account on Shiden1291  const unitsPerSecond = 500_451_000_000_000_000_000n; // The value is taken from the live Shiden1292  const qtzToShidenTransferred = 10n * (10n ** QTZ_DECIMALS); // 10 QTZ1293  const qtzToShidenArrived = 7_998_196_000_000_000_000n; // 7.99 ... QTZ, Shiden takes a commision in foreign tokens12941295  // Shiden -> Quartz1296  const qtzFromShidenTransfered = 5n * (10n ** QTZ_DECIMALS); // 5 QTZ1297  const qtzOnShidenLeft = qtzToShidenArrived - qtzFromShidenTransfered; // 2.99 ... QTZ12981299  let balanceAfterQuartzToShidenXCM: bigint;13001301  before(async () => {1302    await usingPlaygrounds(async (helper, privateKey) => {1303      alice = await privateKey('//Alice');1304      [sender] = await helper.arrange.createAccounts([100n], alice);1305      console.log('sender', sender.address);13061307      // Set the default version to wrap the first message to other chains.1308      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1309    });13101311    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1312      if(!(await helper.callRpc('api.query.assets.asset', [QTZ_ASSET_ID_ON_SHIDEN])).toJSON()) {1313        console.log('1. Create foreign asset and metadata');1314        await helper.getSudo().assets.forceCreate(1315          alice,1316          QTZ_ASSET_ID_ON_SHIDEN,1317          alice.address,1318          QTZ_MINIMAL_BALANCE_ON_SHIDEN,1319        );13201321        await helper.assets.setMetadata(1322          alice,1323          QTZ_ASSET_ID_ON_SHIDEN,1324          'Quartz',1325          'QTZ',1326          Number(QTZ_DECIMALS),1327        );13281329        console.log('2. Register asset location on Shiden');1330        const assetLocation = {1331          V2: {1332            parents: 1,1333            interior: {1334              X1: {1335                Parachain: QUARTZ_CHAIN,1336              },1337            },1338          },1339        };13401341        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]);13421343        console.log('3. Set QTZ payment for XCM execution on Shiden');1344        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);1345      } else {1346        console.log('QTZ is already registered on Shiden');1347      }1348      console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)');1349      await helper.balance.transferToSubstrate(alice, sender.address, shidenInitialBalance);1350    });1351  });13521353  itSub('Should connect and send QTZ to Shiden', async ({helper}) => {1354    const destination = {1355      V2: {1356        parents: 1,1357        interior: {1358          X1: {1359            Parachain: SHIDEN_CHAIN,1360          },1361        },1362      },1363    };13641365    const beneficiary = {1366      V2: {1367        parents: 0,1368        interior: {1369          X1: {1370            AccountId32: {1371              network: 'Any',1372              id: sender.addressRaw,1373            },1374          },1375        },1376      },1377    };13781379    const assets = {1380      V2: [1381        {1382          id: {1383            Concrete: {1384              parents: 0,1385              interior: 'Here',1386            },1387          },1388          fun: {1389            Fungible: qtzToShidenTransferred,1390          },1391        },1392      ],1393    };13941395    // Initial balance is 100 QTZ1396    const balanceBefore = await helper.balance.getSubstrate(sender.address);1397    console.log(`Initial balance is: ${balanceBefore}`);13981399    const feeAssetItem = 0;1400    await helper.xcm.limitedReserveTransferAssets(sender, destination, beneficiary, assets, feeAssetItem, 'Unlimited');14011402    // Balance after reserve transfer is less than 901403    balanceAfterQuartzToShidenXCM = await helper.balance.getSubstrate(sender.address);1404    console.log(`QTZ Balance on Quartz after XCM is: ${balanceAfterQuartzToShidenXCM}`);1405    console.log(`Quartz's QTZ commission is: ${balanceBefore - balanceAfterQuartzToShidenXCM}`);1406    expect(balanceBefore - balanceAfterQuartzToShidenXCM > 0).to.be.true;14071408    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1409      await helper.wait.newBlocks(3);1410      const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1411      const shidenBalance = await helper.balance.getSubstrate(sender.address);14121413      console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);1414      console.log(`Shiden's QTZ commission is: ${qtzToShidenTransferred - xcQTZbalance!}`);14151416      expect(xcQTZbalance).to.eq(qtzToShidenArrived);1417      // SHD balance does not changed:1418      expect(shidenBalance).to.eq(shidenInitialBalance);1419    });1420  });14211422  itSub('Should connect to Shiden and send QTZ back', async ({helper}) => {1423    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1424      const destination = {1425        V2: {1426          parents: 1,1427          interior: {1428            X1: {1429              Parachain: QUARTZ_CHAIN,1430            },1431          },1432        },1433      };14341435      const beneficiary = {1436        V2: {1437          parents: 0,1438          interior: {1439            X1: {1440              AccountId32: {1441                network: 'Any',1442                id: sender.addressRaw,1443              },1444            },1445          },1446        },1447      };14481449      const assets = {1450        V2: [1451          {1452            id: {1453              Concrete: {1454                parents: 1,1455                interior: {1456                  X1: {1457                    Parachain: QUARTZ_CHAIN,1458                  },1459                },1460              },1461            },1462            fun: {1463              Fungible: qtzFromShidenTransfered,1464            },1465          },1466        ],1467      };14681469      // Initial balance is 1 SDN1470      const balanceSDNbefore = await helper.balance.getSubstrate(sender.address);1471      console.log(`SDN balance is: ${balanceSDNbefore}, it does not changed`);1472      expect(balanceSDNbefore).to.eq(shidenInitialBalance);14731474      const feeAssetItem = 0;1475      // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1476      await helper.executeExtrinsic(sender, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);14771478      // Balance after reserve transfer is less than 1 SDN1479      const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1480      const balanceSDN = await helper.balance.getSubstrate(sender.address);1481      console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);14821483      // Assert: xcQTZ balance correctly decreased1484      expect(xcQTZbalance).to.eq(qtzOnShidenLeft);1485      // Assert: SDN balance is 0.996...1486      expect(balanceSDN / (10n ** (SHIDEN_DECIMALS - 3n))).to.eq(996n);1487    });14881489    await helper.wait.newBlocks(3);1490    const balanceQTZ = await helper.balance.getSubstrate(sender.address);1491    console.log(`QTZ Balance on Quartz after XCM is: ${balanceQTZ}`);1492    expect(balanceQTZ).to.eq(balanceAfterQuartzToShidenXCM + qtzFromShidenTransfered);1493  });14941495  itSub('Shiden can send only up to its balance', async ({helper}) => {1496    // set Shiden's sovereign account's balance1497    const shidenBalance = 10000n * (10n ** QTZ_DECIMALS);1498    const shidenSovereignAccount = helper.address.paraSiblingSovereignAccount(SHIDEN_CHAIN);1499    await helper.getSudo().balance.setBalanceSubstrate(alice, shidenSovereignAccount, shidenBalance);15001501    const moreThanShidenHas = shidenBalance * 2n;15021503    let targetAccountBalance = 0n;1504    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);15051506    const quartzMultilocation = {1507      V2: {1508        parents: 1,1509        interior: {1510          X1: {Parachain: QUARTZ_CHAIN},1511        },1512      },1513    };15141515    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1516      targetAccount.addressRaw,1517      {1518        Concrete: {1519          parents: 0,1520          interior: 'Here',1521        },1522      },1523      moreThanShidenHas,1524    );15251526    let maliciousXcmProgramSent: any;1527    const maxWaitBlocks = 3;15281529    // Try to trick Quartz1530    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1531      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);15321533      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1534    });15351536    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1537        && event.outcome.isFailedToTransactAsset);15381539    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1540    expect(targetAccountBalance).to.be.equal(0n);15411542    // But Shiden still can send the correct amount1543    const validTransferAmount = shidenBalance / 2n;1544    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1545      targetAccount.addressRaw,1546      {1547        Concrete: {1548          parents: 0,1549          interior: 'Here',1550        },1551      },1552      validTransferAmount,1553    );15541555    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1556      await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);1557    });15581559    await helper.wait.newBlocks(maxWaitBlocks);15601561    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1562    expect(targetAccountBalance).to.be.equal(validTransferAmount);1563  });15641565  itSub('Should not accept reserve transfer of QTZ from Shiden', async ({helper}) => {1566    const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1567    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);15681569    const quartzMultilocation = {1570      V2: {1571        parents: 1,1572        interior: {1573          X1: {1574            Parachain: QUARTZ_CHAIN,1575          },1576        },1577      },1578    };15791580    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1581      targetAccount.addressRaw,1582      {1583        Concrete: {1584          parents: 1,1585          interior: {1586            X1: {1587              Parachain: QUARTZ_CHAIN,1588            },1589          },1590        },1591      },1592      testAmount,1593    );15941595    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1596      targetAccount.addressRaw,1597      {1598        Concrete: {1599          parents: 0,1600          interior: 'Here',1601        },1602      },1603      testAmount,1604    );16051606    let maliciousXcmProgramFullIdSent: any;1607    let maliciousXcmProgramHereIdSent: any;1608    const maxWaitBlocks = 3;16091610    // Try to trick Quartz using full QTZ identification1611    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1612      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);16131614      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1615    });16161617    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1618        && event.outcome.isUntrustedReserveLocation);16191620    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1621    expect(accountBalance).to.be.equal(0n);16221623    // Try to trick Quartz using shortened QTZ identification1624    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1625      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);16261627      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1628    });16291630    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1631        && event.outcome.isUntrustedReserveLocation);16321633    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1634    expect(accountBalance).to.be.equal(0n);1635  });1636});
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.js';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      };133      const assetId = {Concrete: location};134135      if(await helper.foreignAssets.foreignCollectionId(assetId) == null) {136        const tokenPrefix = USDT_ASSET_METADATA_NAME;137        await helper.getSudo().foreignAssets.register(alice, assetId, USDT_ASSET_METADATA_NAME, tokenPrefix, {Fungible: USDT_ASSET_METADATA_DECIMALS});138      } else {139        console.log('Foreign collection is already registered on Quartz');140      }141142      balanceQuartzBefore = await helper.balance.getSubstrate(alice.address);143      usdtCollectionId = await helper.foreignAssets.foreignCollectionId(assetId);144    });145146147    // Providing the relay currency to the quartz sender account148    // (fee for USDT XCM are paid in relay tokens)149    await usingRelayPlaygrounds(relayUrl, async (helper) => {150      const destination = {151        V2: {152          parents: 0,153          interior: {X1: {154            Parachain: QUARTZ_CHAIN,155          },156          },157        }};158159      const beneficiary = {160        V2: {161          parents: 0,162          interior: {X1: {163            AccountId32: {164              network: 'Any',165              id: alice.addressRaw,166            },167          }},168        },169      };170171      const assets = {172        V2: [173          {174            id: {175              Concrete: {176                parents: 0,177                interior: 'Here',178              },179            },180            fun: {181              Fungible: TRANSFER_AMOUNT_RELAY,182            },183          },184        ],185      };186187      const feeAssetItem = 0;188189      await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');190    });191192  });193194  itSub('Should connect and send USDT from Statemine to Quartz', async ({helper}) => {195    await usingStateminePlaygrounds(statemineUrl, async (helper) => {196      const dest = {197        V2: {198          parents: 1,199          interior: {X1: {200            Parachain: QUARTZ_CHAIN,201          },202          },203        }};204205      const beneficiary = {206        V2: {207          parents: 0,208          interior: {X1: {209            AccountId32: {210              network: 'Any',211              id: alice.addressRaw,212            },213          }},214        },215      };216217      const assets = {218        V2: [219          {220            id: {221              Concrete: {222                parents: 0,223                interior: {224                  X2: [225                    {226                      PalletInstance: STATEMINE_PALLET_INSTANCE,227                    },228                    {229                      GeneralIndex: USDT_ASSET_ID,230                    },231                  ]},232              },233            },234            fun: {235              Fungible: TRANSFER_AMOUNT,236            },237          },238        ],239      };240241      const feeAssetItem = 0;242243      balanceStmnBefore = await helper.balance.getSubstrate(alice.address);244      await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');245246      balanceStmnAfter = await helper.balance.getSubstrate(alice.address);247248      // common good parachain take commission in it native token249      console.log(250        '[Statemine -> Quartz] transaction fees on Statemine: %s WND',251        helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINE_DECIMALS),252      );253      expect(balanceStmnBefore > balanceStmnAfter).to.be.true;254255    });256257258    // ensure that asset has been delivered259    await helper.wait.newBlocks(3);260261    const free = await helper.ft.getBalance(usdtCollectionId, {Substrate: alice.address});262263    balanceQuartzAfter = await helper.balance.getSubstrate(alice.address);264265    console.log(266      '[Statemine -> Quartz] transaction fees on Quartz: %s USDT',267      helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),268    );269    console.log(270      '[Statemine -> Quartz] transaction fees on Quartz: %s QTZ',271      helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzBefore),272    );273    // commission has not paid in USDT token274    expect(free).to.be.equal(TRANSFER_AMOUNT);275    // ... and parachain native token276    expect(balanceQuartzAfter == balanceQuartzBefore).to.be.true;277  });278279  itSub('Should connect and send USDT from Quartz to Statemine back', async ({helper}) => {280    const destination = {281      V2: {282        parents: 1,283        interior: {X2: [284          {285            Parachain: STATEMINE_CHAIN,286          },287          {288            AccountId32: {289              network: 'Any',290              id: alice.addressRaw,291            },292          },293        ]},294      },295    };296297    const relayFee = 400_000_000_000_000n;298    const currencies: [any, bigint][] = [299      [300        usdtCollectionId,301        TRANSFER_AMOUNT,302      ],303      [304        relayCollectionId,305        relayFee,306      ],307    ];308309    const feeItem = 1;310311    await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');312313    // the commission has been paid in parachain native token314    balanceQuartzFinal = await helper.balance.getSubstrate(alice.address);315    console.log('[Quartz -> Statemine] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzFinal));316    expect(balanceQuartzAfter > balanceQuartzFinal).to.be.true;317318    await usingStateminePlaygrounds(statemineUrl, async (helper) => {319      await helper.wait.newBlocks(3);320321      // The USDT token never paid fees. Its amount not changed from begin value.322      // Also check that xcm transfer has been succeeded323      expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;324    });325  });326327  itSub('Should connect and send Relay token to Quartz', async ({helper}) => {328    balanceBobBefore = await helper.balance.getSubstrate(bob.address);329    balanceBobRelayTokenBefore = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});330331    await usingRelayPlaygrounds(relayUrl, async (helper) => {332      const destination = {333        V2: {334          parents: 0,335          interior: {X1: {336            Parachain: QUARTZ_CHAIN,337          },338          },339        }};340341      const beneficiary = {342        V2: {343          parents: 0,344          interior: {X1: {345            AccountId32: {346              network: 'Any',347              id: bob.addressRaw,348            },349          }},350        },351      };352353      const assets = {354        V2: [355          {356            id: {357              Concrete: {358                parents: 0,359                interior: 'Here',360              },361            },362            fun: {363              Fungible: TRANSFER_AMOUNT_RELAY,364            },365          },366        ],367      };368369      const feeAssetItem = 0;370371      await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');372    });373374    await helper.wait.newBlocks(3);375376    balanceBobAfter = await helper.balance.getSubstrate(bob.address);377    balanceBobRelayTokenAfter = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});378379    const wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;380    const wndDiffOnQuartz = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;381    console.log(382      '[Relay (Westend) -> Quartz] transaction fees: %s QTZ',383      helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),384    );385    console.log(386      '[Relay (Westend) -> Quartz] transaction fees: %s WND',387      helper.util.bigIntToDecimals(wndFeeOnQuartz, STATEMINE_DECIMALS),388    );389    console.log('[Relay (Westend) -> Quartz] actually delivered: %s WND', wndDiffOnQuartz);390    expect(wndFeeOnQuartz == 0n, 'No incoming WND fees should be taken').to.be.true;391    expect(balanceBobBefore == balanceBobAfter, 'No incoming QTZ fees should be taken').to.be.true;392  });393394  itSub('Should connect and send Relay token back', async ({helper}) => {395    let relayTokenBalanceBefore: bigint;396    let relayTokenBalanceAfter: bigint;397    await usingRelayPlaygrounds(relayUrl, async (helper) => {398      relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);399    });400401    const destination = {402      V2: {403        parents: 1,404        interior: {405          X1:{406            AccountId32: {407              network: 'Any',408              id: bob.addressRaw,409            },410          },411        },412      },413    };414415    const currencies: any = [416      [417        relayCollectionId,418        TRANSFER_AMOUNT_RELAY,419      ],420    ];421422    const feeItem = 0;423424    await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');425426    balanceBobFinal = await helper.balance.getSubstrate(bob.address);427    console.log('[Quartz -> Relay (Westend)] transaction fees: %s QTZ',  helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));428429    await usingRelayPlaygrounds(relayUrl, async (helper) => {430      await helper.wait.newBlocks(10);431      relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);432433      const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;434      console.log('[Quartz -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));435      expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;436    });437  });438});439440describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => {441  let alice: IKeyringPair;442  let randomAccount: IKeyringPair;443444  let balanceQuartzTokenInit: bigint;445  let balanceQuartzTokenMiddle: bigint;446  let balanceQuartzTokenFinal: bigint;447  let balanceKaruraTokenInit: bigint;448  let balanceKaruraTokenMiddle: bigint;449  let balanceKaruraTokenFinal: bigint;450  let balanceQuartzForeignTokenInit: bigint;451  let balanceQuartzForeignTokenMiddle: bigint;452  let balanceQuartzForeignTokenFinal: bigint;453454  // computed by a test transfer from prod Quartz to prod Karura.455  // 2 QTZ sent https://quartz.subscan.io/xcm_message/kusama-f60d821b049f8835a3005ce7102285006f5b61e9456  // 1.919176000000000000 QTZ received (you can check Karura's chain state in the corresponding block)457  const expectedKaruraIncomeFee = 2000000000000000000n - 1919176000000000000n;458  const karuraEps = 8n * 10n ** 16n;459460  let karuraBackwardTransferAmount: bigint;461462  before(async () => {463    await usingPlaygrounds(async (helper, privateKey) => {464      alice = await privateKey('//Alice');465      [randomAccount] = await helper.arrange.createAccounts([0n], alice);466467      // Set the default version to wrap the first message to other chains.468      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);469    });470471    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {472      const destination = {473        V2: {474          parents: 1,475          interior: {476            X1: {477              Parachain: QUARTZ_CHAIN,478            },479          },480        },481      };482483      const metadata = {484        name: 'Quartz',485        symbol: 'QTZ',486        decimals: 18,487        minimalBalance: 1000000000000000000n,488      };489490      const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v]: [any, any]) =>491        hexToString(v.toJSON()['symbol'])) as string[];492493      if(!assets.includes('QTZ')) {494        await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);495      } else {496        console.log('QTZ token already registered on Karura assetRegistry pallet');497      }498      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);499      balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);500      balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});501    });502503    await usingPlaygrounds(async (helper) => {504      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);505      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);506    });507  });508509  itSub('Should connect and send QTZ to Karura', async ({helper}) => {510    const destination = {511      V2: {512        parents: 1,513        interior: {514          X1: {515            Parachain: KARURA_CHAIN,516          },517        },518      },519    };520521    const beneficiary = {522      V2: {523        parents: 0,524        interior: {525          X1: {526            AccountId32: {527              network: 'Any',528              id: randomAccount.addressRaw,529            },530          },531        },532      },533    };534535    const assets = {536      V2: [537        {538          id: {539            Concrete: {540              parents: 0,541              interior: 'Here',542            },543          },544          fun: {545            Fungible: TRANSFER_AMOUNT,546          },547        },548      ],549    };550551    const feeAssetItem = 0;552553    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');554    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);555556    const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;557    expect(qtzFees > 0n, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;558    console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));559560    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {561      await helper.wait.newBlocks(3);562563      balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});564      balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);565566      const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;567      const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;568      karuraBackwardTransferAmount = qtzIncomeTransfer;569570      const karUnqFees = TRANSFER_AMOUNT - qtzIncomeTransfer;571572      console.log(573        '[Quartz -> Karura] transaction fees on Karura: %s KAR',574        helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),575      );576      console.log(577        '[Quartz -> Karura] transaction fees on Karura: %s QTZ',578        helper.util.bigIntToDecimals(karUnqFees),579      );580      console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));581      expect(karFees == 0n).to.be.true;582583      const bigintAbs = (n: bigint) => (n < 0n) ? -n : n;584585      expect(586        bigintAbs(karUnqFees - expectedKaruraIncomeFee) < karuraEps,587        'Karura took different income fee, check the Karura foreign asset config',588      ).to.be.true;589    });590  });591592  itSub('Should connect to Karura and send QTZ back', async ({helper}) => {593    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {594      const destination = {595        V2: {596          parents: 1,597          interior: {598            X2: [599              {Parachain: QUARTZ_CHAIN},600              {601                AccountId32: {602                  network: 'Any',603                  id: randomAccount.addressRaw,604                },605              },606            ],607          },608        },609      };610611      const id = {612        ForeignAsset: 0,613      };614615      await helper.xTokens.transfer(randomAccount, id, karuraBackwardTransferAmount, destination, 'Unlimited');616      balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);617      balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);618619      const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;620      const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;621622      console.log(623        '[Karura -> Quartz] transaction fees on Karura: %s KAR',624        helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),625      );626      console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));627628      expect(karFees > 0, 'Negative fees KAR, looks like nothing was transferred').to.be.true;629      expect(qtzOutcomeTransfer == karuraBackwardTransferAmount).to.be.true;630    });631632    await helper.wait.newBlocks(3);633634    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);635    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;636    expect(actuallyDelivered > 0).to.be.true;637638    console.log('[Karura -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));639640    const qtzFees = karuraBackwardTransferAmount - actuallyDelivered;641    console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));642    expect(qtzFees == 0n).to.be.true;643  });644645  itSub('Karura can send only up to its balance', async ({helper}) => {646    // set Karura's sovereign account's balance647    const karuraBalance = 10000n * (10n ** QTZ_DECIMALS);648    const karuraSovereignAccount = helper.address.paraSiblingSovereignAccount(KARURA_CHAIN);649    await helper.getSudo().balance.setBalanceSubstrate(alice, karuraSovereignAccount, karuraBalance);650651    const moreThanKaruraHas = karuraBalance * 2n;652653    let targetAccountBalance = 0n;654    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);655656    const quartzMultilocation = {657      V2: {658        parents: 1,659        interior: {660          X1: {Parachain: QUARTZ_CHAIN},661        },662      },663    };664665    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(666      targetAccount.addressRaw,667      {668        Concrete: {669          parents: 0,670          interior: 'Here',671        },672      },673      moreThanKaruraHas,674    );675676    let maliciousXcmProgramSent: any;677    const maxWaitBlocks = 5;678679    // Try to trick Quartz680    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {681      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);682683      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);684    });685686    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash687        && event.outcome.isFailedToTransactAsset);688689    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);690    expect(targetAccountBalance).to.be.equal(0n);691692    // But Karura still can send the correct amount693    const validTransferAmount = karuraBalance / 2n;694    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(695      targetAccount.addressRaw,696      {697        Concrete: {698          parents: 0,699          interior: 'Here',700        },701      },702      validTransferAmount,703    );704705    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {706      await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);707    });708709    await helper.wait.newBlocks(maxWaitBlocks);710711    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);712    expect(targetAccountBalance).to.be.equal(validTransferAmount);713  });714715  itSub('Should not accept reserve transfer of QTZ from Karura', async ({helper}) => {716    const testAmount = 10_000n * (10n ** QTZ_DECIMALS);717    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);718719    const quartzMultilocation = {720      V2: {721        parents: 1,722        interior: {723          X1: {724            Parachain: QUARTZ_CHAIN,725          },726        },727      },728    };729730    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(731      targetAccount.addressRaw,732      {733        Concrete: {734          parents: 1,735          interior: {736            X1: {737              Parachain: QUARTZ_CHAIN,738            },739          },740        },741      },742      testAmount,743    );744745    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(746      targetAccount.addressRaw,747      {748        Concrete: {749          parents: 0,750          interior: 'Here',751        },752      },753      testAmount,754    );755756    let maliciousXcmProgramFullIdSent: any;757    let maliciousXcmProgramHereIdSent: any;758    const maxWaitBlocks = 3;759760    // Try to trick Quartz using full QTZ identification761    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {762      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);763764      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);765    });766767    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash768        && event.outcome.isUntrustedReserveLocation);769770    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);771    expect(accountBalance).to.be.equal(0n);772773    // Try to trick Quartz using shortened QTZ identification774    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {775      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);776777      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);778    });779780    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash781        && event.outcome.isUntrustedReserveLocation);782783    accountBalance = await helper.balance.getSubstrate(targetAccount.address);784    expect(accountBalance).to.be.equal(0n);785  });786});787788// These tests are relevant only when789// the the corresponding foreign assets are not registered790describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => {791  let alice: IKeyringPair;792  let alith: IKeyringPair;793794  const testAmount = 100_000_000_000n;795  let quartzParachainJunction;796  let quartzAccountJunction;797798  let quartzParachainMultilocation: any;799  let quartzAccountMultilocation: any;800  let quartzCombinedMultilocation: any;801802  let messageSent: any;803804  const maxWaitBlocks = 3;805806  before(async () => {807    await usingPlaygrounds(async (helper, privateKey) => {808      alice = await privateKey('//Alice');809810      quartzParachainJunction = {Parachain: QUARTZ_CHAIN};811      quartzAccountJunction = {812        AccountId32: {813          network: 'Any',814          id: alice.addressRaw,815        },816      };817818      quartzParachainMultilocation = {819        V2: {820          parents: 1,821          interior: {822            X1: quartzParachainJunction,823          },824        },825      };826827      quartzAccountMultilocation = {828        V2: {829          parents: 0,830          interior: {831            X1: quartzAccountJunction,832          },833        },834      };835836      quartzCombinedMultilocation = {837        V2: {838          parents: 1,839          interior: {840            X2: [quartzParachainJunction, quartzAccountJunction],841          },842        },843      };844845      // Set the default version to wrap the first message to other chains.846      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);847    });848849    // eslint-disable-next-line require-await850    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {851      alith = helper.account.alithAccount();852    });853  });854855  const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {856    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash857        && event.outcome.isFailedToTransactAsset);858  };859860  itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {861    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {862      const id = {863        Token: 'KAR',864      };865      const destination = quartzCombinedMultilocation;866      await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');867868      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);869    });870871    await expectFailedToTransact(helper, messageSent);872  });873874  itSub('Quartz rejects MOVR tokens from Moonriver', async ({helper}) => {875    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {876      const id = 'SelfReserve';877      const destination = quartzCombinedMultilocation;878      await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');879880      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);881    });882883    await expectFailedToTransact(helper, messageSent);884  });885886  itSub('Quartz rejects SDN tokens from Shiden', async ({helper}) => {887    await usingShidenPlaygrounds(shidenUrl, async (helper) => {888      const destinationParachain = quartzParachainMultilocation;889      const beneficiary = quartzAccountMultilocation;890      const assets = {891        V2: [{892          id: {893            Concrete: {894              parents: 0,895              interior: 'Here',896            },897          },898          fun: {899            Fungible: testAmount,900          },901        }],902      };903      const feeAssetItem = 0;904905      await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [906        destinationParachain,907        beneficiary,908        assets,909        feeAssetItem,910      ]);911912      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);913    });914915    await expectFailedToTransact(helper, messageSent);916  });917});918919describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {920  // Quartz constants921  let alice: IKeyringPair;922  let quartzAssetLocation;923924  let randomAccountQuartz: IKeyringPair;925  let randomAccountMoonriver: IKeyringPair;926927  // Moonriver constants928  let assetId: string;929930  const quartzAssetMetadata = {931    name: 'xcQuartz',932    symbol: 'xcQTZ',933    decimals: 18,934    isFrozen: false,935    minimalBalance: 1n,936  };937938  let balanceQuartzTokenInit: bigint;939  let balanceQuartzTokenMiddle: bigint;940  let balanceQuartzTokenFinal: bigint;941  let balanceForeignQtzTokenInit: bigint;942  let balanceForeignQtzTokenMiddle: bigint;943  let balanceForeignQtzTokenFinal: bigint;944  let balanceMovrTokenInit: bigint;945  let balanceMovrTokenMiddle: bigint;946  let balanceMovrTokenFinal: bigint;947948  before(async () => {949    await usingPlaygrounds(async (helper, privateKey) => {950      alice = await privateKey('//Alice');951      [randomAccountQuartz] = await helper.arrange.createAccounts([0n], alice);952953      balanceForeignQtzTokenInit = 0n;954955      // Set the default version to wrap the first message to other chains.956      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);957    });958959    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {960      const alithAccount = helper.account.alithAccount();961      const baltatharAccount = helper.account.baltatharAccount();962      const dorothyAccount = helper.account.dorothyAccount();963964      randomAccountMoonriver = helper.account.create();965966      // >>> Sponsoring Dorothy >>>967      console.log('Sponsoring Dorothy.......');968      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);969      console.log('Sponsoring Dorothy.......DONE');970      // <<< Sponsoring Dorothy <<<971972      quartzAssetLocation = {973        XCM: {974          parents: 1,975          interior: {X1: {Parachain: QUARTZ_CHAIN}},976        },977      };978      const existentialDeposit = 1n;979      const isSufficient = true;980      const unitsPerSecond = 1n;981      const numAssetsWeightHint = 0;982983      if((await helper.assetManager.assetTypeId(quartzAssetLocation)).toJSON()) {984        console.log('Quartz asset already registered on Moonriver');985      } else {986        const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({987          location: quartzAssetLocation,988          metadata: quartzAssetMetadata,989          existentialDeposit,990          isSufficient,991          unitsPerSecond,992          numAssetsWeightHint,993        });994995        console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);996997        await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);998      }999      // >>> Acquire Quartz AssetId Info on Moonriver >>>1000      console.log('Acquire Quartz AssetId Info on Moonriver.......');10011002      assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();10031004      console.log('QTZ asset ID is %s', assetId);1005      console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');1006      // >>> Acquire Quartz AssetId Info on Moonriver >>>10071008      // >>> Sponsoring random Account >>>1009      console.log('Sponsoring random Account.......');1010      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);1011      console.log('Sponsoring random Account.......DONE');1012      // <<< Sponsoring random Account <<<10131014      balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);1015    });10161017    await usingPlaygrounds(async (helper) => {1018      await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);1019      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);1020    });1021  });10221023  itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {1024    const currencyId = 0;1025    const dest = {1026      V2: {1027        parents: 1,1028        interior: {1029          X2: [1030            {Parachain: MOONRIVER_CHAIN},1031            {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},1032          ],1033        },1034      },1035    };1036    const amount = TRANSFER_AMOUNT;10371038    await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, 'Unlimited');10391040    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);1041    expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;10421043    const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;1044    console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));1045    expect(transactionFees > 0, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;10461047    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1048      await helper.wait.newBlocks(3);10491050      balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);10511052      const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;1053      console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));1054      expect(movrFees == 0n).to.be.true;10551056      balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; // BigInt(qtzRandomAccountAsset['balance']);1057      const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;1058      console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));1059      expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1060    });1061  });10621063  itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {1064    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1065      const asset = {1066        V2: {1067          id: {1068            Concrete: {1069              parents: 1,1070              interior: {1071                X1: {Parachain: QUARTZ_CHAIN},1072              },1073            },1074          },1075          fun: {1076            Fungible: TRANSFER_AMOUNT,1077          },1078        },1079      };1080      const destination = {1081        V2: {1082          parents: 1,1083          interior: {1084            X2: [1085              {Parachain: QUARTZ_CHAIN},1086              {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},1087            ],1088          },1089        },1090      };10911092      await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, 'Unlimited');10931094      balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);10951096      const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;1097      console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));1098      expect(movrFees > 0, 'Negative fees MOVR, looks like nothing was transferred').to.be.true;10991100      const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);11011102      expect(qtzRandomAccountAsset).to.be.null;11031104      balanceForeignQtzTokenFinal = 0n;11051106      const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;1107      console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));1108      expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1109    });11101111    await helper.wait.newBlocks(3);11121113    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);1114    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;1115    expect(actuallyDelivered > 0).to.be.true;11161117    console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));11181119    const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;1120    console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));1121    expect(qtzFees == 0n).to.be.true;1122  });11231124  itSub('Moonriver can send only up to its balance', async ({helper}) => {1125    // set Moonriver's sovereign account's balance1126    const moonriverBalance = 10000n * (10n ** QTZ_DECIMALS);1127    const moonriverSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONRIVER_CHAIN);1128    await helper.getSudo().balance.setBalanceSubstrate(alice, moonriverSovereignAccount, moonriverBalance);11291130    const moreThanMoonriverHas = moonriverBalance * 2n;11311132    let targetAccountBalance = 0n;1133    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);11341135    const quartzMultilocation = {1136      V2: {1137        parents: 1,1138        interior: {1139          X1: {Parachain: QUARTZ_CHAIN},1140        },1141      },1142    };11431144    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1145      targetAccount.addressRaw,1146      {1147        Concrete: {1148          parents: 0,1149          interior: 'Here',1150        },1151      },1152      moreThanMoonriverHas,1153    );11541155    let maliciousXcmProgramSent: any;1156    const maxWaitBlocks = 3;11571158    // Try to trick Quartz1159    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1160      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgram]);11611162      // Needed to bypass the call filter.1163      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1164      await helper.fastDemocracy.executeProposal('try to spend more QTZ than Moonriver has', batchCall);11651166      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1167    });11681169    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1170        && event.outcome.isFailedToTransactAsset);11711172    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1173    expect(targetAccountBalance).to.be.equal(0n);11741175    // But Moonriver still can send the correct amount1176    const validTransferAmount = moonriverBalance / 2n;1177    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1178      targetAccount.addressRaw,1179      {1180        Concrete: {1181          parents: 0,1182          interior: 'Here',1183        },1184      },1185      validTransferAmount,1186    );11871188    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1189      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, validXcmProgram]);11901191      // Needed to bypass the call filter.1192      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1193      await helper.fastDemocracy.executeProposal('Spend the correct amount of QTZ', batchCall);1194    });11951196    await helper.wait.newBlocks(maxWaitBlocks);11971198    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1199    expect(targetAccountBalance).to.be.equal(validTransferAmount);1200  });12011202  itSub('Should not accept reserve transfer of QTZ from Moonriver', async ({helper}) => {1203    const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1204    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);12051206    const quartzMultilocation = {1207      V2: {1208        parents: 1,1209        interior: {1210          X1: {1211            Parachain: QUARTZ_CHAIN,1212          },1213        },1214      },1215    };12161217    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1218      targetAccount.addressRaw,1219      {1220        Concrete: {1221          parents: 0,1222          interior: {1223            X1: {1224              Parachain: QUARTZ_CHAIN,1225            },1226          },1227        },1228      },1229      testAmount,1230    );12311232    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1233      targetAccount.addressRaw,1234      {1235        Concrete: {1236          parents: 0,1237          interior: 'Here',1238        },1239      },1240      testAmount,1241    );12421243    let maliciousXcmProgramFullIdSent: any;1244    let maliciousXcmProgramHereIdSent: any;1245    const maxWaitBlocks = 3;12461247    // Try to trick Quartz using full QTZ identification1248    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1249      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramFullId]);12501251      // Needed to bypass the call filter.1252      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1253      await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using path asset identification', batchCall);12541255      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1256    });12571258    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1259        && event.outcome.isUntrustedReserveLocation);12601261    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1262    expect(accountBalance).to.be.equal(0n);12631264    // Try to trick Quartz using shortened QTZ identification1265    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1266      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramHereId]);12671268      // Needed to bypass the call filter.1269      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1270      await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using "here" asset identification', batchCall);12711272      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1273    });12741275    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1276        && event.outcome.isUntrustedReserveLocation);12771278    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1279    expect(accountBalance).to.be.equal(0n);1280  });1281});12821283describeXCM('[XCM] Integration test: Exchanging tokens with Shiden', () => {1284  let alice: IKeyringPair;1285  let sender: IKeyringPair;12861287  const QTZ_ASSET_ID_ON_SHIDEN = 18_446_744_073_709_551_633n; // The value is taken from the live Shiden1288  const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n; // The value is taken from the live Shiden12891290  // Quartz -> Shiden1291  const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); // 1 SHD, existential deposit required to actually create the account on Shiden1292  const unitsPerSecond = 500_451_000_000_000_000_000n; // The value is taken from the live Shiden1293  const qtzToShidenTransferred = 10n * (10n ** QTZ_DECIMALS); // 10 QTZ1294  const qtzToShidenArrived = 7_998_196_000_000_000_000n; // 7.99 ... QTZ, Shiden takes a commision in foreign tokens12951296  // Shiden -> Quartz1297  const qtzFromShidenTransfered = 5n * (10n ** QTZ_DECIMALS); // 5 QTZ1298  const qtzOnShidenLeft = qtzToShidenArrived - qtzFromShidenTransfered; // 2.99 ... QTZ12991300  let balanceAfterQuartzToShidenXCM: bigint;13011302  before(async () => {1303    await usingPlaygrounds(async (helper, privateKey) => {1304      alice = await privateKey('//Alice');1305      [sender] = await helper.arrange.createAccounts([100n], alice);1306      console.log('sender', sender.address);13071308      // Set the default version to wrap the first message to other chains.1309      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1310    });13111312    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1313      if(!(await helper.callRpc('api.query.assets.asset', [QTZ_ASSET_ID_ON_SHIDEN])).toJSON()) {1314        console.log('1. Create foreign asset and metadata');1315        await helper.getSudo().assets.forceCreate(1316          alice,1317          QTZ_ASSET_ID_ON_SHIDEN,1318          alice.address,1319          QTZ_MINIMAL_BALANCE_ON_SHIDEN,1320        );13211322        await helper.assets.setMetadata(1323          alice,1324          QTZ_ASSET_ID_ON_SHIDEN,1325          'Quartz',1326          'QTZ',1327          Number(QTZ_DECIMALS),1328        );13291330        console.log('2. Register asset location on Shiden');1331        const assetLocation = {1332          V2: {1333            parents: 1,1334            interior: {1335              X1: {1336                Parachain: QUARTZ_CHAIN,1337              },1338            },1339          },1340        };13411342        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]);13431344        console.log('3. Set QTZ payment for XCM execution on Shiden');1345        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);1346      } else {1347        console.log('QTZ is already registered on Shiden');1348      }1349      console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)');1350      await helper.balance.transferToSubstrate(alice, sender.address, shidenInitialBalance);1351    });1352  });13531354  itSub('Should connect and send QTZ to Shiden', async ({helper}) => {1355    const destination = {1356      V2: {1357        parents: 1,1358        interior: {1359          X1: {1360            Parachain: SHIDEN_CHAIN,1361          },1362        },1363      },1364    };13651366    const beneficiary = {1367      V2: {1368        parents: 0,1369        interior: {1370          X1: {1371            AccountId32: {1372              network: 'Any',1373              id: sender.addressRaw,1374            },1375          },1376        },1377      },1378    };13791380    const assets = {1381      V2: [1382        {1383          id: {1384            Concrete: {1385              parents: 0,1386              interior: 'Here',1387            },1388          },1389          fun: {1390            Fungible: qtzToShidenTransferred,1391          },1392        },1393      ],1394    };13951396    // Initial balance is 100 QTZ1397    const balanceBefore = await helper.balance.getSubstrate(sender.address);1398    console.log(`Initial balance is: ${balanceBefore}`);13991400    const feeAssetItem = 0;1401    await helper.xcm.limitedReserveTransferAssets(sender, destination, beneficiary, assets, feeAssetItem, 'Unlimited');14021403    // Balance after reserve transfer is less than 901404    balanceAfterQuartzToShidenXCM = await helper.balance.getSubstrate(sender.address);1405    console.log(`QTZ Balance on Quartz after XCM is: ${balanceAfterQuartzToShidenXCM}`);1406    console.log(`Quartz's QTZ commission is: ${balanceBefore - balanceAfterQuartzToShidenXCM}`);1407    expect(balanceBefore - balanceAfterQuartzToShidenXCM > 0).to.be.true;14081409    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1410      await helper.wait.newBlocks(3);1411      const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1412      const shidenBalance = await helper.balance.getSubstrate(sender.address);14131414      console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);1415      console.log(`Shiden's QTZ commission is: ${qtzToShidenTransferred - xcQTZbalance!}`);14161417      expect(xcQTZbalance).to.eq(qtzToShidenArrived);1418      // SHD balance does not changed:1419      expect(shidenBalance).to.eq(shidenInitialBalance);1420    });1421  });14221423  itSub('Should connect to Shiden and send QTZ back', async ({helper}) => {1424    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1425      const destination = {1426        V2: {1427          parents: 1,1428          interior: {1429            X1: {1430              Parachain: QUARTZ_CHAIN,1431            },1432          },1433        },1434      };14351436      const beneficiary = {1437        V2: {1438          parents: 0,1439          interior: {1440            X1: {1441              AccountId32: {1442                network: 'Any',1443                id: sender.addressRaw,1444              },1445            },1446          },1447        },1448      };14491450      const assets = {1451        V2: [1452          {1453            id: {1454              Concrete: {1455                parents: 1,1456                interior: {1457                  X1: {1458                    Parachain: QUARTZ_CHAIN,1459                  },1460                },1461              },1462            },1463            fun: {1464              Fungible: qtzFromShidenTransfered,1465            },1466          },1467        ],1468      };14691470      // Initial balance is 1 SDN1471      const balanceSDNbefore = await helper.balance.getSubstrate(sender.address);1472      console.log(`SDN balance is: ${balanceSDNbefore}, it does not changed`);1473      expect(balanceSDNbefore).to.eq(shidenInitialBalance);14741475      const feeAssetItem = 0;1476      // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1477      await helper.executeExtrinsic(sender, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);14781479      // Balance after reserve transfer is less than 1 SDN1480      const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1481      const balanceSDN = await helper.balance.getSubstrate(sender.address);1482      console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);14831484      // Assert: xcQTZ balance correctly decreased1485      expect(xcQTZbalance).to.eq(qtzOnShidenLeft);1486      // Assert: SDN balance is 0.996...1487      expect(balanceSDN / (10n ** (SHIDEN_DECIMALS - 3n))).to.eq(996n);1488    });14891490    await helper.wait.newBlocks(3);1491    const balanceQTZ = await helper.balance.getSubstrate(sender.address);1492    console.log(`QTZ Balance on Quartz after XCM is: ${balanceQTZ}`);1493    expect(balanceQTZ).to.eq(balanceAfterQuartzToShidenXCM + qtzFromShidenTransfered);1494  });14951496  itSub('Shiden can send only up to its balance', async ({helper}) => {1497    // set Shiden's sovereign account's balance1498    const shidenBalance = 10000n * (10n ** QTZ_DECIMALS);1499    const shidenSovereignAccount = helper.address.paraSiblingSovereignAccount(SHIDEN_CHAIN);1500    await helper.getSudo().balance.setBalanceSubstrate(alice, shidenSovereignAccount, shidenBalance);15011502    const moreThanShidenHas = shidenBalance * 2n;15031504    let targetAccountBalance = 0n;1505    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);15061507    const quartzMultilocation = {1508      V2: {1509        parents: 1,1510        interior: {1511          X1: {Parachain: QUARTZ_CHAIN},1512        },1513      },1514    };15151516    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1517      targetAccount.addressRaw,1518      {1519        Concrete: {1520          parents: 0,1521          interior: 'Here',1522        },1523      },1524      moreThanShidenHas,1525    );15261527    let maliciousXcmProgramSent: any;1528    const maxWaitBlocks = 3;15291530    // Try to trick Quartz1531    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1532      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);15331534      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1535    });15361537    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1538        && event.outcome.isFailedToTransactAsset);15391540    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1541    expect(targetAccountBalance).to.be.equal(0n);15421543    // But Shiden still can send the correct amount1544    const validTransferAmount = shidenBalance / 2n;1545    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1546      targetAccount.addressRaw,1547      {1548        Concrete: {1549          parents: 0,1550          interior: 'Here',1551        },1552      },1553      validTransferAmount,1554    );15551556    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1557      await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);1558    });15591560    await helper.wait.newBlocks(maxWaitBlocks);15611562    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1563    expect(targetAccountBalance).to.be.equal(validTransferAmount);1564  });15651566  itSub('Should not accept reserve transfer of QTZ from Shiden', async ({helper}) => {1567    const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1568    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);15691570    const quartzMultilocation = {1571      V2: {1572        parents: 1,1573        interior: {1574          X1: {1575            Parachain: QUARTZ_CHAIN,1576          },1577        },1578      },1579    };15801581    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1582      targetAccount.addressRaw,1583      {1584        Concrete: {1585          parents: 1,1586          interior: {1587            X1: {1588              Parachain: QUARTZ_CHAIN,1589            },1590          },1591        },1592      },1593      testAmount,1594    );15951596    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1597      targetAccount.addressRaw,1598      {1599        Concrete: {1600          parents: 0,1601          interior: 'Here',1602        },1603      },1604      testAmount,1605    );16061607    let maliciousXcmProgramFullIdSent: any;1608    let maliciousXcmProgramHereIdSent: any;1609    const maxWaitBlocks = 3;16101611    // Try to trick Quartz using full QTZ identification1612    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1613      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);16141615      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1616    });16171618    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1619        && event.outcome.isUntrustedReserveLocation);16201621    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1622    expect(accountBalance).to.be.equal(0n);16231624    // Try to trick Quartz using shortened QTZ identification1625    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1626      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);16271628      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1629    });16301631    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1632        && event.outcome.isUntrustedReserveLocation);16331634    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1635    expect(accountBalance).to.be.equal(0n);1636  });1637});
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';
+import {XcmTestHelper} from './xcm.types.js';
 
 const STATEMINT_PALLET_INSTANCE = 50;
 
@@ -142,16 +142,17 @@
           },
         ]},
       };
+      const assetId = {Concrete: location};
 
-      if(await helper.foreignAssets.foreignCollectionId(location) == null) {
+      if(await helper.foreignAssets.foreignCollectionId(assetId) == null) {
         const tokenPrefix = USDT_ASSET_METADATA_NAME;
-        await helper.getSudo().foreignAssets.register(alice, location, USDT_ASSET_METADATA_NAME, tokenPrefix, {Fungible: USDT_ASSET_METADATA_DECIMALS});
+        await helper.getSudo().foreignAssets.register(alice, assetId, 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);
+      usdtCollectionId = await helper.foreignAssets.foreignCollectionId(assetId);
     });