1234567891011121314151617import chai from 'chai';18import chaiAsPromised from 'chai-as-promised';1920import {WsProvider, Keyring} from '@polkadot/api';21import {ApiOptions} from '@polkadot/api/types';22import {IKeyringPair} from '@polkadot/types/types';23import usingApi, {submitTransactionAsync} from '../substrate/substrate-api';24import {getGenericResult, generateKeyringPair, waitEvent, describeXCM, bigIntToDecimals} from '../deprecated-helpers/helpers';25import {MultiLocation} from '@polkadot/types/interfaces';26import {blake2AsHex} from '@polkadot/util-crypto';27import waitNewBlocks from '../substrate/wait-new-blocks';28import getBalance from '../substrate/get-balance';29import {XcmV2TraitsOutcome, XcmV2TraitsError} from '../interfaces';3031chai.use(chaiAsPromised);32const expect = chai.expect;3334const QUARTZ_CHAIN = 2095;35const KARURA_CHAIN = 2000;36const MOONRIVER_CHAIN = 2023;3738const RELAY_PORT = 9844;39const KARURA_PORT = 9946;40const MOONRIVER_PORT = 9947;4142const KARURA_DECIMALS = 12;4344const TRANSFER_AMOUNT = 2000000000000000000000000n;4546function parachainApiOptions(port: number): ApiOptions {47 return {48 provider: new WsProvider('ws://127.0.0.1:' + port.toString()),49 };50}5152function karuraOptions(): ApiOptions {53 return parachainApiOptions(KARURA_PORT);54}5556function moonriverOptions(): ApiOptions {57 return parachainApiOptions(MOONRIVER_PORT);58}5960function relayOptions(): ApiOptions {61 return parachainApiOptions(RELAY_PORT);62}6364describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => {65 let alice: IKeyringPair;66 let randomAccount: IKeyringPair;6768 let balanceQuartzTokenInit: bigint;69 let balanceQuartzTokenMiddle: bigint;70 let balanceQuartzTokenFinal: bigint;71 let balanceKaruraTokenInit: bigint;72 let balanceKaruraTokenMiddle: bigint;73 let balanceKaruraTokenFinal: bigint;74 let balanceQuartzForeignTokenInit: bigint;75 let balanceQuartzForeignTokenMiddle: bigint;76 let balanceQuartzForeignTokenFinal: bigint;7778 before(async () => {79 console.log('hey babe');80 await usingApi(async (api, privateKeyWrapper) => {81 const keyringSr25519 = new Keyring({type: 'sr25519'});8283 alice = privateKeyWrapper('//Alice');84 randomAccount = generateKeyringPair(keyringSr25519);85 });8687 88 await usingApi(89 async (api) => {90 const destination = {91 V0: {92 X2: [93 'Parent',94 {95 Parachain: QUARTZ_CHAIN,96 },97 ],98 },99 };100101 const metadata = {102 name: 'QTZ',103 symbol: 'QTZ',104 decimals: 18,105 minimalBalance: 1,106 };107108 const tx = api.tx.assetRegistry.registerForeignAsset(destination, metadata);109 const sudoTx = api.tx.sudo.sudo(tx as any);110 const events = await submitTransactionAsync(alice, sudoTx);111 const result = getGenericResult(events);112 expect(result.success).to.be.true;113114 const tx1 = api.tx.balances.transfer(randomAccount.address, 10000000000000n);115 const events1 = await submitTransactionAsync(alice, tx1);116 const result1 = getGenericResult(events1);117 expect(result1.success).to.be.true;118119 [balanceKaruraTokenInit] = await getBalance(api, [randomAccount.address]);120 {121 const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, {ForeignAsset: 0})).toJSON() as any;122 balanceQuartzForeignTokenInit = BigInt(free);123 }124 },125 karuraOptions(),126 );127128 129 await usingApi(async (api) => {130 const tx0 = api.tx.balances.transfer(randomAccount.address, 10n * TRANSFER_AMOUNT);131 const events0 = await submitTransactionAsync(alice, tx0);132 const result0 = getGenericResult(events0);133 expect(result0.success).to.be.true;134135 [balanceQuartzTokenInit] = await getBalance(api, [randomAccount.address]);136 });137 });138139 it('Should connect and send QTZ to Karura', async () => {140141 142 await usingApi(async (api) => {143144 const destination = {145 V0: {146 X2: [147 'Parent',148 {149 Parachain: KARURA_CHAIN,150 },151 ],152 },153 };154155 const beneficiary = {156 V0: {157 X1: {158 AccountId32: {159 network: 'Any',160 id: randomAccount.addressRaw,161 },162 },163 },164 };165166 const assets = {167 V1: [168 {169 id: {170 Concrete: {171 parents: 0,172 interior: 'Here',173 },174 },175 fun: {176 Fungible: TRANSFER_AMOUNT,177 },178 },179 ],180 };181182 const feeAssetItem = 0;183184 const weightLimit = {185 Limited: 5000000000,186 };187188 const tx = api.tx.polkadotXcm.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit);189 const events = await submitTransactionAsync(randomAccount, tx);190 const result = getGenericResult(events);191 expect(result.success).to.be.true;192193 [balanceQuartzTokenMiddle] = await getBalance(api, [randomAccount.address]);194195 const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;196 console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', bigIntToDecimals(qtzFees));197 expect(qtzFees > 0n).to.be.true;198 });199200 201 await usingApi(202 async (api) => {203 await waitNewBlocks(api, 3);204 const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, {ForeignAsset: 0})).toJSON() as any;205 balanceQuartzForeignTokenMiddle = BigInt(free);206207 [balanceKaruraTokenMiddle] = await getBalance(api, [randomAccount.address]);208209 const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;210 const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;211212 console.log(213 '[Quartz -> Karura] transaction fees on Karura: %s KAR',214 bigIntToDecimals(karFees, KARURA_DECIMALS),215 );216 console.log('[Quartz -> Karura] income %s QTZ', bigIntToDecimals(qtzIncomeTransfer));217 expect(karFees == 0n).to.be.true;218 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;219 },220 karuraOptions(),221 );222 });223224 it('Should connect to Karura and send QTZ back', async () => {225226 227 await usingApi(228 async (api) => {229 const destination = {230 V1: {231 parents: 1,232 interior: {233 X2: [234 {Parachain: QUARTZ_CHAIN},235 {236 AccountId32: {237 network: 'Any',238 id: randomAccount.addressRaw,239 },240 },241 ],242 },243 },244 };245246 const id = {247 ForeignAsset: 0,248 };249250 const destWeight = 50000000;251252 const tx = api.tx.xTokens.transfer(id as any, TRANSFER_AMOUNT, destination, destWeight);253 const events = await submitTransactionAsync(randomAccount, tx);254 const result = getGenericResult(events);255 expect(result.success).to.be.true;256257 [balanceKaruraTokenFinal] = await getBalance(api, [randomAccount.address]);258 {259 const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, id)).toJSON() as any;260 balanceQuartzForeignTokenFinal = BigInt(free);261 }262263 const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;264 const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;265266 console.log(267 '[Karura -> Quartz] transaction fees on Karura: %s KAR',268 bigIntToDecimals(karFees, KARURA_DECIMALS),269 );270 console.log('[Karura -> Quartz] outcome %s QTZ', bigIntToDecimals(qtzOutcomeTransfer));271272 expect(karFees > 0).to.be.true;273 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;274 },275 karuraOptions(),276 );277278 279 await usingApi(async (api) => {280 await waitNewBlocks(api, 3);281282 [balanceQuartzTokenFinal] = await getBalance(api, [randomAccount.address]);283 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;284 expect(actuallyDelivered > 0).to.be.true;285286 console.log('[Karura -> Quartz] actually delivered %s QTZ', bigIntToDecimals(actuallyDelivered));287288 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;289 console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', bigIntToDecimals(qtzFees));290 expect(qtzFees == 0n).to.be.true;291 });292 });293});294295296describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => {297 let alice: IKeyringPair;298299 before(async () => {300 await usingApi(async (api, privateKeyWrapper) => {301 alice = privateKeyWrapper('//Alice');302 });303 });304305 it('Quartz rejects tokens from the Relay', async () => {306 await usingApi(async (api) => {307 const destination = {308 V1: {309 parents: 0,310 interior: {X1: {311 Parachain: QUARTZ_CHAIN,312 },313 },314 }};315316 const beneficiary = {317 V1: {318 parents: 0,319 interior: {X1: {320 AccountId32: {321 network: 'Any',322 id: alice.addressRaw,323 },324 }},325 },326 };327328 const assets = {329 V1: [330 {331 id: {332 Concrete: {333 parents: 0,334 interior: 'Here',335 },336 },337 fun: {338 Fungible: 50_000_000_000_000_000n,339 },340 },341 ],342 };343344 const feeAssetItem = 0;345346 const weightLimit = {347 Limited: 5_000_000_000,348 };349350 const tx = api.tx.xcmPallet.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit);351 const events = await submitTransactionAsync(alice, tx);352 const result = getGenericResult(events);353 expect(result.success).to.be.true;354 }, relayOptions());355356 await usingApi(async api => {357 const maxWaitBlocks = 3;358 const dmpQueueExecutedDownward = await waitEvent(359 api,360 maxWaitBlocks,361 'dmpQueue',362 'ExecutedDownward',363 );364365 expect(366 dmpQueueExecutedDownward != null,367 '[Relay] dmpQueue.ExecutedDownward event is expected',368 ).to.be.true;369370 const event = dmpQueueExecutedDownward!.event;371 const outcome = event.data[1] as XcmV2TraitsOutcome;372373 expect(374 outcome.isIncomplete,375 '[Relay] The outcome of the XCM should be `Incomplete`',376 ).to.be.true;377378 const incomplete = outcome.asIncomplete;379 expect(380 incomplete[1].toString() == 'AssetNotFound',381 '[Relay] The XCM error should be `AssetNotFound`',382 ).to.be.true;383 });384 });385386 it('Quartz rejects KAR tokens from Karura', async () => {387 await usingApi(async (api) => {388 const destination = {389 V1: {390 parents: 1,391 interior: {392 X2: [393 {Parachain: QUARTZ_CHAIN},394 {395 AccountId32: {396 network: 'Any',397 id: alice.addressRaw,398 },399 },400 ],401 },402 },403 };404405 const id = {406 Token: 'KAR',407 };408409 const destWeight = 50000000;410411 const tx = api.tx.xTokens.transfer(id as any, 100_000_000_000, destination, destWeight);412 const events = await submitTransactionAsync(alice, tx);413 const result = getGenericResult(events);414 expect(result.success).to.be.true;415 }, karuraOptions());416417 await usingApi(async api => {418 const maxWaitBlocks = 3;419 const xcmpQueueFailEvent = await waitEvent(api, maxWaitBlocks, 'xcmpQueue', 'Fail');420421 expect(422 xcmpQueueFailEvent != null,423 '[Karura] xcmpQueue.FailEvent event is expected',424 ).to.be.true;425426 const event = xcmpQueueFailEvent!.event;427 const outcome = event.data[1] as XcmV2TraitsError;428429 expect(430 outcome.isUntrustedReserveLocation,431 '[Karura] The XCM error should be `UntrustedReserveLocation`',432 ).to.be.true;433 });434 });435});436437describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {438 439 let quartzAlice: IKeyringPair;440 let quartzAssetLocation;441442 let randomAccountQuartz: IKeyringPair;443 let randomAccountMoonriver: IKeyringPair;444445 446 let assetId: string;447448 const moonriverKeyring = new Keyring({type: 'ethereum'});449 const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133';450 const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b';451 const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68';452453 const alithAccount = moonriverKeyring.addFromUri(alithPrivateKey, undefined, 'ethereum');454 const baltatharAccount = moonriverKeyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum');455 const dorothyAccount = moonriverKeyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum');456457 const councilVotingThreshold = 2;458 const technicalCommitteeThreshold = 2;459 const votingPeriod = 3;460 const delayPeriod = 0;461462 const quartzAssetMetadata = {463 name: 'xcQuartz',464 symbol: 'xcQTZ',465 decimals: 18,466 isFrozen: false,467 minimalBalance: 1,468 };469470 let balanceQuartzTokenInit: bigint;471 let balanceQuartzTokenMiddle: bigint;472 let balanceQuartzTokenFinal: bigint;473 let balanceForeignQtzTokenInit: bigint;474 let balanceForeignQtzTokenMiddle: bigint;475 let balanceForeignQtzTokenFinal: bigint;476 let balanceMovrTokenInit: bigint;477 let balanceMovrTokenMiddle: bigint;478 let balanceMovrTokenFinal: bigint;479480 before(async () => {481 await usingApi(async (api, privateKeyWrapper) => {482 const keyringEth = new Keyring({type: 'ethereum'});483 const keyringSr25519 = new Keyring({type: 'sr25519'});484485 quartzAlice = privateKeyWrapper('//Alice');486 randomAccountQuartz = generateKeyringPair(keyringSr25519);487 randomAccountMoonriver = generateKeyringPair(keyringEth);488489 balanceForeignQtzTokenInit = 0n;490 });491492 await usingApi(493 async (api) => {494495 496 console.log('Sponsoring Dorothy.......');497 const tx0 = api.tx.balances.transfer(dorothyAccount.address, 11_000_000_000_000_000_000n);498 const events0 = await submitTransactionAsync(alithAccount, tx0);499 const result0 = getGenericResult(events0);500 expect(result0.success).to.be.true;501 console.log('Sponsoring Dorothy.......DONE');502 503504 const sourceLocation: MultiLocation = api.createType(505 'MultiLocation',506 {507 parents: 1,508 interior: {X1: {Parachain: QUARTZ_CHAIN}},509 },510 );511512 quartzAssetLocation = {XCM: sourceLocation};513 const existentialDeposit = 1;514 const isSufficient = true;515 const unitsPerSecond = '1';516 const numAssetsWeightHint = 0;517518 const registerTx = api.tx.assetManager.registerForeignAsset(519 quartzAssetLocation,520 quartzAssetMetadata,521 existentialDeposit,522 isSufficient,523 );524 console.log('Encoded proposal for registerAsset is %s', registerTx.method.toHex() || '');525526 const setUnitsTx = api.tx.assetManager.setAssetUnitsPerSecond(527 quartzAssetLocation,528 unitsPerSecond,529 numAssetsWeightHint,530 );531 console.log('Encoded proposal for setAssetUnitsPerSecond is %s', setUnitsTx.method.toHex() || '');532533 const batchCall = api.tx.utility.batchAll([registerTx, setUnitsTx]);534 console.log('Encoded proposal for batchCall is %s', batchCall.method.toHex() || '');535536 537 console.log('Note motion preimage.......');538 const encodedProposal = batchCall?.method.toHex() || '';539 const proposalHash = blake2AsHex(encodedProposal);540 console.log('Encoded proposal for batch utility after schedule is %s', encodedProposal);541 console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);542 console.log('Encoded length %d', encodedProposal.length);543544 const tx1 = api.tx.democracy.notePreimage(encodedProposal);545 const events1 = await submitTransactionAsync(baltatharAccount, tx1);546 const result1 = getGenericResult(events1);547 expect(result1.success).to.be.true;548 console.log('Note motion preimage.......DONE');549 550551 552 console.log('Propose external motion through council.......');553 const externalMotion = api.tx.democracy.externalProposeMajority(proposalHash);554 const tx2 = api.tx.councilCollective.propose(555 councilVotingThreshold,556 externalMotion,557 externalMotion.encodedLength,558 );559 const events2 = await submitTransactionAsync(baltatharAccount, tx2);560 const result2 = getGenericResult(events2);561 expect(result2.success).to.be.true;562563 const encodedMotion = externalMotion?.method.toHex() || '';564 const motionHash = blake2AsHex(encodedMotion);565 console.log('Motion hash is %s', motionHash);566567 const tx3 = api.tx.councilCollective.vote(motionHash, 0, true);568 {569 const events3 = await submitTransactionAsync(dorothyAccount, tx3);570 const result3 = getGenericResult(events3);571 expect(result3.success).to.be.true;572 }573 {574 const events3 = await submitTransactionAsync(baltatharAccount, tx3);575 const result3 = getGenericResult(events3);576 expect(result3.success).to.be.true;577 }578579 const tx4 = api.tx.councilCollective.close(motionHash, 0, 1_000_000_000, externalMotion.encodedLength);580 const events4 = await submitTransactionAsync(dorothyAccount, tx4);581 const result4 = getGenericResult(events4);582 expect(result4.success).to.be.true;583 console.log('Propose external motion through council.......DONE');584 585586 587 console.log('Fast track proposal through technical committee.......');588 const fastTrack = api.tx.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);589 const tx5 = api.tx.techCommitteeCollective.propose(590 technicalCommitteeThreshold,591 fastTrack,592 fastTrack.encodedLength,593 );594 const events5 = await submitTransactionAsync(alithAccount, tx5);595 const result5 = getGenericResult(events5);596 expect(result5.success).to.be.true;597598 const encodedFastTrack = fastTrack?.method.toHex() || '';599 const fastTrackHash = blake2AsHex(encodedFastTrack);600 console.log('FastTrack hash is %s', fastTrackHash);601602 const proposalIdx = Number(await api.query.techCommitteeCollective.proposalCount()) - 1;603 const tx6 = api.tx.techCommitteeCollective.vote(fastTrackHash, proposalIdx, true);604 {605 const events6 = await submitTransactionAsync(baltatharAccount, tx6);606 const result6 = getGenericResult(events6);607 expect(result6.success).to.be.true;608 }609 {610 const events6 = await submitTransactionAsync(alithAccount, tx6);611 const result6 = getGenericResult(events6);612 expect(result6.success).to.be.true;613 }614615 const tx7 = api.tx.techCommitteeCollective616 .close(fastTrackHash, proposalIdx, 1_000_000_000, fastTrack.encodedLength);617 const events7 = await submitTransactionAsync(baltatharAccount, tx7);618 const result7 = getGenericResult(events7);619 expect(result7.success).to.be.true;620 console.log('Fast track proposal through technical committee.......DONE');621 622623 624 console.log('Referendum voting.......');625 const tx8 = api.tx.democracy.vote(626 0,627 {Standard: {balance: 10_000_000_000_000_000_000n, vote: {aye: true, conviction: 1}}},628 );629 const events8 = await submitTransactionAsync(dorothyAccount, tx8);630 const result8 = getGenericResult(events8);631 expect(result8.success).to.be.true;632 console.log('Referendum voting.......DONE');633 634635 636 console.log('Acquire Quartz AssetId Info on Moonriver.......');637638 639 await waitNewBlocks(api, 5);640641 assetId = (await api.query.assetManager.assetTypeId({642 XCM: sourceLocation,643 })).toString();644645 console.log('QTZ asset ID is %s', assetId);646 console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');647 648649 650 console.log('Sponsoring random Account.......');651 const tx10 = api.tx.balances.transfer(randomAccountMoonriver.address, 11_000_000_000_000_000_000n);652 const events10 = await submitTransactionAsync(baltatharAccount, tx10);653 const result10 = getGenericResult(events10);654 expect(result10.success).to.be.true;655 console.log('Sponsoring random Account.......DONE');656 657658 [balanceMovrTokenInit] = await getBalance(api, [randomAccountMoonriver.address]);659 },660 moonriverOptions(),661 );662663 await usingApi(async (api) => {664 const tx0 = api.tx.balances.transfer(randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);665 const events0 = await submitTransactionAsync(quartzAlice, tx0);666 const result0 = getGenericResult(events0);667 expect(result0.success).to.be.true;668669 [balanceQuartzTokenInit] = await getBalance(api, [randomAccountQuartz.address]);670 });671 });672673 it('Should connect and send QTZ to Moonriver', async () => {674 await usingApi(async (api) => {675 const currencyId = {676 NativeAssetId: 'Here',677 };678 const dest = {679 V1: {680 parents: 1,681 interior: {682 X2: [683 {Parachain: MOONRIVER_CHAIN},684 {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},685 ],686 },687 },688 };689 const amount = TRANSFER_AMOUNT;690 const destWeight = 850000000;691692 const tx = api.tx.xTokens.transfer(currencyId, amount, dest, destWeight);693 const events = await submitTransactionAsync(randomAccountQuartz, tx);694 const result = getGenericResult(events);695 expect(result.success).to.be.true;696697 [balanceQuartzTokenMiddle] = await getBalance(api, [randomAccountQuartz.address]);698 expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;699700 const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;701 console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', bigIntToDecimals(transactionFees));702 expect(transactionFees > 0).to.be.true;703 });704705 await usingApi(706 async (api) => {707 await waitNewBlocks(api, 3);708709 [balanceMovrTokenMiddle] = await getBalance(api, [randomAccountMoonriver.address]);710711 const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;712 console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',bigIntToDecimals(movrFees));713 expect(movrFees == 0n).to.be.true;714715 const qtzRandomAccountAsset = (716 await api.query.assets.account(assetId, randomAccountMoonriver.address)717 ).toJSON()! as any;718719 balanceForeignQtzTokenMiddle = BigInt(qtzRandomAccountAsset['balance']);720 const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;721 console.log('[Quartz -> Moonriver] income %s QTZ', bigIntToDecimals(qtzIncomeTransfer));722 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;723 },724 moonriverOptions(),725 );726 });727728 it('Should connect to Moonriver and send QTZ back', async () => {729 await usingApi(730 async (api) => {731 const asset = {732 V1: {733 id: {734 Concrete: {735 parents: 1,736 interior: {737 X1: {Parachain: QUARTZ_CHAIN},738 },739 },740 },741 fun: {742 Fungible: TRANSFER_AMOUNT,743 },744 },745 };746 const destination = {747 V1: {748 parents: 1,749 interior: {750 X2: [751 {Parachain: QUARTZ_CHAIN},752 {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},753 ],754 },755 },756 };757 const destWeight = 50000000;758759 const tx = api.tx.xTokens.transferMultiasset(asset, destination, destWeight);760 const events = await submitTransactionAsync(randomAccountMoonriver, tx);761 const result = getGenericResult(events);762 expect(result.success).to.be.true;763764 [balanceMovrTokenFinal] = await getBalance(api, [randomAccountMoonriver.address]);765766 const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;767 console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', bigIntToDecimals(movrFees));768 expect(movrFees > 0).to.be.true;769770 const qtzRandomAccountAsset = (771 await api.query.assets.account(assetId, randomAccountMoonriver.address)772 ).toJSON()! as any;773774 expect(qtzRandomAccountAsset).to.be.null;775776 balanceForeignQtzTokenFinal = 0n;777778 const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;779 console.log('[Quartz -> Moonriver] outcome %s QTZ', bigIntToDecimals(qtzOutcomeTransfer));780 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;781 },782 moonriverOptions(),783 );784785 await usingApi(async (api) => {786 await waitNewBlocks(api, 3);787788 [balanceQuartzTokenFinal] = await getBalance(api, [randomAccountQuartz.address]);789 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;790 expect(actuallyDelivered > 0).to.be.true;791792 console.log('[Moonriver -> Quartz] actually delivered %s QTZ', bigIntToDecimals(actuallyDelivered));793794 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;795 console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', bigIntToDecimals(qtzFees));796 expect(qtzFees == 0n).to.be.true;797 });798 });799});