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

difftreelog

source

js-packages/tests/xcm/xcmQuartz.test.ts54.5 KiBsourcehistory
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';22232425const STATEMINE_PALLET_INSTANCE = 50;2627const TRANSFER_AMOUNT = 2000000000000000000000000n;2829const FUNDING_AMOUNT = 3_500_000_0000_000_000n;3031const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;3233const USDT_ASSET_ID = 100;34const USDT_ASSET_METADATA_DECIMALS = 18;35const USDT_ASSET_METADATA_NAME = 'USDT';36const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';37const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;38const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;3940const SAFE_XCM_VERSION = 2;4142describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => {43  let alice: IKeyringPair;44  let bob: IKeyringPair;4546  let balanceStmnBefore: bigint;47  let balanceStmnAfter: bigint;4849  let balanceQuartzBefore: bigint;50  let balanceQuartzAfter: bigint;51  let balanceQuartzFinal: bigint;5253  let balanceBobBefore: bigint;54  let balanceBobAfter: bigint;55  let balanceBobFinal: bigint;5657  let balanceBobRelayTokenBefore: bigint;58  let balanceBobRelayTokenAfter: bigint;596061  before(async () => {62    await usingPlaygrounds(async (helper, privateKey) => {63      alice = await privateKey('//Alice');64      bob = await privateKey('//Bob'); // sovereign account on Statemine(t) funds donor6566      // Set the default version to wrap the first message to other chains.67      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);68    });6970    await usingRelayPlaygrounds(relayUrl, async (helper) => {71      // Fund accounts on Statemine(t)72      await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, alice.addressRaw, FUNDING_AMOUNT);73      await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, bob.addressRaw, FUNDING_AMOUNT);74    });7576    await usingStateminePlaygrounds(statemineUrl, async (helper) => {77      const sovereignFundingAmount = 3_500_000_000n;7879      await helper.assets.create(80        alice,81        USDT_ASSET_ID,82        alice.address,83        USDT_ASSET_METADATA_MINIMAL_BALANCE,84      );85      await helper.assets.setMetadata(86        alice,87        USDT_ASSET_ID,88        USDT_ASSET_METADATA_NAME,89        USDT_ASSET_METADATA_DESCRIPTION,90        USDT_ASSET_METADATA_DECIMALS,91      );92      await helper.assets.mint(93        alice,94        USDT_ASSET_ID,95        alice.address,96        USDT_ASSET_AMOUNT,97      );9899      // funding parachain sovereing account on Statemine(t).100      // The sovereign account should be created before any action101      // (the assets pallet on Statemine(t) check if the sovereign account exists)102      const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(QUARTZ_CHAIN);103      await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);104    });105106107    await usingPlaygrounds(async (helper) => {108      const location = {109        V2: {110          parents: 1,111          interior: {X3: [112            {113              Parachain: STATEMINE_CHAIN,114            },115            {116              PalletInstance: STATEMINE_PALLET_INSTANCE,117            },118            {119              GeneralIndex: USDT_ASSET_ID,120            },121          ]},122        },123      };124125      const metadata =126      {127        name: USDT_ASSET_ID,128        symbol: USDT_ASSET_METADATA_NAME,129        decimals: USDT_ASSET_METADATA_DECIMALS,130        minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,131      };132      await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);133      balanceQuartzBefore = await helper.balance.getSubstrate(alice.address);134    });135136137    // Providing the relay currency to the quartz sender account138    // (fee for USDT XCM are paid in relay tokens)139    await usingRelayPlaygrounds(relayUrl, async (helper) => {140      const destination = {141        V2: {142          parents: 0,143          interior: {X1: {144            Parachain: QUARTZ_CHAIN,145          },146          },147        }};148149      const beneficiary = {150        V2: {151          parents: 0,152          interior: {X1: {153            AccountId32: {154              network: 'Any',155              id: alice.addressRaw,156            },157          }},158        },159      };160161      const assets = {162        V2: [163          {164            id: {165              Concrete: {166                parents: 0,167                interior: 'Here',168              },169            },170            fun: {171              Fungible: TRANSFER_AMOUNT_RELAY,172            },173          },174        ],175      };176177      const feeAssetItem = 0;178179      await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');180    });181182  });183184  itSub('Should connect and send USDT from Statemine to Quartz', async ({helper}) => {185    await usingStateminePlaygrounds(statemineUrl, async (helper) => {186      const dest = {187        V2: {188          parents: 1,189          interior: {X1: {190            Parachain: QUARTZ_CHAIN,191          },192          },193        }};194195      const beneficiary = {196        V2: {197          parents: 0,198          interior: {X1: {199            AccountId32: {200              network: 'Any',201              id: alice.addressRaw,202            },203          }},204        },205      };206207      const assets = {208        V2: [209          {210            id: {211              Concrete: {212                parents: 0,213                interior: {214                  X2: [215                    {216                      PalletInstance: STATEMINE_PALLET_INSTANCE,217                    },218                    {219                      GeneralIndex: USDT_ASSET_ID,220                    },221                  ]},222              },223            },224            fun: {225              Fungible: TRANSFER_AMOUNT,226            },227          },228        ],229      };230231      const feeAssetItem = 0;232233      balanceStmnBefore = await helper.balance.getSubstrate(alice.address);234      await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');235236      balanceStmnAfter = await helper.balance.getSubstrate(alice.address);237238      // common good parachain take commission in it native token239      console.log(240        '[Statemine -> Quartz] transaction fees on Statemine: %s WND',241        helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINE_DECIMALS),242      );243      expect(balanceStmnBefore > balanceStmnAfter).to.be.true;244245    });246247248    // ensure that asset has been delivered249    await helper.wait.newBlocks(3);250251    // expext collection id will be with id 1252    const free = await helper.ft.getBalance(1, {Substrate: alice.address});253254    balanceQuartzAfter = await helper.balance.getSubstrate(alice.address);255256    console.log(257      '[Statemine -> Quartz] transaction fees on Quartz: %s USDT',258      helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),259    );260    console.log(261      '[Statemine -> Quartz] transaction fees on Quartz: %s QTZ',262      helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzBefore),263    );264    // commission has not paid in USDT token265    expect(free).to.be.equal(TRANSFER_AMOUNT);266    // ... and parachain native token267    expect(balanceQuartzAfter == balanceQuartzBefore).to.be.true;268  });269270  itSub('Should connect and send USDT from Quartz to Statemine back', async ({helper}) => {271    const destination = {272      V2: {273        parents: 1,274        interior: {X2: [275          {276            Parachain: STATEMINE_CHAIN,277          },278          {279            AccountId32: {280              network: 'Any',281              id: alice.addressRaw,282            },283          },284        ]},285      },286    };287288    const relayFee = 400_000_000_000_000n;289    const currencies: [any, bigint][] = [290      [291        {292          ForeignAssetId: 0,293        },294        TRANSFER_AMOUNT,295      ],296      [297        {298          NativeAssetId: 'Parent',299        },300        relayFee,301      ],302    ];303304    const feeItem = 1;305306    await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');307308    // the commission has been paid in parachain native token309    balanceQuartzFinal = await helper.balance.getSubstrate(alice.address);310    console.log('[Quartz -> Statemine] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzFinal));311    expect(balanceQuartzAfter > balanceQuartzFinal).to.be.true;312313    await usingStateminePlaygrounds(statemineUrl, async (helper) => {314      await helper.wait.newBlocks(3);315316      // The USDT token never paid fees. Its amount not changed from begin value.317      // Also check that xcm transfer has been succeeded318      expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;319    });320  });321322  itSub('Should connect and send Relay token to Quartz', async ({helper}) => {323    balanceBobBefore = await helper.balance.getSubstrate(bob.address);324    balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});325326    await usingRelayPlaygrounds(relayUrl, async (helper) => {327      const destination = {328        V2: {329          parents: 0,330          interior: {X1: {331            Parachain: QUARTZ_CHAIN,332          },333          },334        }};335336      const beneficiary = {337        V2: {338          parents: 0,339          interior: {X1: {340            AccountId32: {341              network: 'Any',342              id: bob.addressRaw,343            },344          }},345        },346      };347348      const assets = {349        V2: [350          {351            id: {352              Concrete: {353                parents: 0,354                interior: 'Here',355              },356            },357            fun: {358              Fungible: TRANSFER_AMOUNT_RELAY,359            },360          },361        ],362      };363364      const feeAssetItem = 0;365366      await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');367    });368369    await helper.wait.newBlocks(3);370371    balanceBobAfter = await helper.balance.getSubstrate(bob.address);372    balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});373374    const wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;375    const wndDiffOnQuartz = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;376    console.log(377      '[Relay (Westend) -> Quartz] transaction fees: %s QTZ',378      helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),379    );380    console.log(381      '[Relay (Westend) -> Quartz] transaction fees: %s WND',382      helper.util.bigIntToDecimals(wndFeeOnQuartz, STATEMINE_DECIMALS),383    );384    console.log('[Relay (Westend) -> Quartz] actually delivered: %s WND', wndDiffOnQuartz);385    expect(wndFeeOnQuartz == 0n, 'No incoming WND fees should be taken').to.be.true;386    expect(balanceBobBefore == balanceBobAfter, 'No incoming QTZ fees should be taken').to.be.true;387  });388389  itSub('Should connect and send Relay token back', async ({helper}) => {390    let relayTokenBalanceBefore: bigint;391    let relayTokenBalanceAfter: bigint;392    await usingRelayPlaygrounds(relayUrl, async (helper) => {393      relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);394    });395396    const destination = {397      V2: {398        parents: 1,399        interior: {400          X1:{401            AccountId32: {402              network: 'Any',403              id: bob.addressRaw,404            },405          },406        },407      },408    };409410    const currencies: any = [411      [412        {413          NativeAssetId: 'Parent',414        },415        TRANSFER_AMOUNT_RELAY,416      ],417    ];418419    const feeItem = 0;420421    await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');422423    balanceBobFinal = await helper.balance.getSubstrate(bob.address);424    console.log('[Quartz -> Relay (Westend)] transaction fees: %s QTZ',  helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));425426    await usingRelayPlaygrounds(relayUrl, async (helper) => {427      await helper.wait.newBlocks(10);428      relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);429430      const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;431      console.log('[Quartz -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));432      expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;433    });434  });435});436437describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => {438  let alice: IKeyringPair;439  let randomAccount: IKeyringPair;440441  let balanceQuartzTokenInit: bigint;442  let balanceQuartzTokenMiddle: bigint;443  let balanceQuartzTokenFinal: bigint;444  let balanceKaruraTokenInit: bigint;445  let balanceKaruraTokenMiddle: bigint;446  let balanceKaruraTokenFinal: bigint;447  let balanceQuartzForeignTokenInit: bigint;448  let balanceQuartzForeignTokenMiddle: bigint;449  let balanceQuartzForeignTokenFinal: bigint;450451  // computed by a test transfer from prod Quartz to prod Karura.452  // 2 QTZ sent https://quartz.subscan.io/xcm_message/kusama-f60d821b049f8835a3005ce7102285006f5b61e9453  // 1.919176000000000000 QTZ received (you can check Karura's chain state in the corresponding block)454  const expectedKaruraIncomeFee = 2000000000000000000n - 1919176000000000000n;455  const karuraEps = 8n * 10n ** 16n;456457  let karuraBackwardTransferAmount: bigint;458459  before(async () => {460    await usingPlaygrounds(async (helper, privateKey) => {461      alice = await privateKey('//Alice');462      [randomAccount] = await helper.arrange.createAccounts([0n], alice);463464      // Set the default version to wrap the first message to other chains.465      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);466    });467468    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {469      const destination = {470        V2: {471          parents: 1,472          interior: {473            X1: {474              Parachain: QUARTZ_CHAIN,475            },476          },477        },478      };479480      const metadata = {481        name: 'Quartz',482        symbol: 'QTZ',483        decimals: 18,484        minimalBalance: 1000000000000000000n,485      };486487      const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v]: [any, any]) =>488        hexToString(v.toJSON()['symbol'])) as string[];489490      if(!assets.includes('QTZ')) {491        await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);492      } else {493        console.log('QTZ token already registered on Karura assetRegistry pallet');494      }495      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);496      balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);497      balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});498    });499500    await usingPlaygrounds(async (helper) => {501      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);502      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);503    });504  });505506  itSub('Should connect and send QTZ to Karura', async ({helper}) => {507    const destination = {508      V2: {509        parents: 1,510        interior: {511          X1: {512            Parachain: KARURA_CHAIN,513          },514        },515      },516    };517518    const beneficiary = {519      V2: {520        parents: 0,521        interior: {522          X1: {523            AccountId32: {524              network: 'Any',525              id: randomAccount.addressRaw,526            },527          },528        },529      },530    };531532    const assets = {533      V2: [534        {535          id: {536            Concrete: {537              parents: 0,538              interior: 'Here',539            },540          },541          fun: {542            Fungible: TRANSFER_AMOUNT,543          },544        },545      ],546    };547548    const feeAssetItem = 0;549550    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');551    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);552553    const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;554    expect(qtzFees > 0n, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;555    console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));556557    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {558      await helper.wait.newBlocks(3);559560      balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});561      balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);562563      const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;564      const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;565      karuraBackwardTransferAmount = qtzIncomeTransfer;566567      const karUnqFees = TRANSFER_AMOUNT - qtzIncomeTransfer;568569      console.log(570        '[Quartz -> Karura] transaction fees on Karura: %s KAR',571        helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),572      );573      console.log(574        '[Quartz -> Karura] transaction fees on Karura: %s QTZ',575        helper.util.bigIntToDecimals(karUnqFees),576      );577      console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));578      expect(karFees == 0n).to.be.true;579580      const bigintAbs = (n: bigint) => (n < 0n) ? -n : n;581582      expect(583        bigintAbs(karUnqFees - expectedKaruraIncomeFee) < karuraEps,584        'Karura took different income fee, check the Karura foreign asset config',585      ).to.be.true;586    });587  });588589  itSub('Should connect to Karura and send QTZ back', async ({helper}) => {590    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {591      const destination = {592        V2: {593          parents: 1,594          interior: {595            X2: [596              {Parachain: QUARTZ_CHAIN},597              {598                AccountId32: {599                  network: 'Any',600                  id: randomAccount.addressRaw,601                },602              },603            ],604          },605        },606      };607608      const id = {609        ForeignAsset: 0,610      };611612      await helper.xTokens.transfer(randomAccount, id, karuraBackwardTransferAmount, destination, 'Unlimited');613      balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);614      balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);615616      const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;617      const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;618619      console.log(620        '[Karura -> Quartz] transaction fees on Karura: %s KAR',621        helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),622      );623      console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));624625      expect(karFees > 0, 'Negative fees KAR, looks like nothing was transferred').to.be.true;626      expect(qtzOutcomeTransfer == karuraBackwardTransferAmount).to.be.true;627    });628629    await helper.wait.newBlocks(3);630631    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);632    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;633    expect(actuallyDelivered > 0).to.be.true;634635    console.log('[Karura -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));636637    const qtzFees = karuraBackwardTransferAmount - actuallyDelivered;638    console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));639    expect(qtzFees == 0n).to.be.true;640  });641642  itSub('Karura can send only up to its balance', async ({helper}) => {643    // set Karura's sovereign account's balance644    const karuraBalance = 10000n * (10n ** QTZ_DECIMALS);645    const karuraSovereignAccount = helper.address.paraSiblingSovereignAccount(KARURA_CHAIN);646    await helper.getSudo().balance.setBalanceSubstrate(alice, karuraSovereignAccount, karuraBalance);647648    const moreThanKaruraHas = karuraBalance * 2n;649650    let targetAccountBalance = 0n;651    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);652653    const quartzMultilocation = {654      V2: {655        parents: 1,656        interior: {657          X1: {Parachain: QUARTZ_CHAIN},658        },659      },660    };661662    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(663      targetAccount.addressRaw,664      {665        Concrete: {666          parents: 0,667          interior: 'Here',668        },669      },670      moreThanKaruraHas,671    );672673    let maliciousXcmProgramSent: any;674    const maxWaitBlocks = 5;675676    // Try to trick Quartz677    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {678      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);679680      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);681    });682683    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash684        && event.outcome.isFailedToTransactAsset);685686    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);687    expect(targetAccountBalance).to.be.equal(0n);688689    // But Karura still can send the correct amount690    const validTransferAmount = karuraBalance / 2n;691    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(692      targetAccount.addressRaw,693      {694        Concrete: {695          parents: 0,696          interior: 'Here',697        },698      },699      validTransferAmount,700    );701702    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {703      await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);704    });705706    await helper.wait.newBlocks(maxWaitBlocks);707708    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);709    expect(targetAccountBalance).to.be.equal(validTransferAmount);710  });711712  itSub('Should not accept reserve transfer of QTZ from Karura', async ({helper}) => {713    const testAmount = 10_000n * (10n ** QTZ_DECIMALS);714    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);715716    const quartzMultilocation = {717      V2: {718        parents: 1,719        interior: {720          X1: {721            Parachain: QUARTZ_CHAIN,722          },723        },724      },725    };726727    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(728      targetAccount.addressRaw,729      {730        Concrete: {731          parents: 1,732          interior: {733            X1: {734              Parachain: QUARTZ_CHAIN,735            },736          },737        },738      },739      testAmount,740    );741742    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(743      targetAccount.addressRaw,744      {745        Concrete: {746          parents: 0,747          interior: 'Here',748        },749      },750      testAmount,751    );752753    let maliciousXcmProgramFullIdSent: any;754    let maliciousXcmProgramHereIdSent: any;755    const maxWaitBlocks = 3;756757    // Try to trick Quartz using full QTZ identification758    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {759      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);760761      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);762    });763764    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash765        && event.outcome.isUntrustedReserveLocation);766767    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);768    expect(accountBalance).to.be.equal(0n);769770    // Try to trick Quartz using shortened QTZ identification771    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {772      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);773774      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);775    });776777    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash778        && event.outcome.isUntrustedReserveLocation);779780    accountBalance = await helper.balance.getSubstrate(targetAccount.address);781    expect(accountBalance).to.be.equal(0n);782  });783});784785// These tests are relevant only when786// the the corresponding foreign assets are not registered787describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => {788  let alice: IKeyringPair;789  let alith: IKeyringPair;790791  const testAmount = 100_000_000_000n;792  let quartzParachainJunction;793  let quartzAccountJunction;794795  let quartzParachainMultilocation: any;796  let quartzAccountMultilocation: any;797  let quartzCombinedMultilocation: any;798799  let messageSent: any;800801  const maxWaitBlocks = 3;802803  before(async () => {804    await usingPlaygrounds(async (helper, privateKey) => {805      alice = await privateKey('//Alice');806807      quartzParachainJunction = {Parachain: QUARTZ_CHAIN};808      quartzAccountJunction = {809        AccountId32: {810          network: 'Any',811          id: alice.addressRaw,812        },813      };814815      quartzParachainMultilocation = {816        V2: {817          parents: 1,818          interior: {819            X1: quartzParachainJunction,820          },821        },822      };823824      quartzAccountMultilocation = {825        V2: {826          parents: 0,827          interior: {828            X1: quartzAccountJunction,829          },830        },831      };832833      quartzCombinedMultilocation = {834        V2: {835          parents: 1,836          interior: {837            X2: [quartzParachainJunction, quartzAccountJunction],838          },839        },840      };841842      // Set the default version to wrap the first message to other chains.843      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);844    });845846    // eslint-disable-next-line require-await847    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {848      alith = helper.account.alithAccount();849    });850  });851852  const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {853    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash854        && event.outcome.isFailedToTransactAsset);855  };856857  itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {858    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {859      const id = {860        Token: 'KAR',861      };862      const destination = quartzCombinedMultilocation;863      await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');864865      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);866    });867868    await expectFailedToTransact(helper, messageSent);869  });870871  itSub('Quartz rejects MOVR tokens from Moonriver', async ({helper}) => {872    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {873      const id = 'SelfReserve';874      const destination = quartzCombinedMultilocation;875      await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');876877      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);878    });879880    await expectFailedToTransact(helper, messageSent);881  });882883  itSub('Quartz rejects SDN tokens from Shiden', async ({helper}) => {884    await usingShidenPlaygrounds(shidenUrl, async (helper) => {885      const destinationParachain = quartzParachainMultilocation;886      const beneficiary = quartzAccountMultilocation;887      const assets = {888        V2: [{889          id: {890            Concrete: {891              parents: 0,892              interior: 'Here',893            },894          },895          fun: {896            Fungible: testAmount,897          },898        }],899      };900      const feeAssetItem = 0;901902      await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [903        destinationParachain,904        beneficiary,905        assets,906        feeAssetItem,907      ]);908909      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);910    });911912    await expectFailedToTransact(helper, messageSent);913  });914});915916describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {917  // Quartz constants918  let alice: IKeyringPair;919  let quartzAssetLocation;920921  let randomAccountQuartz: IKeyringPair;922  let randomAccountMoonriver: IKeyringPair;923924  // Moonriver constants925  let assetId: string;926927  const quartzAssetMetadata = {928    name: 'xcQuartz',929    symbol: 'xcQTZ',930    decimals: 18,931    isFrozen: false,932    minimalBalance: 1n,933  };934935  let balanceQuartzTokenInit: bigint;936  let balanceQuartzTokenMiddle: bigint;937  let balanceQuartzTokenFinal: bigint;938  let balanceForeignQtzTokenInit: bigint;939  let balanceForeignQtzTokenMiddle: bigint;940  let balanceForeignQtzTokenFinal: bigint;941  let balanceMovrTokenInit: bigint;942  let balanceMovrTokenMiddle: bigint;943  let balanceMovrTokenFinal: bigint;944945  before(async () => {946    await usingPlaygrounds(async (helper, privateKey) => {947      alice = await privateKey('//Alice');948      [randomAccountQuartz] = await helper.arrange.createAccounts([0n], alice);949950      balanceForeignQtzTokenInit = 0n;951952      // Set the default version to wrap the first message to other chains.953      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);954    });955956    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {957      const alithAccount = helper.account.alithAccount();958      const baltatharAccount = helper.account.baltatharAccount();959      const dorothyAccount = helper.account.dorothyAccount();960961      randomAccountMoonriver = helper.account.create();962963      // >>> Sponsoring Dorothy >>>964      console.log('Sponsoring Dorothy.......');965      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);966      console.log('Sponsoring Dorothy.......DONE');967      // <<< Sponsoring Dorothy <<<968969      quartzAssetLocation = {970        XCM: {971          parents: 1,972          interior: {X1: {Parachain: QUARTZ_CHAIN}},973        },974      };975      const existentialDeposit = 1n;976      const isSufficient = true;977      const unitsPerSecond = 1n;978      const numAssetsWeightHint = 0;979980      if((await helper.assetManager.assetTypeId(quartzAssetLocation)).toJSON()) {981        console.log('Quartz asset already registered on Moonriver');982      } else {983        const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({984          location: quartzAssetLocation,985          metadata: quartzAssetMetadata,986          existentialDeposit,987          isSufficient,988          unitsPerSecond,989          numAssetsWeightHint,990        });991992        console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);993994        await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);995      }996      // >>> Acquire Quartz AssetId Info on Moonriver >>>997      console.log('Acquire Quartz AssetId Info on Moonriver.......');998999      assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();10001001      console.log('QTZ asset ID is %s', assetId);1002      console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');1003      // >>> Acquire Quartz AssetId Info on Moonriver >>>10041005      // >>> Sponsoring random Account >>>1006      console.log('Sponsoring random Account.......');1007      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);1008      console.log('Sponsoring random Account.......DONE');1009      // <<< Sponsoring random Account <<<10101011      balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);1012    });10131014    await usingPlaygrounds(async (helper) => {1015      await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);1016      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);1017    });1018  });10191020  itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {1021    const currencyId = {1022      NativeAssetId: 'Here',1023    };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});