git.delta.rocks / unique-network / refs/commits / 63d711f1931e

difftreelog

fix hash

PraetorP2023-10-27parent: #4eaa889.patch.diff
in: master

1 file changed

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  });785786  itSub.skip('Transfer NFT from Quartz to Karura', async ({helper}) => {787    const collection = await helper.nft.mintCollection(alice, {788      tokenPrefix: 'xNFT',789    });790791    const collectionId = collection.collectionId;792793    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {794      const uniqueCollectionLocation = {795        parents: 1,796        interior: {797          X2: [798            {799              Parachain: QUARTZ_CHAIN,800            },801            {802              GeneralIndex: collectionId,803            },804          ],805        },806      };807808      await helper.executeExtrinsic(809        alice,810        'api.tx.xnft.registerAsset',811        [{812          Concrete: uniqueCollectionLocation,813        }],814      );815    });816817    const token = await collection.mintToken(alice, {Substrate: alice.address});818    const tokenId = token.tokenId;819820    const destination = {821      V3: {822        parents: 1,823        interior: {824          X1: {825            Parachain: KARURA_CHAIN,826          },827        },828      },829    };830831    const beneficiary = {832      V3: {833        parents: 0,834        interior: {835          X1: {836            AccountId32: {837              network: null,838              id: randomAccount.addressRaw,839            },840          },841        },842      },843    };844845    const buyExecutionFee = 10n * 10n ** QTZ_DECIMALS;846847    const assets = {848      V3: [849        {850          id: {851            Concrete: {852              parents: 0,853              interior: 'Here',854            },855          },856          fun: {857            Fungible: buyExecutionFee,858          },859        },860        {861          id: {862            Concrete: {863              parents: 1,864              interior: {865                X2: [866                  {867                    Parachain: QUARTZ_CHAIN,868                  },869                  {870                    GeneralIndex: collectionId,871                  },872                ],873              },874            },875          },876          fun: {877            NonFungible: {878              Index: tokenId,879            },880          },881        },882      ],883    };884885    const feeAssetItem = 0;886887    await helper.xcm.limitedReserveTransferAssets(888      alice,889      destination,890      beneficiary,891      assets,892      feeAssetItem,893      'Unlimited',894    );895896    const maxWaitBlocks = 3;897    const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);898899    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {900      const xcmpSuccess = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success);901      expect(xcmpSuccess.messageHash).to.be.equal(messageSent.messageSent);902    });903  });904});905906// These tests are relevant only when907// the the corresponding foreign assets are not registered908describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => {909  let alice: IKeyringPair;910  let alith: IKeyringPair;911912  const testAmount = 100_000_000_000n;913  let quartzParachainJunction;914  let quartzAccountJunction;915916  let quartzParachainMultilocation: any;917  let quartzAccountMultilocation: any;918  let quartzCombinedMultilocation: any;919920  let messageSent: any;921922  const maxWaitBlocks = 3;923924  before(async () => {925    await usingPlaygrounds(async (helper, privateKey) => {926      alice = await privateKey('//Alice');927928      quartzParachainJunction = {Parachain: QUARTZ_CHAIN};929      quartzAccountJunction = {930        AccountId32: {931          network: 'Any',932          id: alice.addressRaw,933        },934      };935936      quartzParachainMultilocation = {937        V2: {938          parents: 1,939          interior: {940            X1: quartzParachainJunction,941          },942        },943      };944945      quartzAccountMultilocation = {946        V2: {947          parents: 0,948          interior: {949            X1: quartzAccountJunction,950          },951        },952      };953954      quartzCombinedMultilocation = {955        V2: {956          parents: 1,957          interior: {958            X2: [quartzParachainJunction, quartzAccountJunction],959          },960        },961      };962963      // Set the default version to wrap the first message to other chains.964      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);965    });966967    // eslint-disable-next-line require-await968    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {969      alith = helper.account.alithAccount();970    });971  });972973  const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {974    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash975        && event.outcome.isFailedToTransactAsset);976  };977978  itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {979    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {980      const id = {981        Token: 'KAR',982      };983      const destination = quartzCombinedMultilocation;984      await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');985986      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);987    });988989    await expectFailedToTransact(helper, messageSent);990  });991992  itSub('Quartz rejects MOVR tokens from Moonriver', async ({helper}) => {993    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {994      const id = 'SelfReserve';995      const destination = quartzCombinedMultilocation;996      await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');997998      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);999    });10001001    await expectFailedToTransact(helper, messageSent);1002  });10031004  itSub('Quartz rejects SDN tokens from Shiden', async ({helper}) => {1005    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1006      const destinationParachain = quartzParachainMultilocation;1007      const beneficiary = quartzAccountMultilocation;1008      const assets = {1009        V2: [{1010          id: {1011            Concrete: {1012              parents: 0,1013              interior: 'Here',1014            },1015          },1016          fun: {1017            Fungible: testAmount,1018          },1019        }],1020      };1021      const feeAssetItem = 0;10221023      await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [1024        destinationParachain,1025        beneficiary,1026        assets,1027        feeAssetItem,1028      ]);10291030      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1031    });10321033    await expectFailedToTransact(helper, messageSent);1034  });1035});10361037describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {1038  // Quartz constants1039  let alice: IKeyringPair;1040  let quartzAssetLocation;10411042  let randomAccountQuartz: IKeyringPair;1043  let randomAccountMoonriver: IKeyringPair;10441045  // Moonriver constants1046  let assetId: string;10471048  const quartzAssetMetadata = {1049    name: 'xcQuartz',1050    symbol: 'xcQTZ',1051    decimals: 18,1052    isFrozen: false,1053    minimalBalance: 1n,1054  };10551056  let balanceQuartzTokenInit: bigint;1057  let balanceQuartzTokenMiddle: bigint;1058  let balanceQuartzTokenFinal: bigint;1059  let balanceForeignQtzTokenInit: bigint;1060  let balanceForeignQtzTokenMiddle: bigint;1061  let balanceForeignQtzTokenFinal: bigint;1062  let balanceMovrTokenInit: bigint;1063  let balanceMovrTokenMiddle: bigint;1064  let balanceMovrTokenFinal: bigint;10651066  before(async () => {1067    await usingPlaygrounds(async (helper, privateKey) => {1068      alice = await privateKey('//Alice');1069      [randomAccountQuartz] = await helper.arrange.createAccounts([0n], alice);10701071      balanceForeignQtzTokenInit = 0n;10721073      // Set the default version to wrap the first message to other chains.1074      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1075    });10761077    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1078      const alithAccount = helper.account.alithAccount();1079      const baltatharAccount = helper.account.baltatharAccount();1080      const dorothyAccount = helper.account.dorothyAccount();10811082      randomAccountMoonriver = helper.account.create();10831084      // >>> Sponsoring Dorothy >>>1085      console.log('Sponsoring Dorothy.......');1086      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);1087      console.log('Sponsoring Dorothy.......DONE');1088      // <<< Sponsoring Dorothy <<<10891090      quartzAssetLocation = {1091        XCM: {1092          parents: 1,1093          interior: {X1: {Parachain: QUARTZ_CHAIN}},1094        },1095      };1096      const existentialDeposit = 1n;1097      const isSufficient = true;1098      const unitsPerSecond = 1n;1099      const numAssetsWeightHint = 0;11001101      if((await helper.assetManager.assetTypeId(quartzAssetLocation)).toJSON()) {1102        console.log('Quartz asset already registered on Moonriver');1103      } else {1104        const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({1105          location: quartzAssetLocation,1106          metadata: quartzAssetMetadata,1107          existentialDeposit,1108          isSufficient,1109          unitsPerSecond,1110          numAssetsWeightHint,1111        });11121113        console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);11141115        await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);1116      }1117      // >>> Acquire Quartz AssetId Info on Moonriver >>>1118      console.log('Acquire Quartz AssetId Info on Moonriver.......');11191120      assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();11211122      console.log('QTZ asset ID is %s', assetId);1123      console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');1124      // >>> Acquire Quartz AssetId Info on Moonriver >>>11251126      // >>> Sponsoring random Account >>>1127      console.log('Sponsoring random Account.......');1128      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);1129      console.log('Sponsoring random Account.......DONE');1130      // <<< Sponsoring random Account <<<11311132      balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);1133    });11341135    await usingPlaygrounds(async (helper) => {1136      await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);1137      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);1138    });1139  });11401141  itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {1142    const currencyId = 0;1143    const dest = {1144      V2: {1145        parents: 1,1146        interior: {1147          X2: [1148            {Parachain: MOONRIVER_CHAIN},1149            {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},1150          ],1151        },1152      },1153    };1154    const amount = TRANSFER_AMOUNT;11551156    await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, 'Unlimited');11571158    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);1159    expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;11601161    const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;1162    console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));1163    expect(transactionFees > 0, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;11641165    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1166      await helper.wait.newBlocks(3);11671168      balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);11691170      const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;1171      console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));1172      expect(movrFees == 0n).to.be.true;11731174      balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; // BigInt(qtzRandomAccountAsset['balance']);1175      const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;1176      console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));1177      expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1178    });1179  });11801181  itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {1182    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1183      const asset = {1184        V2: {1185          id: {1186            Concrete: {1187              parents: 1,1188              interior: {1189                X1: {Parachain: QUARTZ_CHAIN},1190              },1191            },1192          },1193          fun: {1194            Fungible: TRANSFER_AMOUNT,1195          },1196        },1197      };1198      const destination = {1199        V2: {1200          parents: 1,1201          interior: {1202            X2: [1203              {Parachain: QUARTZ_CHAIN},1204              {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},1205            ],1206          },1207        },1208      };12091210      await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, 'Unlimited');12111212      balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);12131214      const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;1215      console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));1216      expect(movrFees > 0, 'Negative fees MOVR, looks like nothing was transferred').to.be.true;12171218      const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);12191220      expect(qtzRandomAccountAsset).to.be.null;12211222      balanceForeignQtzTokenFinal = 0n;12231224      const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;1225      console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));1226      expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1227    });12281229    await helper.wait.newBlocks(3);12301231    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);1232    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;1233    expect(actuallyDelivered > 0).to.be.true;12341235    console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));12361237    const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;1238    console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));1239    expect(qtzFees == 0n).to.be.true;1240  });12411242  itSub('Moonriver can send only up to its balance', async ({helper}) => {1243    // set Moonriver's sovereign account's balance1244    const moonriverBalance = 10000n * (10n ** QTZ_DECIMALS);1245    const moonriverSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONRIVER_CHAIN);1246    await helper.getSudo().balance.setBalanceSubstrate(alice, moonriverSovereignAccount, moonriverBalance);12471248    const moreThanMoonriverHas = moonriverBalance * 2n;12491250    let targetAccountBalance = 0n;1251    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);12521253    const quartzMultilocation = {1254      V2: {1255        parents: 1,1256        interior: {1257          X1: {Parachain: QUARTZ_CHAIN},1258        },1259      },1260    };12611262    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1263      targetAccount.addressRaw,1264      {1265        Concrete: {1266          parents: 0,1267          interior: 'Here',1268        },1269      },1270      moreThanMoonriverHas,1271    );12721273    let maliciousXcmProgramSent: any;1274    const maxWaitBlocks = 3;12751276    // Try to trick Quartz1277    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1278      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgram]);12791280      // Needed to bypass the call filter.1281      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1282      await helper.fastDemocracy.executeProposal('try to spend more QTZ than Moonriver has', batchCall);12831284      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1285    });12861287    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1288        && event.outcome.isFailedToTransactAsset);12891290    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1291    expect(targetAccountBalance).to.be.equal(0n);12921293    // But Moonriver still can send the correct amount1294    const validTransferAmount = moonriverBalance / 2n;1295    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1296      targetAccount.addressRaw,1297      {1298        Concrete: {1299          parents: 0,1300          interior: 'Here',1301        },1302      },1303      validTransferAmount,1304    );13051306    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1307      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, validXcmProgram]);13081309      // Needed to bypass the call filter.1310      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1311      await helper.fastDemocracy.executeProposal('Spend the correct amount of QTZ', batchCall);1312    });13131314    await helper.wait.newBlocks(maxWaitBlocks);13151316    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1317    expect(targetAccountBalance).to.be.equal(validTransferAmount);1318  });13191320  itSub('Should not accept reserve transfer of QTZ from Moonriver', async ({helper}) => {1321    const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1322    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);13231324    const quartzMultilocation = {1325      V2: {1326        parents: 1,1327        interior: {1328          X1: {1329            Parachain: QUARTZ_CHAIN,1330          },1331        },1332      },1333    };13341335    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1336      targetAccount.addressRaw,1337      {1338        Concrete: {1339          parents: 0,1340          interior: {1341            X1: {1342              Parachain: QUARTZ_CHAIN,1343            },1344          },1345        },1346      },1347      testAmount,1348    );13491350    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1351      targetAccount.addressRaw,1352      {1353        Concrete: {1354          parents: 0,1355          interior: 'Here',1356        },1357      },1358      testAmount,1359    );13601361    let maliciousXcmProgramFullIdSent: any;1362    let maliciousXcmProgramHereIdSent: any;1363    const maxWaitBlocks = 3;13641365    // Try to trick Quartz using full QTZ identification1366    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1367      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramFullId]);13681369      // Needed to bypass the call filter.1370      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1371      await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using path asset identification', batchCall);13721373      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1374    });13751376    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1377        && event.outcome.isUntrustedReserveLocation);13781379    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1380    expect(accountBalance).to.be.equal(0n);13811382    // Try to trick Quartz using shortened QTZ identification1383    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1384      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramHereId]);13851386      // Needed to bypass the call filter.1387      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1388      await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using "here" asset identification', batchCall);13891390      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1391    });13921393    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1394        && event.outcome.isUntrustedReserveLocation);13951396    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1397    expect(accountBalance).to.be.equal(0n);1398  });1399});14001401describeXCM('[XCM] Integration test: Exchanging tokens with Shiden', () => {1402  let alice: IKeyringPair;1403  let sender: IKeyringPair;14041405  const QTZ_ASSET_ID_ON_SHIDEN = 18_446_744_073_709_551_633n; // The value is taken from the live Shiden1406  const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n; // The value is taken from the live Shiden14071408  // Quartz -> Shiden1409  const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); // 1 SHD, existential deposit required to actually create the account on Shiden1410  const unitsPerSecond = 500_451_000_000_000_000_000n; // The value is taken from the live Shiden1411  const qtzToShidenTransferred = 10n * (10n ** QTZ_DECIMALS); // 10 QTZ1412  const qtzToShidenArrived = 7_998_196_000_000_000_000n; // 7.99 ... QTZ, Shiden takes a commision in foreign tokens14131414  // Shiden -> Quartz1415  const qtzFromShidenTransfered = 5n * (10n ** QTZ_DECIMALS); // 5 QTZ1416  const qtzOnShidenLeft = qtzToShidenArrived - qtzFromShidenTransfered; // 2.99 ... QTZ14171418  let balanceAfterQuartzToShidenXCM: bigint;14191420  before(async () => {1421    await usingPlaygrounds(async (helper, privateKey) => {1422      alice = await privateKey('//Alice');1423      [sender] = await helper.arrange.createAccounts([100n], alice);1424      console.log('sender', sender.address);14251426      // Set the default version to wrap the first message to other chains.1427      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1428    });14291430    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1431      if(!(await helper.callRpc('api.query.assets.asset', [QTZ_ASSET_ID_ON_SHIDEN])).toJSON()) {1432        console.log('1. Create foreign asset and metadata');1433        await helper.getSudo().assets.forceCreate(1434          alice,1435          QTZ_ASSET_ID_ON_SHIDEN,1436          alice.address,1437          QTZ_MINIMAL_BALANCE_ON_SHIDEN,1438        );14391440        await helper.assets.setMetadata(1441          alice,1442          QTZ_ASSET_ID_ON_SHIDEN,1443          'Quartz',1444          'QTZ',1445          Number(QTZ_DECIMALS),1446        );14471448        console.log('2. Register asset location on Shiden');1449        const assetLocation = {1450          V2: {1451            parents: 1,1452            interior: {1453              X1: {1454                Parachain: QUARTZ_CHAIN,1455              },1456            },1457          },1458        };14591460        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]);14611462        console.log('3. Set QTZ payment for XCM execution on Shiden');1463        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);1464      } else {1465        console.log('QTZ is already registered on Shiden');1466      }1467      console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)');1468      await helper.balance.transferToSubstrate(alice, sender.address, shidenInitialBalance);1469    });1470  });14711472  itSub('Should connect and send QTZ to Shiden', async ({helper}) => {1473    const destination = {1474      V2: {1475        parents: 1,1476        interior: {1477          X1: {1478            Parachain: SHIDEN_CHAIN,1479          },1480        },1481      },1482    };14831484    const beneficiary = {1485      V2: {1486        parents: 0,1487        interior: {1488          X1: {1489            AccountId32: {1490              network: 'Any',1491              id: sender.addressRaw,1492            },1493          },1494        },1495      },1496    };14971498    const assets = {1499      V2: [1500        {1501          id: {1502            Concrete: {1503              parents: 0,1504              interior: 'Here',1505            },1506          },1507          fun: {1508            Fungible: qtzToShidenTransferred,1509          },1510        },1511      ],1512    };15131514    // Initial balance is 100 QTZ1515    const balanceBefore = await helper.balance.getSubstrate(sender.address);1516    console.log(`Initial balance is: ${balanceBefore}`);15171518    const feeAssetItem = 0;1519    await helper.xcm.limitedReserveTransferAssets(sender, destination, beneficiary, assets, feeAssetItem, 'Unlimited');15201521    // Balance after reserve transfer is less than 901522    balanceAfterQuartzToShidenXCM = await helper.balance.getSubstrate(sender.address);1523    console.log(`QTZ Balance on Quartz after XCM is: ${balanceAfterQuartzToShidenXCM}`);1524    console.log(`Quartz's QTZ commission is: ${balanceBefore - balanceAfterQuartzToShidenXCM}`);1525    expect(balanceBefore - balanceAfterQuartzToShidenXCM > 0).to.be.true;15261527    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1528      await helper.wait.newBlocks(3);1529      const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1530      const shidenBalance = await helper.balance.getSubstrate(sender.address);15311532      console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);1533      console.log(`Shiden's QTZ commission is: ${qtzToShidenTransferred - xcQTZbalance!}`);15341535      expect(xcQTZbalance).to.eq(qtzToShidenArrived);1536      // SHD balance does not changed:1537      expect(shidenBalance).to.eq(shidenInitialBalance);1538    });1539  });15401541  itSub('Should connect to Shiden and send QTZ back', async ({helper}) => {1542    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1543      const destination = {1544        V2: {1545          parents: 1,1546          interior: {1547            X1: {1548              Parachain: QUARTZ_CHAIN,1549            },1550          },1551        },1552      };15531554      const beneficiary = {1555        V2: {1556          parents: 0,1557          interior: {1558            X1: {1559              AccountId32: {1560                network: 'Any',1561                id: sender.addressRaw,1562              },1563            },1564          },1565        },1566      };15671568      const assets = {1569        V2: [1570          {1571            id: {1572              Concrete: {1573                parents: 1,1574                interior: {1575                  X1: {1576                    Parachain: QUARTZ_CHAIN,1577                  },1578                },1579              },1580            },1581            fun: {1582              Fungible: qtzFromShidenTransfered,1583            },1584          },1585        ],1586      };15871588      // Initial balance is 1 SDN1589      const balanceSDNbefore = await helper.balance.getSubstrate(sender.address);1590      console.log(`SDN balance is: ${balanceSDNbefore}, it does not changed`);1591      expect(balanceSDNbefore).to.eq(shidenInitialBalance);15921593      const feeAssetItem = 0;1594      // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1595      await helper.executeExtrinsic(sender, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);15961597      // Balance after reserve transfer is less than 1 SDN1598      const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1599      const balanceSDN = await helper.balance.getSubstrate(sender.address);1600      console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);16011602      // Assert: xcQTZ balance correctly decreased1603      expect(xcQTZbalance).to.eq(qtzOnShidenLeft);1604      // Assert: SDN balance is 0.996...1605      expect(balanceSDN / (10n ** (SHIDEN_DECIMALS - 3n))).to.eq(996n);1606    });16071608    await helper.wait.newBlocks(3);1609    const balanceQTZ = await helper.balance.getSubstrate(sender.address);1610    console.log(`QTZ Balance on Quartz after XCM is: ${balanceQTZ}`);1611    expect(balanceQTZ).to.eq(balanceAfterQuartzToShidenXCM + qtzFromShidenTransfered);1612  });16131614  itSub('Shiden can send only up to its balance', async ({helper}) => {1615    // set Shiden's sovereign account's balance1616    const shidenBalance = 10000n * (10n ** QTZ_DECIMALS);1617    const shidenSovereignAccount = helper.address.paraSiblingSovereignAccount(SHIDEN_CHAIN);1618    await helper.getSudo().balance.setBalanceSubstrate(alice, shidenSovereignAccount, shidenBalance);16191620    const moreThanShidenHas = shidenBalance * 2n;16211622    let targetAccountBalance = 0n;1623    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);16241625    const quartzMultilocation = {1626      V2: {1627        parents: 1,1628        interior: {1629          X1: {Parachain: QUARTZ_CHAIN},1630        },1631      },1632    };16331634    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1635      targetAccount.addressRaw,1636      {1637        Concrete: {1638          parents: 0,1639          interior: 'Here',1640        },1641      },1642      moreThanShidenHas,1643    );16441645    let maliciousXcmProgramSent: any;1646    const maxWaitBlocks = 3;16471648    // Try to trick Quartz1649    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1650      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);16511652      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1653    });16541655    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1656        && event.outcome.isFailedToTransactAsset);16571658    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1659    expect(targetAccountBalance).to.be.equal(0n);16601661    // But Shiden still can send the correct amount1662    const validTransferAmount = shidenBalance / 2n;1663    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1664      targetAccount.addressRaw,1665      {1666        Concrete: {1667          parents: 0,1668          interior: 'Here',1669        },1670      },1671      validTransferAmount,1672    );16731674    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1675      await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);1676    });16771678    await helper.wait.newBlocks(maxWaitBlocks);16791680    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1681    expect(targetAccountBalance).to.be.equal(validTransferAmount);1682  });16831684  itSub('Should not accept reserve transfer of QTZ from Shiden', async ({helper}) => {1685    const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1686    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);16871688    const quartzMultilocation = {1689      V2: {1690        parents: 1,1691        interior: {1692          X1: {1693            Parachain: QUARTZ_CHAIN,1694          },1695        },1696      },1697    };16981699    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1700      targetAccount.addressRaw,1701      {1702        Concrete: {1703          parents: 1,1704          interior: {1705            X1: {1706              Parachain: QUARTZ_CHAIN,1707            },1708          },1709        },1710      },1711      testAmount,1712    );17131714    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1715      targetAccount.addressRaw,1716      {1717        Concrete: {1718          parents: 0,1719          interior: 'Here',1720        },1721      },1722      testAmount,1723    );17241725    let maliciousXcmProgramFullIdSent: any;1726    let maliciousXcmProgramHereIdSent: any;1727    const maxWaitBlocks = 3;17281729    // Try to trick Quartz using full QTZ identification1730    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1731      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);17321733      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1734    });17351736    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1737        && event.outcome.isUntrustedReserveLocation);17381739    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1740    expect(accountBalance).to.be.equal(0n);17411742    // Try to trick Quartz using shortened QTZ identification1743    await usingShidenPlaygrounds(shidenUrl, async (helper) => {1744      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);17451746      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1747    });17481749    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1750        && event.outcome.isUntrustedReserveLocation);17511752    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1753    expect(accountBalance).to.be.equal(0n);1754  });1755});