git.delta.rocks / unique-network / refs/commits / 9f829acfc3d3

difftreelog

refactor use playgrounds in quartz xcm tests

Daniel Shiposha2022-10-10parent: #ea2cca9.patch.diff
in: master

1 file changed

modifiedtests/src/xcm/xcmQuartz.test.tsdiffbeforeafterboth
before · tests/src/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 {Keyring} from '@polkadot/api';18import {IKeyringPair} from '@polkadot/types/types';19import {submitTransactionAsync} from '../substrate/substrate-api';20import {getGenericResult, generateKeyringPair, waitEvent, bigIntToDecimals} from '../deprecated-helpers/helpers';21import {MultiLocation} from '@polkadot/types/interfaces';22import {blake2AsHex} from '@polkadot/util-crypto';23import getBalance from '../substrate/get-balance';24import {XcmV2TraitsOutcome, XcmV2TraitsError} from '../interfaces';25import {itSub, expect, describeXcm, usingPlaygrounds} from '../util/playgrounds';2627const QUARTZ_CHAIN = 2095;28const KARURA_CHAIN = 2000;29const MOONRIVER_CHAIN = 2023;3031const RELAY_PORT = 9844;32const KARURA_PORT = 9946;33const MOONRIVER_PORT = 9947;3435const relayUrl = 'ws://127.0.0.1:' + RELAY_PORT;36const karuraUrl = 'ws://127.0.0.1:' + KARURA_PORT;37const moonriverUrl = 'ws://127.0.0.1:' + MOONRIVER_PORT;3839const KARURA_DECIMALS = 12;4041const TRANSFER_AMOUNT = 2000000000000000000000000n;4243describeXcm('[XCM] Integration test: Exchanging tokens with Karura', () => {44  let alice: IKeyringPair;45  let randomAccount: IKeyringPair;4647  let balanceQuartzTokenInit: bigint;48  let balanceQuartzTokenMiddle: bigint;49  let balanceQuartzTokenFinal: bigint;50  let balanceKaruraTokenInit: bigint;51  let balanceKaruraTokenMiddle: bigint;52  let balanceKaruraTokenFinal: bigint;53  let balanceQuartzForeignTokenInit: bigint;54  let balanceQuartzForeignTokenMiddle: bigint;55  let balanceQuartzForeignTokenFinal: bigint;5657  before(async () => {58    await usingPlaygrounds(async (helper, privateKey) => {59      const keyringSr25519 = new Keyring({type: 'sr25519'});6061      alice = privateKey('//Alice');62      randomAccount = generateKeyringPair(keyringSr25519);63    });6465    // Karura side66    await usingPlaygrounds.atUrl(karuraUrl, async (helper) => {67      const api = helper.getApi();6869      const destination = {70        V0: {71          X2: [72            'Parent',73            {74              Parachain: QUARTZ_CHAIN,75            },76          ],77        },78      };7980      const metadata = {81        name: 'QTZ',82        symbol: 'QTZ',83        decimals: 18,84        minimalBalance: 1,85      };8687      const tx = api.tx.assetRegistry.registerForeignAsset(destination, metadata);88      const sudoTx = api.tx.sudo.sudo(tx as any);89      const events = await submitTransactionAsync(alice, sudoTx);90      const result = getGenericResult(events);91      expect(result.success).to.be.true;9293      const tx1 = api.tx.balances.transfer(randomAccount.address, 10000000000000n);94      const events1 = await submitTransactionAsync(alice, tx1);95      const result1 = getGenericResult(events1);96      expect(result1.success).to.be.true;9798      [balanceKaruraTokenInit] = await getBalance(api, [randomAccount.address]);99      {100        const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, {ForeignAsset: 0})).toJSON() as any;101        balanceQuartzForeignTokenInit = BigInt(free);102      }103    });104105    // Quartz side106    await usingPlaygrounds(async (helper) => {107      // const tx0 = api.tx.balances.transfer(randomAccount.address, 10n * TRANSFER_AMOUNT);108      // const events0 = await submitTransactionAsync(alice, tx0);109      // const result0 = getGenericResult(events0);110      // expect(result0.success).to.be.true;111      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);112113      // [balanceQuartzTokenInit] = await getBalance(api, [randomAccount.address]);114      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);115    });116  });117118  itSub('Should connect and send QTZ to Karura', async ({helper}) => {119120    // Quartz side121    const destination = {122      V0: {123        X2: [124          'Parent',125          {126            Parachain: KARURA_CHAIN,127          },128        ],129      },130    };131132    const beneficiary = {133      V0: {134        X1: {135          AccountId32: {136            network: 'Any',137            id: randomAccount.addressRaw,138          },139        },140      },141    };142143    const assets = {144      V1: [145        {146          id: {147            Concrete: {148              parents: 0,149              interior: 'Here',150            },151          },152          fun: {153            Fungible: TRANSFER_AMOUNT,154          },155        },156      ],157    };158159    const feeAssetItem = 0;160161    const weightLimit = {162      Limited: 5000000000,163    };164165    // TODO166    const tx = helper.getApi().tx.polkadotXcm.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit);167    const events = await submitTransactionAsync(randomAccount, tx);168    const result = getGenericResult(events);169    expect(result.success).to.be.true;170171    // [balanceQuartzTokenMiddle] = await getBalance(api, [randomAccount.address]);172    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);173174    const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;175    console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', bigIntToDecimals(qtzFees));176    expect(qtzFees > 0n).to.be.true;177178    // Karura side179    await usingPlaygrounds.atUrl(karuraUrl, async (helper) => {180      // await waitNewBlocks(api, 3);181      await helper.wait.newBlocks(3);182183      // TODO184      const {free} = (await helper.getApi().query.tokens.accounts(randomAccount.addressRaw, {ForeignAsset: 0})).toJSON() as any;185      balanceQuartzForeignTokenMiddle = BigInt(free);186187      // [balanceKaruraTokenMiddle] = await getBalance(api, [randomAccount.address]);188      balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);189190      const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;191      const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;192193      console.log(194        '[Quartz -> Karura] transaction fees on Karura: %s KAR',195        bigIntToDecimals(karFees, KARURA_DECIMALS),196      );197      console.log('[Quartz -> Karura] income %s QTZ', bigIntToDecimals(qtzIncomeTransfer));198      expect(karFees == 0n).to.be.true;199      expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;200    });201  });202203  itSub('Should connect to Karura and send QTZ back', async ({helper}) => {204205    // Karura side206    await usingPlaygrounds.atUrl(karuraUrl, async (helper) => {207      const destination = {208        V1: {209          parents: 1,210          interior: {211            X2: [212              {Parachain: QUARTZ_CHAIN},213              {214                AccountId32: {215                  network: 'Any',216                  id: randomAccount.addressRaw,217                },218              },219            ],220          },221        },222      };223224      const id = {225        ForeignAsset: 0,226      };227228      const destWeight = 50000000;229230      // TODO231      const tx = helper.getApi().tx.xTokens.transfer(id as any, TRANSFER_AMOUNT, destination, destWeight);232      const events = await submitTransactionAsync(randomAccount, tx);233      const result = getGenericResult(events);234      expect(result.success).to.be.true;235236      // [balanceKaruraTokenFinal] = await getBalance(api, [randomAccount.address]);237      balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);238      {239        // TODO240        const {free} = (await helper.getApi().query.tokens.accounts(randomAccount.addressRaw, id)).toJSON() as any;241        balanceQuartzForeignTokenFinal = BigInt(free);242      }243244      const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;245      const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;246247      console.log(248        '[Karura -> Quartz] transaction fees on Karura: %s KAR',249        bigIntToDecimals(karFees, KARURA_DECIMALS),250      );251      console.log('[Karura -> Quartz] outcome %s QTZ', bigIntToDecimals(qtzOutcomeTransfer));252253      expect(karFees > 0).to.be.true;254      expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;255    });256257    // Quartz side258    // await waitNewBlocks(api, 3);259    await helper.wait.newBlocks(3);260261    // [balanceQuartzTokenFinal] = await getBalance(api, [randomAccount.address]);262    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);263    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;264    expect(actuallyDelivered > 0).to.be.true;265266    console.log('[Karura -> Quartz] actually delivered %s QTZ', bigIntToDecimals(actuallyDelivered));267268    const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;269    console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', bigIntToDecimals(qtzFees));270    expect(qtzFees == 0n).to.be.true;271  });272});273274// These tests are relevant only when the foreign asset pallet is disabled275describeXcm('[XCM] Integration test: Quartz rejects non-native tokens', () => {276  let alice: IKeyringPair;277278  before(async () => {279    await usingPlaygrounds(async (_helper, privateKey) => {280      alice = privateKey('//Alice');281    });282  });283284  itSub('Quartz rejects tokens from the Relay', async ({helper}) => {285    await usingPlaygrounds.atUrl(relayUrl, async (helper) => {286      const destination = {287        V1: {288          parents: 0,289          interior: {X1: {290            Parachain: QUARTZ_CHAIN,291          },292          },293        }};294295      const beneficiary = {296        V1: {297          parents: 0,298          interior: {X1: {299            AccountId32: {300              network: 'Any',301              id: alice.addressRaw,302            },303          }},304        },305      };306307      const assets = {308        V1: [309          {310            id: {311              Concrete: {312                parents: 0,313                interior: 'Here',314              },315            },316            fun: {317              Fungible: 50_000_000_000_000_000n,318            },319          },320        ],321      };322323      const feeAssetItem = 0;324325      const weightLimit = {326        Limited: 5_000_000_000,327      };328329      // TODO330      const tx = helper.getApi().tx.xcmPallet.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit);331      const events = await submitTransactionAsync(alice, tx);332      const result = getGenericResult(events);333      expect(result.success).to.be.true;334    });335336    const maxWaitBlocks = 3;337338    // TODO339    const dmpQueueExecutedDownward = await waitEvent(340      helper.getApi(),341      maxWaitBlocks,342      'dmpQueue',343      'ExecutedDownward',344    );345346    expect(347      dmpQueueExecutedDownward != null,348      '[Relay] dmpQueue.ExecutedDownward event is expected',349    ).to.be.true;350351    const event = dmpQueueExecutedDownward!.event;352    const outcome = event.data[1] as XcmV2TraitsOutcome;353354    expect(355      outcome.isIncomplete,356      '[Relay] The outcome of the XCM should be `Incomplete`',357    ).to.be.true;358359    const incomplete = outcome.asIncomplete;360    expect(361      incomplete[1].toString() == 'AssetNotFound',362      '[Relay] The XCM error should be `AssetNotFound`',363    ).to.be.true;364  });365366  itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {367    await usingPlaygrounds.atUrl(karuraUrl, async (helper) => {368      const destination = {369        V1: {370          parents: 1,371          interior: {372            X2: [373              {Parachain: QUARTZ_CHAIN},374              {375                AccountId32: {376                  network: 'Any',377                  id: alice.addressRaw,378                },379              },380            ],381          },382        },383      };384385      const id = {386        Token: 'KAR',387      };388389      const destWeight = 50000000;390391      // TODO392      const tx = helper.getApi().tx.xTokens.transfer(id as any, 100_000_000_000, destination, destWeight);393      const events = await submitTransactionAsync(alice, tx);394      const result = getGenericResult(events);395      expect(result.success).to.be.true;396    });397398    const maxWaitBlocks = 3;399400    // TODO401    const xcmpQueueFailEvent = await waitEvent(helper.getApi(), maxWaitBlocks, 'xcmpQueue', 'Fail');402403    expect(404      xcmpQueueFailEvent != null,405      '[Karura] xcmpQueue.FailEvent event is expected',406    ).to.be.true;407408    const event = xcmpQueueFailEvent!.event;409    const outcome = event.data[1] as XcmV2TraitsError;410411    expect(412      outcome.isUntrustedReserveLocation,413      '[Karura] The XCM error should be `UntrustedReserveLocation`',414    ).to.be.true;415  });416});417418describeXcm('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {419420  // Quartz constants421  let quartzAlice: IKeyringPair;422  let quartzAssetLocation;423424  let randomAccountQuartz: IKeyringPair;425  let randomAccountMoonriver: IKeyringPair;426427  // Moonriver constants428  let assetId: string;429430  const moonriverKeyring = new Keyring({type: 'ethereum'});431  const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133';432  const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b';433  const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68';434435  const alithAccount = moonriverKeyring.addFromUri(alithPrivateKey, undefined, 'ethereum');436  const baltatharAccount = moonriverKeyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum');437  const dorothyAccount = moonriverKeyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum');438439  const councilVotingThreshold = 2;440  const technicalCommitteeThreshold = 2;441  const votingPeriod = 3;442  const delayPeriod = 0;443444  const quartzAssetMetadata = {445    name: 'xcQuartz',446    symbol: 'xcQTZ',447    decimals: 18,448    isFrozen: false,449    minimalBalance: 1,450  };451452  let balanceQuartzTokenInit: bigint;453  let balanceQuartzTokenMiddle: bigint;454  let balanceQuartzTokenFinal: bigint;455  let balanceForeignQtzTokenInit: bigint;456  let balanceForeignQtzTokenMiddle: bigint;457  let balanceForeignQtzTokenFinal: bigint;458  let balanceMovrTokenInit: bigint;459  let balanceMovrTokenMiddle: bigint;460  let balanceMovrTokenFinal: bigint;461462  before(async () => {463    await usingPlaygrounds(async (_helper, privateKey) => {464      const keyringEth = new Keyring({type: 'ethereum'});465      const keyringSr25519 = new Keyring({type: 'sr25519'});466467      quartzAlice = privateKey('//Alice');468      randomAccountQuartz = generateKeyringPair(keyringSr25519);469      randomAccountMoonriver = generateKeyringPair(keyringEth);470471      balanceForeignQtzTokenInit = 0n;472    });473474    await usingPlaygrounds.atUrl(moonriverUrl, async (helper) => {475      // TODO476477      const api = helper.getApi();478479      // >>> Sponsoring Dorothy >>>480      console.log('Sponsoring Dorothy.......');481      // const tx0 = api.tx.balances.transfer(dorothyAccount.address, 11_000_000_000_000_000_000n);482      // const events0 = await submitTransactionAsync(alithAccount, tx0);483      // const result0 = getGenericResult(events0);484      // expect(result0.success).to.be.true;485      await helper.balance.transferToSubstrate(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);486      console.log('Sponsoring Dorothy.......DONE');487      // <<< Sponsoring Dorothy <<<488489      const sourceLocation: MultiLocation = api.createType(490        'MultiLocation',491        {492          parents: 1,493          interior: {X1: {Parachain: QUARTZ_CHAIN}},494        },495      );496497      quartzAssetLocation = {XCM: sourceLocation};498      const existentialDeposit = 1;499      const isSufficient = true;500      const unitsPerSecond = '1';501      const numAssetsWeightHint = 0;502503      const registerTx = api.tx.assetManager.registerForeignAsset(504        quartzAssetLocation,505        quartzAssetMetadata,506        existentialDeposit,507        isSufficient,508      );509      console.log('Encoded proposal for registerAsset is %s', registerTx.method.toHex() || '');510511      const setUnitsTx = api.tx.assetManager.setAssetUnitsPerSecond(512        quartzAssetLocation,513        unitsPerSecond,514        numAssetsWeightHint,515      );516      console.log('Encoded proposal for setAssetUnitsPerSecond is %s', setUnitsTx.method.toHex() || '');517518      const batchCall = api.tx.utility.batchAll([registerTx, setUnitsTx]);519      console.log('Encoded proposal for batchCall is %s', batchCall.method.toHex() || '');520521      // >>> Note motion preimage >>>522      console.log('Note motion preimage.......');523      const encodedProposal = batchCall?.method.toHex() || '';524      const proposalHash = blake2AsHex(encodedProposal);525      console.log('Encoded proposal for batch utility after schedule is %s', encodedProposal);526      console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);527      console.log('Encoded length %d', encodedProposal.length);528529      const tx1 = api.tx.democracy.notePreimage(encodedProposal);530      const events1 = await submitTransactionAsync(baltatharAccount, tx1);531      const result1 = getGenericResult(events1);532      expect(result1.success).to.be.true;533      console.log('Note motion preimage.......DONE');534      // <<< Note motion preimage <<<535536      // >>> Propose external motion through council >>>537      console.log('Propose external motion through council.......');538      const externalMotion = api.tx.democracy.externalProposeMajority(proposalHash);539      const tx2 = api.tx.councilCollective.propose(540        councilVotingThreshold,541        externalMotion,542        externalMotion.encodedLength,543      );544      const events2 = await submitTransactionAsync(baltatharAccount, tx2);545      const result2 = getGenericResult(events2);546      expect(result2.success).to.be.true;547548      const encodedMotion = externalMotion?.method.toHex() || '';549      const motionHash = blake2AsHex(encodedMotion);550      console.log('Motion hash is %s', motionHash);551552      const tx3 = api.tx.councilCollective.vote(motionHash, 0, true);553      {554        const events3 = await submitTransactionAsync(dorothyAccount, tx3);555        const result3 = getGenericResult(events3);556        expect(result3.success).to.be.true;557      }558      {559        const events3 = await submitTransactionAsync(baltatharAccount, tx3);560        const result3 = getGenericResult(events3);561        expect(result3.success).to.be.true;562      }563564      const tx4 = api.tx.councilCollective.close(motionHash, 0, 1_000_000_000, externalMotion.encodedLength);565      const events4 = await submitTransactionAsync(dorothyAccount, tx4);566      const result4 = getGenericResult(events4);567      expect(result4.success).to.be.true;568      console.log('Propose external motion through council.......DONE');569      // <<< Propose external motion through council <<<570571      // >>> Fast track proposal through technical committee >>>572      console.log('Fast track proposal through technical committee.......');573      const fastTrack = api.tx.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);574      const tx5 = api.tx.techCommitteeCollective.propose(575        technicalCommitteeThreshold,576        fastTrack,577        fastTrack.encodedLength,578      );579      const events5 = await submitTransactionAsync(alithAccount, tx5);580      const result5 = getGenericResult(events5);581      expect(result5.success).to.be.true;582583      const encodedFastTrack = fastTrack?.method.toHex() || '';584      const fastTrackHash = blake2AsHex(encodedFastTrack);585      console.log('FastTrack hash is %s', fastTrackHash);586587      const proposalIdx = Number(await api.query.techCommitteeCollective.proposalCount()) - 1;588      const tx6 = api.tx.techCommitteeCollective.vote(fastTrackHash, proposalIdx, true);589      {590        const events6 = await submitTransactionAsync(baltatharAccount, tx6);591        const result6 = getGenericResult(events6);592        expect(result6.success).to.be.true;593      }594      {595        const events6 = await submitTransactionAsync(alithAccount, tx6);596        const result6 = getGenericResult(events6);597        expect(result6.success).to.be.true;598      }599600      const tx7 = api.tx.techCommitteeCollective601        .close(fastTrackHash, proposalIdx, 1_000_000_000, fastTrack.encodedLength);602      const events7 = await submitTransactionAsync(baltatharAccount, tx7);603      const result7 = getGenericResult(events7);604      expect(result7.success).to.be.true;605      console.log('Fast track proposal through technical committee.......DONE');606      // <<< Fast track proposal through technical committee <<<607608      // >>> Referendum voting >>>609      console.log('Referendum voting.......');610      const tx8 = api.tx.democracy.vote(611        0,612        {Standard: {balance: 10_000_000_000_000_000_000n, vote: {aye: true, conviction: 1}}},613      );614      const events8 = await submitTransactionAsync(dorothyAccount, tx8);615      const result8 = getGenericResult(events8);616      expect(result8.success).to.be.true;617      console.log('Referendum voting.......DONE');618      // <<< Referendum voting <<<619620      // >>> Acquire Quartz AssetId Info on Moonriver >>>621      console.log('Acquire Quartz AssetId Info on Moonriver.......');622623      // Wait for the democracy execute624      // await waitNewBlocks(api, 5);625      await helper.wait.newBlocks(5);626627      assetId = (await api.query.assetManager.assetTypeId({628        XCM: sourceLocation,629      })).toString();630631      console.log('QTZ asset ID is %s', assetId);632      console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');633      // >>> Acquire Quartz AssetId Info on Moonriver >>>634635      // >>> Sponsoring random Account >>>636      console.log('Sponsoring random Account.......');637      // const tx10 = api.tx.balances.transfer(randomAccountMoonriver.address, 11_000_000_000_000_000_000n);638      // const events10 = await submitTransactionAsync(baltatharAccount, tx10);639      // const result10 = getGenericResult(events10);640      // expect(result10.success).to.be.true;641      await helper.balance.transferToSubstrate(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);642      console.log('Sponsoring random Account.......DONE');643      // <<< Sponsoring random Account <<<644645      // [balanceMovrTokenInit] = await getBalance(api, [randomAccountMoonriver.address]);646      balanceMovrTokenInit = await helper.balance.getSubstrate(randomAccountMoonriver.address);647    });648649    await usingPlaygrounds(async (helper) => {650      // const tx0 = api.tx.balances.transfer(randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);651      // const events0 = await submitTransactionAsync(quartzAlice, tx0);652      // const result0 = getGenericResult(events0);653      // expect(result0.success).to.be.true;654      await helper.balance.transferToSubstrate(quartzAlice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);655656      // [balanceQuartzTokenInit] = await getBalance(api, [randomAccountQuartz.address]);657      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);658    });659  });660661  itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {662    const currencyId = {663      NativeAssetId: 'Here',664    };665    const dest = {666      V1: {667        parents: 1,668        interior: {669          X2: [670            {Parachain: MOONRIVER_CHAIN},671            {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},672          ],673        },674      },675    };676    const amount = TRANSFER_AMOUNT;677    const destWeight = 850000000;678679    // TODO680    const tx = helper.getApi().tx.xTokens.transfer(currencyId, amount, dest, destWeight);681    const events = await submitTransactionAsync(randomAccountQuartz, tx);682    const result = getGenericResult(events);683    expect(result.success).to.be.true;684685    // [balanceQuartzTokenMiddle] = await getBalance(api, [randomAccountQuartz.address]);686    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);687    expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;688689    const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;690    console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', bigIntToDecimals(transactionFees));691    expect(transactionFees > 0).to.be.true;692693    await usingPlaygrounds.atUrl(moonriverUrl, async (helper) => {694      // await waitNewBlocks(api, 3);695      await helper.wait.newBlocks(3);696697      // [balanceMovrTokenMiddle] = await getBalance(api, [randomAccountMoonriver.address]);698      balanceMovrTokenMiddle = await helper.balance.getSubstrate(randomAccountMoonriver.address);699700      const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;701      console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',bigIntToDecimals(movrFees));702      expect(movrFees == 0n).to.be.true;703704      // TODO705      const qtzRandomAccountAsset = (706        await helper.getApi().query.assets.account(assetId, randomAccountMoonriver.address)707      ).toJSON()! as any;708709      balanceForeignQtzTokenMiddle = BigInt(qtzRandomAccountAsset['balance']);710      const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;711      console.log('[Quartz -> Moonriver] income %s QTZ', bigIntToDecimals(qtzIncomeTransfer));712      expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;713    });714  });715716  itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {717    await usingPlaygrounds.atUrl(moonriverUrl, async (helper) => {718      const asset = {719        V1: {720          id: {721            Concrete: {722              parents: 1,723              interior: {724                X1: {Parachain: QUARTZ_CHAIN},725              },726            },727          },728          fun: {729            Fungible: TRANSFER_AMOUNT,730          },731        },732      };733      const destination = {734        V1: {735          parents: 1,736          interior: {737            X2: [738              {Parachain: QUARTZ_CHAIN},739              {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},740            ],741          },742        },743      };744      const destWeight = 50000000;745746      // TODO747      const tx = helper.getApi().tx.xTokens.transferMultiasset(asset, destination, destWeight);748      const events = await submitTransactionAsync(randomAccountMoonriver, tx);749      const result = getGenericResult(events);750      expect(result.success).to.be.true;751752      // [balanceMovrTokenFinal] = await getBalance(api, [randomAccountMoonriver.address]);753      balanceMovrTokenFinal = await helper.balance.getSubstrate(randomAccountMoonriver.address);754755      const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;756      console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', bigIntToDecimals(movrFees));757      expect(movrFees > 0).to.be.true;758759      const qtzRandomAccountAsset = (760        await helper.getApi().query.assets.account(assetId, randomAccountMoonriver.address)761      ).toJSON()! as any;762763      expect(qtzRandomAccountAsset).to.be.null;764765      balanceForeignQtzTokenFinal = 0n;766767      const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;768      console.log('[Quartz -> Moonriver] outcome %s QTZ', bigIntToDecimals(qtzOutcomeTransfer));769      expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;770    });771772    // await waitNewBlocks(api, 3);773    await helper.wait.newBlocks(3);774775    // [balanceQuartzTokenFinal] = await getBalance(api, [randomAccountQuartz.address]);776    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);777    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;778    expect(actuallyDelivered > 0).to.be.true;779780    console.log('[Moonriver -> Quartz] actually delivered %s QTZ', bigIntToDecimals(actuallyDelivered));781782    const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;783    console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', bigIntToDecimals(qtzFees));784    expect(qtzFees == 0n).to.be.true;785  });786});
after · tests/src/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 {Keyring} from '@polkadot/api';18import {IKeyringPair} from '@polkadot/types/types';19import {generateKeyringPair, bigIntToDecimals} from '../deprecated-helpers/helpers';20import {blake2AsHex} from '@polkadot/util-crypto';21import {XcmV2TraitsOutcome, XcmV2TraitsError} from '../interfaces';22import {itSub, expect, describeXcm, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds} from '../util/playgrounds';2324const QUARTZ_CHAIN = 2095;25const KARURA_CHAIN = 2000;26const MOONRIVER_CHAIN = 2023;2728const RELAY_PORT = 9844;29const KARURA_PORT = 9946;30const MOONRIVER_PORT = 9947;3132const relayUrl = 'ws://127.0.0.1:' + RELAY_PORT;33const karuraUrl = 'ws://127.0.0.1:' + KARURA_PORT;34const moonriverUrl = 'ws://127.0.0.1:' + MOONRIVER_PORT;3536const KARURA_DECIMALS = 12;3738const TRANSFER_AMOUNT = 2000000000000000000000000n;3940describeXcm('[XCM] Integration test: Exchanging tokens with Karura', () => {41  let alice: IKeyringPair;42  let randomAccount: IKeyringPair;4344  let balanceQuartzTokenInit: bigint;45  let balanceQuartzTokenMiddle: bigint;46  let balanceQuartzTokenFinal: bigint;47  let balanceKaruraTokenInit: bigint;48  let balanceKaruraTokenMiddle: bigint;49  let balanceKaruraTokenFinal: bigint;50  let balanceQuartzForeignTokenInit: bigint;51  let balanceQuartzForeignTokenMiddle: bigint;52  let balanceQuartzForeignTokenFinal: bigint;5354  before(async () => {55    await usingPlaygrounds(async (helper, privateKey) => {56      const keyringSr25519 = new Keyring({type: 'sr25519'});5758      alice = privateKey('//Alice');59      randomAccount = generateKeyringPair(keyringSr25519);60    });6162    // Karura side63    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {64      const destination = {65        V0: {66          X2: [67            'Parent',68            {69              Parachain: QUARTZ_CHAIN,70            },71          ],72        },73      };7475      const metadata = {76        name: 'QTZ',77        symbol: 'QTZ',78        decimals: 18,79        minimalBalance: 1n,80      };8182      await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);83      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);84      balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);85      balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});86    });8788    await usingPlaygrounds(async (helper) => {89      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);90      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);91    });92  });9394  itSub('Should connect and send QTZ to Karura', async ({helper}) => {95    const destination = {96      V0: {97        X2: [98          'Parent',99          {100            Parachain: KARURA_CHAIN,101          },102        ],103      },104    };105106    const beneficiary = {107      V0: {108        X1: {109          AccountId32: {110            network: 'Any',111            id: randomAccount.addressRaw,112          },113        },114      },115    };116117    const assets = {118      V1: [119        {120          id: {121            Concrete: {122              parents: 0,123              interior: 'Here',124            },125          },126          fun: {127            Fungible: TRANSFER_AMOUNT,128          },129        },130      ],131    };132133    const feeAssetItem = 0;134    const weightLimit = 5000000000;135136    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, weightLimit);137    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);138139    const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;140    console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', bigIntToDecimals(qtzFees));141    expect(qtzFees > 0n).to.be.true;142143    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {144      await helper.wait.newBlocks(3);145      balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});146      balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);147148      const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;149      const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;150151      console.log(152        '[Quartz -> Karura] transaction fees on Karura: %s KAR',153        bigIntToDecimals(karFees, KARURA_DECIMALS),154      );155      console.log('[Quartz -> Karura] income %s QTZ', bigIntToDecimals(qtzIncomeTransfer));156      expect(karFees == 0n).to.be.true;157      expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;158    });159  });160161  itSub('Should connect to Karura and send QTZ back', async ({helper}) => {162    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {163      const destination = {164        V1: {165          parents: 1,166          interior: {167            X2: [168              {Parachain: QUARTZ_CHAIN},169              {170                AccountId32: {171                  network: 'Any',172                  id: randomAccount.addressRaw,173                },174              },175            ],176          },177        },178      };179180      const id = {181        ForeignAsset: 0,182      };183184      const destWeight = 50000000;185186      await helper.xTokens.transfer(randomAccount, id, TRANSFER_AMOUNT, destination, destWeight);187      balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);188      balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);189190      const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;191      const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;192193      console.log(194        '[Karura -> Quartz] transaction fees on Karura: %s KAR',195        bigIntToDecimals(karFees, KARURA_DECIMALS),196      );197      console.log('[Karura -> Quartz] outcome %s QTZ', bigIntToDecimals(qtzOutcomeTransfer));198199      expect(karFees > 0).to.be.true;200      expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;201    });202203    await helper.wait.newBlocks(3);204205    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);206    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;207    expect(actuallyDelivered > 0).to.be.true;208209    console.log('[Karura -> Quartz] actually delivered %s QTZ', bigIntToDecimals(actuallyDelivered));210211    const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;212    console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', bigIntToDecimals(qtzFees));213    expect(qtzFees == 0n).to.be.true;214  });215});216217// These tests are relevant only when the foreign asset pallet is disabled218describeXcm('[XCM] Integration test: Quartz rejects non-native tokens', () => {219  let alice: IKeyringPair;220221  before(async () => {222    await usingPlaygrounds(async (_helper, privateKey) => {223      alice = privateKey('//Alice');224    });225  });226227  itSub('Quartz rejects tokens from the Relay', async ({helper}) => {228    await usingRelayPlaygrounds(relayUrl, async (helper) => {229      const destination = {230        V1: {231          parents: 0,232          interior: {X1: {233            Parachain: QUARTZ_CHAIN,234          },235          },236        }};237238      const beneficiary = {239        V1: {240          parents: 0,241          interior: {X1: {242            AccountId32: {243              network: 'Any',244              id: alice.addressRaw,245            },246          }},247        },248      };249250      const assets = {251        V1: [252          {253            id: {254              Concrete: {255                parents: 0,256                interior: 'Here',257              },258            },259            fun: {260              Fungible: 50_000_000_000_000_000n,261            },262          },263        ],264      };265266      const feeAssetItem = 0;267      const weightLimit = 5_000_000_000;268269      await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, weightLimit);270    });271272    const maxWaitBlocks = 3;273274    const dmpQueueExecutedDownward = await helper.wait.event(maxWaitBlocks, 'dmpQueue', 'ExecutedDownward');275276    expect(277      dmpQueueExecutedDownward != null,278      '[Relay] dmpQueue.ExecutedDownward event is expected',279    ).to.be.true;280281    const event = dmpQueueExecutedDownward!.event;282    const outcome = event.data[1] as XcmV2TraitsOutcome;283284    expect(285      outcome.isIncomplete,286      '[Relay] The outcome of the XCM should be `Incomplete`',287    ).to.be.true;288289    const incomplete = outcome.asIncomplete;290    expect(291      incomplete[1].toString() == 'AssetNotFound',292      '[Relay] The XCM error should be `AssetNotFound`',293    ).to.be.true;294  });295296  itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {297    await usingKaruraPlaygrounds(karuraUrl, async (helper) => {298      const destination = {299        V1: {300          parents: 1,301          interior: {302            X2: [303              {Parachain: QUARTZ_CHAIN},304              {305                AccountId32: {306                  network: 'Any',307                  id: alice.addressRaw,308                },309              },310            ],311          },312        },313      };314315      const id = {316        Token: 'KAR',317      };318319      const destWeight = 50000000;320321      await helper.xTokens.transfer(alice, id, 100_000_000_000n, destination, destWeight);322    });323324    const maxWaitBlocks = 3;325326    const xcmpQueueFailEvent = await helper.wait.event(maxWaitBlocks, 'xcmpQueue', 'Fail');327328    expect(329      xcmpQueueFailEvent != null,330      '[Karura] xcmpQueue.FailEvent event is expected',331    ).to.be.true;332333    const event = xcmpQueueFailEvent!.event;334    const outcome = event.data[1] as XcmV2TraitsError;335336    expect(337      outcome.isUntrustedReserveLocation,338      '[Karura] The XCM error should be `UntrustedReserveLocation`',339    ).to.be.true;340  });341});342343describeXcm('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {344345  // Quartz constants346  let quartzAlice: IKeyringPair;347  let quartzAssetLocation;348349  let randomAccountQuartz: IKeyringPair;350  let randomAccountMoonriver: IKeyringPair;351352  // Moonriver constants353  let assetId: string;354355  const councilVotingThreshold = 2;356  const technicalCommitteeThreshold = 2;357  const votingPeriod = 3;358  const delayPeriod = 0;359360  const quartzAssetMetadata = {361    name: 'xcQuartz',362    symbol: 'xcQTZ',363    decimals: 18,364    isFrozen: false,365    minimalBalance: 1n,366  };367368  let balanceQuartzTokenInit: bigint;369  let balanceQuartzTokenMiddle: bigint;370  let balanceQuartzTokenFinal: bigint;371  let balanceForeignQtzTokenInit: bigint;372  let balanceForeignQtzTokenMiddle: bigint;373  let balanceForeignQtzTokenFinal: bigint;374  let balanceMovrTokenInit: bigint;375  let balanceMovrTokenMiddle: bigint;376  let balanceMovrTokenFinal: bigint;377378  before(async () => {379    await usingPlaygrounds(async (_helper, privateKey) => {380      const keyringEth = new Keyring({type: 'ethereum'});381      const keyringSr25519 = new Keyring({type: 'sr25519'});382383      quartzAlice = privateKey('//Alice');384      randomAccountQuartz = generateKeyringPair(keyringSr25519);385      randomAccountMoonriver = generateKeyringPair(keyringEth);386387      balanceForeignQtzTokenInit = 0n;388    });389390    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {391      const alithAccount = helper.account.alithAccount();392      const baltatharAccount = helper.account.baltatharAccount();393      const dorothyAccount = helper.account.dorothyAccount();394395      // >>> Sponsoring Dorothy >>>396      console.log('Sponsoring Dorothy.......');397      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);398      console.log('Sponsoring Dorothy.......DONE');399      // <<< Sponsoring Dorothy <<<400401      quartzAssetLocation = {402        XCM: {403          parents: 1,404          interior: {X1: {Parachain: QUARTZ_CHAIN}},405        },406      };407      const existentialDeposit = 1n;408      const isSufficient = true;409      const unitsPerSecond = 1n;410      const numAssetsWeightHint = 0;411412      const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({413        location: quartzAssetLocation,414        metadata: quartzAssetMetadata,415        existentialDeposit,416        isSufficient,417        unitsPerSecond,418        numAssetsWeightHint,419      });420      const proposalHash = blake2AsHex(encodedProposal);421422      console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);423      console.log('Encoded length %d', encodedProposal.length);424      console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);425426      // >>> Note motion preimage >>>427      console.log('Note motion preimage.......');428      await helper.democracy.notePreimage(baltatharAccount, encodedProposal);429      console.log('Note motion preimage.......DONE');430      // <<< Note motion preimage <<<431432      // >>> Propose external motion through council >>>433      console.log('Propose external motion through council.......');434      const externalMotion = helper.democracy.externalProposeMajority(proposalHash);435      const encodedMotion = externalMotion?.method.toHex() || '';436      const motionHash = blake2AsHex(encodedMotion);437      console.log('Motion hash is %s', motionHash);438439      await helper.collective.council.propose(baltatharAccount, councilVotingThreshold, externalMotion, externalMotion.encodedLength);440441      const councilProposalIdx = await helper.collective.council.proposalCount() - 1;442      await helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);443      await helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);444445      await helper.collective.council.close(dorothyAccount, motionHash, councilProposalIdx, 1_000_000_000, externalMotion.encodedLength);446      console.log('Propose external motion through council.......DONE');447      // <<< Propose external motion through council <<<448449      // >>> Fast track proposal through technical committee >>>450      console.log('Fast track proposal through technical committee.......');451      const fastTrack = helper.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);452      const encodedFastTrack = fastTrack?.method.toHex() || '';453      const fastTrackHash = blake2AsHex(encodedFastTrack);454      console.log('FastTrack hash is %s', fastTrackHash);455456      await helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);457458      const techProposalIdx = await helper.collective.techCommittee.proposalCount() - 1;459      await helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);460      await helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);461462      await helper.collective.techCommittee.close(baltatharAccount, fastTrackHash, techProposalIdx, 1_000_000_000, fastTrack.encodedLength);463      console.log('Fast track proposal through technical committee.......DONE');464      // <<< Fast track proposal through technical committee <<<465466      // >>> Referendum voting >>>467      console.log('Referendum voting.......');468      await helper.democracy.referendumVote(dorothyAccount, 0, {469        balance: 10_000_000_000_000_000_000n,470        vote: {aye: true, conviction: 1},471      });472      console.log('Referendum voting.......DONE');473      // <<< Referendum voting <<<474475      // >>> Acquire Quartz AssetId Info on Moonriver >>>476      console.log('Acquire Quartz AssetId Info on Moonriver.......');477478      // Wait for the democracy execute479      await helper.wait.newBlocks(5);480481      assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();482483      console.log('QTZ asset ID is %s', assetId);484      console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');485      // >>> Acquire Quartz AssetId Info on Moonriver >>>486487      // >>> Sponsoring random Account >>>488      console.log('Sponsoring random Account.......');489      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);490      console.log('Sponsoring random Account.......DONE');491      // <<< Sponsoring random Account <<<492493      balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);494    });495496    await usingPlaygrounds(async (helper) => {497      await helper.balance.transferToSubstrate(quartzAlice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);498      balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);499    });500  });501502  itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {503    const currencyId = {504      NativeAssetId: 'Here',505    };506    const dest = {507      V1: {508        parents: 1,509        interior: {510          X2: [511            {Parachain: MOONRIVER_CHAIN},512            {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},513          ],514        },515      },516    };517    const amount = TRANSFER_AMOUNT;518    const destWeight = 850000000;519520    await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, destWeight);521522    balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);523    expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;524525    const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;526    console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', bigIntToDecimals(transactionFees));527    expect(transactionFees > 0).to.be.true;528529    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {530      await helper.wait.newBlocks(3);531532      balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);533534      const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;535      console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',bigIntToDecimals(movrFees));536      expect(movrFees == 0n).to.be.true;537538      balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; // BigInt(qtzRandomAccountAsset['balance']);539      const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;540      console.log('[Quartz -> Moonriver] income %s QTZ', bigIntToDecimals(qtzIncomeTransfer));541      expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;542    });543  });544545  itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {546    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {547      const asset = {548        V1: {549          id: {550            Concrete: {551              parents: 1,552              interior: {553                X1: {Parachain: QUARTZ_CHAIN},554              },555            },556          },557          fun: {558            Fungible: TRANSFER_AMOUNT,559          },560        },561      };562      const destination = {563        V1: {564          parents: 1,565          interior: {566            X2: [567              {Parachain: QUARTZ_CHAIN},568              {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},569            ],570          },571        },572      };573      const destWeight = 50000000;574575      await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, destWeight);576577      balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);578579      const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;580      console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', bigIntToDecimals(movrFees));581      expect(movrFees > 0).to.be.true;582583      const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);584585      expect(qtzRandomAccountAsset).to.be.null;586587      balanceForeignQtzTokenFinal = 0n;588589      const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;590      console.log('[Quartz -> Moonriver] outcome %s QTZ', bigIntToDecimals(qtzOutcomeTransfer));591      expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;592    });593594    await helper.wait.newBlocks(3);595596    balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);597    const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;598    expect(actuallyDelivered > 0).to.be.true;599600    console.log('[Moonriver -> Quartz] actually delivered %s QTZ', bigIntToDecimals(actuallyDelivered));601602    const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;603    console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', bigIntToDecimals(qtzFees));604    expect(qtzFees == 0n).to.be.true;605  });606});