difftreelog
refactor use playgrounds in quartz xcm tests
in: master
1 file changed
tests/src/xcm/xcmQuartz.test.tsdiffbeforeafterboth1// 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});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});