difftreelog
fix moonriver xcm test
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 {IKeyringPair} from '@polkadot/types/types';18import {blake2AsHex} from '@polkadot/util-crypto';19import config from '../config';20import {XcmV2TraitsError} from '../interfaces';21import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds} from '../util';2223const QUARTZ_CHAIN = 2095;24const STATEMINE_CHAIN = 1000;25const KARURA_CHAIN = 2000;26const MOONRIVER_CHAIN = 2023;2728const STATEMINE_PALLET_INSTANCE = 50;2930const relayUrl = config.relayUrl;31const statemineUrl = config.statemineUrl;32const karuraUrl = config.karuraUrl;33const moonriverUrl = config.moonriverUrl;3435const STATEMINE_DECIMALS = 12;36const KARURA_DECIMALS = 12;3738const TRANSFER_AMOUNT = 2000000000000000000000000n;3940const FUNDING_AMOUNT = 3_500_000_0000_000_000n; 4142const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;4344const USDT_ASSET_ID = 100;45const USDT_ASSET_METADATA_DECIMALS = 18;46const USDT_ASSET_METADATA_NAME = 'USDT';47const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';48const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;49const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;5051describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => {52 let alice: IKeyringPair;53 let bob: IKeyringPair;54 55 let balanceStmnBefore: bigint;56 let balanceStmnAfter: bigint;5758 let balanceQuartzBefore: bigint;59 let balanceQuartzAfter: bigint;60 let balanceQuartzFinal: bigint;6162 let balanceBobBefore: bigint;63 let balanceBobAfter: bigint;64 let balanceBobFinal: bigint;6566 let balanceBobRelayTokenBefore: bigint;67 let balanceBobRelayTokenAfter: bigint;686970 before(async () => {71 await usingPlaygrounds(async (_helper, privateKey) => {72 alice = await privateKey('//Alice');73 bob = await privateKey('//Bob'); // sovereign account on Statemine(t) funds donor74 });7576 await usingRelayPlaygrounds(relayUrl, async (helper) => {77 // Fund accounts on Statemine(t)78 await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, alice.addressRaw, FUNDING_AMOUNT);79 await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, bob.addressRaw, FUNDING_AMOUNT);80 });8182 await usingStateminePlaygrounds(statemineUrl, async (helper) => {83 const sovereignFundingAmount = 3_500_000_000n; 8485 await helper.assets.create(86 alice,87 USDT_ASSET_ID,88 alice.address,89 USDT_ASSET_METADATA_MINIMAL_BALANCE,90 );91 await helper.assets.setMetadata(92 alice,93 USDT_ASSET_ID,94 USDT_ASSET_METADATA_NAME,95 USDT_ASSET_METADATA_DESCRIPTION,96 USDT_ASSET_METADATA_DECIMALS,97 );98 await helper.assets.mint(99 alice,100 USDT_ASSET_ID,101 alice.address,102 USDT_ASSET_AMOUNT,103 );104105 // funding parachain sovereing account on Statemine(t).106 // The sovereign account should be created before any action107 // (the assets pallet on Statemine(t) check if the sovereign account exists)108 const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(QUARTZ_CHAIN);109 await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);110 });111112113 await usingPlaygrounds(async (helper) => {114 const location = {115 V1: {116 parents: 1,117 interior: {X3: [118 {119 Parachain: STATEMINE_CHAIN,120 },121 {122 PalletInstance: STATEMINE_PALLET_INSTANCE,123 },124 {125 GeneralIndex: USDT_ASSET_ID,126 },127 ]},128 },129 };130131 const metadata =132 {133 name: USDT_ASSET_ID,134 symbol: USDT_ASSET_METADATA_NAME,135 decimals: USDT_ASSET_METADATA_DECIMALS,136 minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,137 };138 await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);139 balanceQuartzBefore = await helper.balance.getSubstrate(alice.address);140 });141142143 // Providing the relay currency to the quartz sender account144 // (fee for USDT XCM are paid in relay tokens)145 await usingRelayPlaygrounds(relayUrl, async (helper) => {146 const destination = {147 V1: {148 parents: 0,149 interior: {X1: {150 Parachain: QUARTZ_CHAIN,151 },152 },153 }};154155 const beneficiary = {156 V1: {157 parents: 0,158 interior: {X1: {159 AccountId32: {160 network: 'Any',161 id: alice.addressRaw,162 },163 }},164 },165 };166167 const assets = {168 V1: [169 {170 id: {171 Concrete: {172 parents: 0,173 interior: 'Here',174 },175 },176 fun: {177 Fungible: TRANSFER_AMOUNT_RELAY,178 },179 },180 ],181 };182183 const feeAssetItem = 0;184185 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, {Unlimited: null});186 });187 188 });189190 itSub('Should connect and send USDT from Statemine to Quartz', async ({helper}) => {191 await usingStateminePlaygrounds(statemineUrl, async (helper) => {192 const dest = {193 V1: {194 parents: 1,195 interior: {X1: {196 Parachain: QUARTZ_CHAIN,197 },198 },199 }};200201 const beneficiary = {202 V1: {203 parents: 0,204 interior: {X1: {205 AccountId32: {206 network: 'Any',207 id: alice.addressRaw,208 },209 }},210 },211 };212213 const assets = {214 V1: [215 {216 id: {217 Concrete: {218 parents: 0,219 interior: {220 X2: [221 {222 PalletInstance: STATEMINE_PALLET_INSTANCE,223 },224 {225 GeneralIndex: USDT_ASSET_ID,226 }, 227 ]},228 },229 },230 fun: {231 Fungible: TRANSFER_AMOUNT,232 },233 },234 ],235 };236237 const feeAssetItem = 0;238239 balanceStmnBefore = await helper.balance.getSubstrate(alice.address);240 await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, {Unlimited: null});241242 balanceStmnAfter = await helper.balance.getSubstrate(alice.address);243244 // common good parachain take commission in it native token245 console.log(246 '[Quartz -> Statemine] transaction fees on Statemine: %s WND',247 helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINE_DECIMALS),248 );249 expect(balanceStmnBefore > balanceStmnAfter).to.be.true;250251 });252253254 // ensure that asset has been delivered255 await helper.wait.newBlocks(3);256257 // expext collection id will be with id 1258 const free = await helper.ft.getBalance(1, {Substrate: alice.address});259260 balanceQuartzAfter = await helper.balance.getSubstrate(alice.address);261262 console.log(263 '[Quartz -> Statemine] transaction fees on Quartz: %s USDT',264 helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),265 );266 console.log(267 '[Quartz -> Statemine] transaction fees on Quartz: %s QTZ',268 helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzBefore),269 ); 270 // commission has not paid in USDT token271 expect(free).to.be.equal(TRANSFER_AMOUNT);272 // ... and parachain native token273 expect(balanceQuartzAfter == balanceQuartzBefore).to.be.true;274 });275276 itSub('Should connect and send USDT from Quartz to Statemine back', async ({helper}) => {277 const destination = {278 V1: {279 parents: 1,280 interior: {X2: [281 {282 Parachain: STATEMINE_CHAIN,283 },284 {285 AccountId32: {286 network: 'Any',287 id: alice.addressRaw,288 },289 },290 ]},291 },292 };293294 const relayFee = 400_000_000_000_000n;295 const currencies: [any, bigint][] = [296 [297 {298 ForeignAssetId: 0,299 },300 TRANSFER_AMOUNT,301 ], 302 [303 {304 NativeAssetId: 'Parent',305 },306 relayFee,307 ],308 ];309310 const feeItem = 1;311312 await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, {Unlimited: null});313 314 // the commission has been paid in parachain native token315 balanceQuartzFinal = await helper.balance.getSubstrate(alice.address);316 expect(balanceQuartzAfter > balanceQuartzFinal).to.be.true;317318 await usingStateminePlaygrounds(statemineUrl, async (helper) => {319 await helper.wait.newBlocks(3);320 321 // The USDT token never paid fees. Its amount not changed from begin value.322 // Also check that xcm transfer has been succeeded 323 expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;324 });325 });326327 itSub('Should connect and send Relay token to Quartz', async ({helper}) => {328 balanceBobBefore = await helper.balance.getSubstrate(bob.address);329 balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});330331 await usingRelayPlaygrounds(relayUrl, async (helper) => {332 const destination = {333 V1: {334 parents: 0,335 interior: {X1: {336 Parachain: QUARTZ_CHAIN,337 },338 },339 }};340341 const beneficiary = {342 V1: {343 parents: 0,344 interior: {X1: {345 AccountId32: {346 network: 'Any',347 id: bob.addressRaw,348 },349 }},350 },351 };352353 const assets = {354 V1: [355 {356 id: {357 Concrete: {358 parents: 0,359 interior: 'Here',360 },361 },362 fun: {363 Fungible: TRANSFER_AMOUNT_RELAY,364 },365 },366 ],367 };368369 const feeAssetItem = 0;370371 await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, {Unlimited: null});372 });373 374 await helper.wait.newBlocks(3);375376 balanceBobAfter = await helper.balance.getSubstrate(bob.address); 377 balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});378379 const wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;380 console.log(381 '[Relay (Westend) -> Quartz] transaction fees: %s QTZ',382 helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),383 );384 console.log(385 '[Relay (Westend) -> Quartz] transaction fees: %s WND',386 helper.util.bigIntToDecimals(wndFeeOnQuartz, STATEMINE_DECIMALS),387 );388 expect(wndFeeOnQuartz == 0n, 'No incoming WND fees should be taken').to.be.true;389 expect(balanceBobBefore == balanceBobAfter, 'No incoming QTZ fees should be taken').to.be.true;390 expect(balanceBobRelayTokenBefore < balanceBobRelayTokenAfter).to.be.true;391 });392393 itSub('Should connect and send Relay token back', async ({helper}) => {394 const destination = {395 V1: {396 parents: 1,397 interior: {X2: [398 {399 Parachain: STATEMINE_CHAIN,400 },401 {402 AccountId32: {403 network: 'Any',404 id: bob.addressRaw,405 },406 },407 ]},408 },409 };410411 const currencies: any = [412 [413 {414 NativeAssetId: 'Parent',415 },416 TRANSFER_AMOUNT_RELAY,417 ],418 ];419420 const feeItem = 0;421422 await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, {Unlimited: null});423424 balanceBobFinal = await helper.balance.getSubstrate(bob.address);425 console.log('[Relay (Westend) to Quartz] transaction fees: %s QTZ', balanceBobAfter - balanceBobFinal);426 });427});428429describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => {430 let alice: IKeyringPair;431 let randomAccount: IKeyringPair;432433 let balanceQuartzTokenInit: bigint;434 let balanceQuartzTokenMiddle: bigint;435 let balanceQuartzTokenFinal: bigint;436 let balanceKaruraTokenInit: bigint;437 let balanceKaruraTokenMiddle: bigint;438 let balanceKaruraTokenFinal: bigint;439 let balanceQuartzForeignTokenInit: bigint;440 let balanceQuartzForeignTokenMiddle: bigint;441 let balanceQuartzForeignTokenFinal: bigint;442443 before(async () => {444 await usingPlaygrounds(async (helper, privateKey) => {445 alice = await privateKey('//Alice');446 [randomAccount] = await helper.arrange.createAccounts([0n], alice);447 });448449 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {450 const destination = {451 V0: {452 X2: [453 'Parent',454 {455 Parachain: QUARTZ_CHAIN,456 },457 ],458 },459 };460461 const metadata = {462 name: 'QTZ',463 symbol: 'QTZ',464 decimals: 18,465 minimalBalance: 1n,466 };467468 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);469 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);470 balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);471 balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});472 });473474 await usingPlaygrounds(async (helper) => {475 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);476 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);477 });478 });479480 itSub('Should connect and send QTZ to Karura', async ({helper}) => {481 const destination = {482 V0: {483 X2: [484 'Parent',485 {486 Parachain: KARURA_CHAIN,487 },488 ],489 },490 };491492 const beneficiary = {493 V0: {494 X1: {495 AccountId32: {496 network: 'Any',497 id: randomAccount.addressRaw,498 },499 },500 },501 };502503 const assets = {504 V1: [505 {506 id: {507 Concrete: {508 parents: 0,509 interior: 'Here',510 },511 },512 fun: {513 Fungible: TRANSFER_AMOUNT,514 },515 },516 ],517 };518519 const feeAssetItem = 0;520521 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, {Unlimited: null});522 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);523524 const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;525 expect(qtzFees > 0n, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;526 console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));527528 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {529 await helper.wait.newBlocks(3);530 balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});531 balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);532533 const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;534 const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;535536 console.log(537 '[Quartz -> Karura] transaction fees on Karura: %s KAR',538 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),539 );540 console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));541 expect(karFees == 0n).to.be.true;542 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;543 });544 });545546 itSub('Should connect to Karura and send QTZ back', async ({helper}) => {547 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {548 const destination = {549 V1: {550 parents: 1,551 interior: {552 X2: [553 {Parachain: QUARTZ_CHAIN},554 {555 AccountId32: {556 network: 'Any',557 id: randomAccount.addressRaw,558 },559 },560 ],561 },562 },563 };564565 const id = {566 ForeignAsset: 0,567 };568569 await helper.xTokens.transfer(randomAccount, id, TRANSFER_AMOUNT, destination, {Unlimited: null});570 balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);571 balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);572573 const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;574 const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;575576 console.log(577 '[Karura -> Quartz] transaction fees on Karura: %s KAR',578 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),579 );580 console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));581582 expect(karFees > 0, 'Negative fees KAR, looks like nothing was transferred').to.be.true;583 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;584 });585586 await helper.wait.newBlocks(3);587588 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);589 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;590 expect(actuallyDelivered > 0).to.be.true;591592 console.log('[Karura -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));593594 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;595 console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));596 expect(qtzFees == 0n).to.be.true;597 });598});599600// These tests are relevant only when the foreign asset pallet is disabled601describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => {602 let alice: IKeyringPair;603604 before(async () => {605 await usingPlaygrounds(async (_helper, privateKey) => {606 alice = await privateKey('//Alice');607 });608 });609610 itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {611 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {612 const destination = {613 V1: {614 parents: 1,615 interior: {616 X2: [617 {Parachain: QUARTZ_CHAIN},618 {619 AccountId32: {620 network: 'Any',621 id: alice.addressRaw,622 },623 },624 ],625 },626 },627 };628629 const id = {630 Token: 'KAR',631 };632633 await helper.xTokens.transfer(alice, id, 100_000_000_000n, destination, {Unlimited: null});634 });635636 const maxWaitBlocks = 3;637638 const xcmpQueueFailEvent = await helper.wait.event(maxWaitBlocks, 'xcmpQueue', 'Fail');639640 expect(641 xcmpQueueFailEvent != null,642 '[Karura] xcmpQueue.FailEvent event is expected',643 ).to.be.true;644645 const event = xcmpQueueFailEvent!.event;646 const outcome = event.data[1] as XcmV2TraitsError;647648 expect(649 outcome.isFailedToTransactAsset,650 '[Karura] The XCM error should be `FailedToTransactAsset`',651 ).to.be.true;652 });653});654655describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {656 // Quartz constants657 let quartzDonor: IKeyringPair;658 let quartzAssetLocation;659660 let randomAccountQuartz: IKeyringPair;661 let randomAccountMoonriver: IKeyringPair;662663 // Moonriver constants664 let assetId: string;665666 const councilVotingThreshold = 2;667 const technicalCommitteeThreshold = 2;668 const votingPeriod = 3;669 const delayPeriod = 0;670671 const quartzAssetMetadata = {672 name: 'xcQuartz',673 symbol: 'xcQTZ',674 decimals: 18,675 isFrozen: false,676 minimalBalance: 1n,677 };678679 let balanceQuartzTokenInit: bigint;680 let balanceQuartzTokenMiddle: bigint;681 let balanceQuartzTokenFinal: bigint;682 let balanceForeignQtzTokenInit: bigint;683 let balanceForeignQtzTokenMiddle: bigint;684 let balanceForeignQtzTokenFinal: bigint;685 let balanceMovrTokenInit: bigint;686 let balanceMovrTokenMiddle: bigint;687 let balanceMovrTokenFinal: bigint;688689 before(async () => {690 await usingPlaygrounds(async (helper, privateKey) => {691 quartzDonor = await privateKey('//Alice');692 [randomAccountQuartz] = await helper.arrange.createAccounts([0n], quartzDonor);693694 balanceForeignQtzTokenInit = 0n;695 });696697 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {698 const alithAccount = helper.account.alithAccount();699 const baltatharAccount = helper.account.baltatharAccount();700 const dorothyAccount = helper.account.dorothyAccount();701702 randomAccountMoonriver = helper.account.create();703704 // >>> Sponsoring Dorothy >>>705 console.log('Sponsoring Dorothy.......');706 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);707 console.log('Sponsoring Dorothy.......DONE');708 // <<< Sponsoring Dorothy <<<709710 quartzAssetLocation = {711 XCM: {712 parents: 1,713 interior: {X1: {Parachain: QUARTZ_CHAIN}},714 },715 };716 const existentialDeposit = 1n;717 const isSufficient = true;718 const unitsPerSecond = 1n;719 const numAssetsWeightHint = 0;720721 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({722 location: quartzAssetLocation,723 metadata: quartzAssetMetadata,724 existentialDeposit,725 isSufficient,726 unitsPerSecond,727 numAssetsWeightHint,728 });729 const proposalHash = blake2AsHex(encodedProposal);730731 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);732 console.log('Encoded length %d', encodedProposal.length);733 console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);734735 // >>> Note motion preimage >>>736 console.log('Note motion preimage.......');737 await helper.democracy.notePreimage(baltatharAccount, encodedProposal);738 console.log('Note motion preimage.......DONE');739 // <<< Note motion preimage <<<740741 // >>> Propose external motion through council >>>742 console.log('Propose external motion through council.......');743 const externalMotion = helper.democracy.externalProposeMajority(proposalHash);744 const encodedMotion = externalMotion?.method.toHex() || '';745 const motionHash = blake2AsHex(encodedMotion);746 console.log('Motion hash is %s', motionHash);747748 await helper.collective.council.propose(baltatharAccount, councilVotingThreshold, externalMotion, externalMotion.encodedLength);749750 const councilProposalIdx = await helper.collective.council.proposalCount() - 1;751 await helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);752 await helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);753754 await helper.collective.council.close(dorothyAccount, motionHash, councilProposalIdx, 1_000_000_000, externalMotion.encodedLength);755 console.log('Propose external motion through council.......DONE');756 // <<< Propose external motion through council <<<757758 // >>> Fast track proposal through technical committee >>>759 console.log('Fast track proposal through technical committee.......');760 const fastTrack = helper.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);761 const encodedFastTrack = fastTrack?.method.toHex() || '';762 const fastTrackHash = blake2AsHex(encodedFastTrack);763 console.log('FastTrack hash is %s', fastTrackHash);764765 await helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);766767 const techProposalIdx = await helper.collective.techCommittee.proposalCount() - 1;768 await helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);769 await helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);770771 await helper.collective.techCommittee.close(baltatharAccount, fastTrackHash, techProposalIdx, 1_000_000_000, fastTrack.encodedLength);772 console.log('Fast track proposal through technical committee.......DONE');773 // <<< Fast track proposal through technical committee <<<774775 // >>> Referendum voting >>>776 console.log('Referendum voting.......');777 await helper.democracy.referendumVote(dorothyAccount, 0, {778 balance: 10_000_000_000_000_000_000n,779 vote: {aye: true, conviction: 1},780 });781 console.log('Referendum voting.......DONE');782 // <<< Referendum voting <<<783784 // >>> Acquire Quartz AssetId Info on Moonriver >>>785 console.log('Acquire Quartz AssetId Info on Moonriver.......');786787 // Wait for the democracy execute788 await helper.wait.newBlocks(5);789790 assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();791792 console.log('QTZ asset ID is %s', assetId);793 console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');794 // >>> Acquire Quartz AssetId Info on Moonriver >>>795796 // >>> Sponsoring random Account >>>797 console.log('Sponsoring random Account.......');798 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);799 console.log('Sponsoring random Account.......DONE');800 // <<< Sponsoring random Account <<<801802 balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);803 });804805 await usingPlaygrounds(async (helper) => {806 await helper.balance.transferToSubstrate(quartzDonor, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);807 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);808 });809 });810811 itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {812 const currencyId = {813 NativeAssetId: 'Here',814 };815 const dest = {816 V1: {817 parents: 1,818 interior: {819 X2: [820 {Parachain: MOONRIVER_CHAIN},821 {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},822 ],823 },824 },825 };826 const amount = TRANSFER_AMOUNT;827828 await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, {Unlimited: null});829830 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);831 expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;832833 const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;834 console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));835 expect(transactionFees > 0, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;836837 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {838 await helper.wait.newBlocks(3);839840 balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);841842 const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;843 console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));844 expect(movrFees == 0n).to.be.true;845846 balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; // BigInt(qtzRandomAccountAsset['balance']);847 const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;848 console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));849 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;850 });851 });852853 itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {854 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {855 const asset = {856 V1: {857 id: {858 Concrete: {859 parents: 1,860 interior: {861 X1: {Parachain: QUARTZ_CHAIN},862 },863 },864 },865 fun: {866 Fungible: TRANSFER_AMOUNT,867 },868 },869 };870 const destination = {871 V1: {872 parents: 1,873 interior: {874 X2: [875 {Parachain: QUARTZ_CHAIN},876 {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},877 ],878 },879 },880 };881882 await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, {Unlimited: null});883884 balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);885886 const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;887 console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));888 expect(movrFees > 0, 'Negative fees MOVR, looks like nothing was transferred').to.be.true;889890 const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);891892 expect(qtzRandomAccountAsset).to.be.null;893894 balanceForeignQtzTokenFinal = 0n;895896 const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;897 console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));898 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;899 });900901 await helper.wait.newBlocks(3);902903 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);904 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;905 expect(actuallyDelivered > 0).to.be.true;906907 console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));908909 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;910 console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));911 expect(qtzFees == 0n).to.be.true;912 });913});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 {IKeyringPair} from '@polkadot/types/types';18import {blake2AsHex} from '@polkadot/util-crypto';19import config from '../config';20import {XcmV2TraitsError} from '../interfaces';21import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds} from '../util';2223const QUARTZ_CHAIN = 2095;24const STATEMINE_CHAIN = 1000;25const KARURA_CHAIN = 2000;26const MOONRIVER_CHAIN = 2023;2728const STATEMINE_PALLET_INSTANCE = 50;2930const relayUrl = config.relayUrl;31const statemineUrl = config.statemineUrl;32const karuraUrl = config.karuraUrl;33const moonriverUrl = config.moonriverUrl;3435const STATEMINE_DECIMALS = 12;36const KARURA_DECIMALS = 12;3738const TRANSFER_AMOUNT = 2000000000000000000000000n;3940const FUNDING_AMOUNT = 3_500_000_0000_000_000n; 4142const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;4344const USDT_ASSET_ID = 100;45const USDT_ASSET_METADATA_DECIMALS = 18;46const USDT_ASSET_METADATA_NAME = 'USDT';47const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';48const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;49const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;5051describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => {52 let alice: IKeyringPair;53 let bob: IKeyringPair;54 55 let balanceStmnBefore: bigint;56 let balanceStmnAfter: bigint;5758 let balanceQuartzBefore: bigint;59 let balanceQuartzAfter: bigint;60 let balanceQuartzFinal: bigint;6162 let balanceBobBefore: bigint;63 let balanceBobAfter: bigint;64 let balanceBobFinal: bigint;6566 let balanceBobRelayTokenBefore: bigint;67 let balanceBobRelayTokenAfter: bigint;686970 before(async () => {71 await usingPlaygrounds(async (_helper, privateKey) => {72 alice = await privateKey('//Alice');73 bob = await privateKey('//Bob'); // sovereign account on Statemine(t) funds donor74 });7576 await usingRelayPlaygrounds(relayUrl, async (helper) => {77 // Fund accounts on Statemine(t)78 await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, alice.addressRaw, FUNDING_AMOUNT);79 await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, bob.addressRaw, FUNDING_AMOUNT);80 });8182 await usingStateminePlaygrounds(statemineUrl, async (helper) => {83 const sovereignFundingAmount = 3_500_000_000n; 8485 await helper.assets.create(86 alice,87 USDT_ASSET_ID,88 alice.address,89 USDT_ASSET_METADATA_MINIMAL_BALANCE,90 );91 await helper.assets.setMetadata(92 alice,93 USDT_ASSET_ID,94 USDT_ASSET_METADATA_NAME,95 USDT_ASSET_METADATA_DESCRIPTION,96 USDT_ASSET_METADATA_DECIMALS,97 );98 await helper.assets.mint(99 alice,100 USDT_ASSET_ID,101 alice.address,102 USDT_ASSET_AMOUNT,103 );104105 // funding parachain sovereing account on Statemine(t).106 // The sovereign account should be created before any action107 // (the assets pallet on Statemine(t) check if the sovereign account exists)108 const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(QUARTZ_CHAIN);109 await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);110 });111112113 await usingPlaygrounds(async (helper) => {114 const location = {115 V1: {116 parents: 1,117 interior: {X3: [118 {119 Parachain: STATEMINE_CHAIN,120 },121 {122 PalletInstance: STATEMINE_PALLET_INSTANCE,123 },124 {125 GeneralIndex: USDT_ASSET_ID,126 },127 ]},128 },129 };130131 const metadata =132 {133 name: USDT_ASSET_ID,134 symbol: USDT_ASSET_METADATA_NAME,135 decimals: USDT_ASSET_METADATA_DECIMALS,136 minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,137 };138 await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);139 balanceQuartzBefore = await helper.balance.getSubstrate(alice.address);140 });141142143 // Providing the relay currency to the quartz sender account144 // (fee for USDT XCM are paid in relay tokens)145 await usingRelayPlaygrounds(relayUrl, async (helper) => {146 const destination = {147 V1: {148 parents: 0,149 interior: {X1: {150 Parachain: QUARTZ_CHAIN,151 },152 },153 }};154155 const beneficiary = {156 V1: {157 parents: 0,158 interior: {X1: {159 AccountId32: {160 network: 'Any',161 id: alice.addressRaw,162 },163 }},164 },165 };166167 const assets = {168 V1: [169 {170 id: {171 Concrete: {172 parents: 0,173 interior: 'Here',174 },175 },176 fun: {177 Fungible: TRANSFER_AMOUNT_RELAY,178 },179 },180 ],181 };182183 const feeAssetItem = 0;184185 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, {Unlimited: null});186 });187 188 });189190 itSub('Should connect and send USDT from Statemine to Quartz', async ({helper}) => {191 await usingStateminePlaygrounds(statemineUrl, async (helper) => {192 const dest = {193 V1: {194 parents: 1,195 interior: {X1: {196 Parachain: QUARTZ_CHAIN,197 },198 },199 }};200201 const beneficiary = {202 V1: {203 parents: 0,204 interior: {X1: {205 AccountId32: {206 network: 'Any',207 id: alice.addressRaw,208 },209 }},210 },211 };212213 const assets = {214 V1: [215 {216 id: {217 Concrete: {218 parents: 0,219 interior: {220 X2: [221 {222 PalletInstance: STATEMINE_PALLET_INSTANCE,223 },224 {225 GeneralIndex: USDT_ASSET_ID,226 }, 227 ]},228 },229 },230 fun: {231 Fungible: TRANSFER_AMOUNT,232 },233 },234 ],235 };236237 const feeAssetItem = 0;238239 balanceStmnBefore = await helper.balance.getSubstrate(alice.address);240 await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, {Unlimited: null});241242 balanceStmnAfter = await helper.balance.getSubstrate(alice.address);243244 // common good parachain take commission in it native token245 console.log(246 '[Quartz -> Statemine] transaction fees on Statemine: %s WND',247 helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINE_DECIMALS),248 );249 expect(balanceStmnBefore > balanceStmnAfter).to.be.true;250251 });252253254 // ensure that asset has been delivered255 await helper.wait.newBlocks(3);256257 // expext collection id will be with id 1258 const free = await helper.ft.getBalance(1, {Substrate: alice.address});259260 balanceQuartzAfter = await helper.balance.getSubstrate(alice.address);261262 console.log(263 '[Quartz -> Statemine] transaction fees on Quartz: %s USDT',264 helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),265 );266 console.log(267 '[Quartz -> Statemine] transaction fees on Quartz: %s QTZ',268 helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzBefore),269 ); 270 // commission has not paid in USDT token271 expect(free).to.be.equal(TRANSFER_AMOUNT);272 // ... and parachain native token273 expect(balanceQuartzAfter == balanceQuartzBefore).to.be.true;274 });275276 itSub('Should connect and send USDT from Quartz to Statemine back', async ({helper}) => {277 const destination = {278 V1: {279 parents: 1,280 interior: {X2: [281 {282 Parachain: STATEMINE_CHAIN,283 },284 {285 AccountId32: {286 network: 'Any',287 id: alice.addressRaw,288 },289 },290 ]},291 },292 };293294 const relayFee = 400_000_000_000_000n;295 const currencies: [any, bigint][] = [296 [297 {298 ForeignAssetId: 0,299 },300 TRANSFER_AMOUNT,301 ], 302 [303 {304 NativeAssetId: 'Parent',305 },306 relayFee,307 ],308 ];309310 const feeItem = 1;311312 await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, {Unlimited: null});313 314 // the commission has been paid in parachain native token315 balanceQuartzFinal = await helper.balance.getSubstrate(alice.address);316 expect(balanceQuartzAfter > balanceQuartzFinal).to.be.true;317318 await usingStateminePlaygrounds(statemineUrl, async (helper) => {319 await helper.wait.newBlocks(3);320 321 // The USDT token never paid fees. Its amount not changed from begin value.322 // Also check that xcm transfer has been succeeded 323 expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;324 });325 });326327 itSub('Should connect and send Relay token to Quartz', async ({helper}) => {328 balanceBobBefore = await helper.balance.getSubstrate(bob.address);329 balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});330331 await usingRelayPlaygrounds(relayUrl, async (helper) => {332 const destination = {333 V1: {334 parents: 0,335 interior: {X1: {336 Parachain: QUARTZ_CHAIN,337 },338 },339 }};340341 const beneficiary = {342 V1: {343 parents: 0,344 interior: {X1: {345 AccountId32: {346 network: 'Any',347 id: bob.addressRaw,348 },349 }},350 },351 };352353 const assets = {354 V1: [355 {356 id: {357 Concrete: {358 parents: 0,359 interior: 'Here',360 },361 },362 fun: {363 Fungible: TRANSFER_AMOUNT_RELAY,364 },365 },366 ],367 };368369 const feeAssetItem = 0;370371 await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, {Unlimited: null});372 });373 374 await helper.wait.newBlocks(3);375376 balanceBobAfter = await helper.balance.getSubstrate(bob.address); 377 balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});378379 const wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;380 console.log(381 '[Relay (Westend) -> Quartz] transaction fees: %s QTZ',382 helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),383 );384 console.log(385 '[Relay (Westend) -> Quartz] transaction fees: %s WND',386 helper.util.bigIntToDecimals(wndFeeOnQuartz, STATEMINE_DECIMALS),387 );388 expect(wndFeeOnQuartz == 0n, 'No incoming WND fees should be taken').to.be.true;389 expect(balanceBobBefore == balanceBobAfter, 'No incoming QTZ fees should be taken').to.be.true;390 expect(balanceBobRelayTokenBefore < balanceBobRelayTokenAfter).to.be.true;391 });392393 itSub('Should connect and send Relay token back', async ({helper}) => {394 const destination = {395 V1: {396 parents: 1,397 interior: {X2: [398 {399 Parachain: STATEMINE_CHAIN,400 },401 {402 AccountId32: {403 network: 'Any',404 id: bob.addressRaw,405 },406 },407 ]},408 },409 };410411 const currencies: any = [412 [413 {414 NativeAssetId: 'Parent',415 },416 TRANSFER_AMOUNT_RELAY,417 ],418 ];419420 const feeItem = 0;421422 await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, {Unlimited: null});423424 balanceBobFinal = await helper.balance.getSubstrate(bob.address);425 console.log('[Relay (Westend) to Quartz] transaction fees: %s QTZ', balanceBobAfter - balanceBobFinal);426 });427});428429describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => {430 let alice: IKeyringPair;431 let randomAccount: IKeyringPair;432433 let balanceQuartzTokenInit: bigint;434 let balanceQuartzTokenMiddle: bigint;435 let balanceQuartzTokenFinal: bigint;436 let balanceKaruraTokenInit: bigint;437 let balanceKaruraTokenMiddle: bigint;438 let balanceKaruraTokenFinal: bigint;439 let balanceQuartzForeignTokenInit: bigint;440 let balanceQuartzForeignTokenMiddle: bigint;441 let balanceQuartzForeignTokenFinal: bigint;442443 before(async () => {444 await usingPlaygrounds(async (helper, privateKey) => {445 alice = await privateKey('//Alice');446 [randomAccount] = await helper.arrange.createAccounts([0n], alice);447 });448449 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {450 const destination = {451 V0: {452 X2: [453 'Parent',454 {455 Parachain: QUARTZ_CHAIN,456 },457 ],458 },459 };460461 const metadata = {462 name: 'QTZ',463 symbol: 'QTZ',464 decimals: 18,465 minimalBalance: 1n,466 };467468 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);469 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);470 balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);471 balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});472 });473474 await usingPlaygrounds(async (helper) => {475 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);476 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);477 });478 });479480 itSub('Should connect and send QTZ to Karura', async ({helper}) => {481 const destination = {482 V0: {483 X2: [484 'Parent',485 {486 Parachain: KARURA_CHAIN,487 },488 ],489 },490 };491492 const beneficiary = {493 V0: {494 X1: {495 AccountId32: {496 network: 'Any',497 id: randomAccount.addressRaw,498 },499 },500 },501 };502503 const assets = {504 V1: [505 {506 id: {507 Concrete: {508 parents: 0,509 interior: 'Here',510 },511 },512 fun: {513 Fungible: TRANSFER_AMOUNT,514 },515 },516 ],517 };518519 const feeAssetItem = 0;520521 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, {Unlimited: null});522 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);523524 const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;525 expect(qtzFees > 0n, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;526 console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));527528 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {529 await helper.wait.newBlocks(3);530 balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});531 balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);532533 const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;534 const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;535536 console.log(537 '[Quartz -> Karura] transaction fees on Karura: %s KAR',538 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),539 );540 console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));541 expect(karFees == 0n).to.be.true;542 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;543 });544 });545546 itSub('Should connect to Karura and send QTZ back', async ({helper}) => {547 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {548 const destination = {549 V1: {550 parents: 1,551 interior: {552 X2: [553 {Parachain: QUARTZ_CHAIN},554 {555 AccountId32: {556 network: 'Any',557 id: randomAccount.addressRaw,558 },559 },560 ],561 },562 },563 };564565 const id = {566 ForeignAsset: 0,567 };568569 await helper.xTokens.transfer(randomAccount, id, TRANSFER_AMOUNT, destination, {Unlimited: null});570 balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);571 balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);572573 const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;574 const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;575576 console.log(577 '[Karura -> Quartz] transaction fees on Karura: %s KAR',578 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),579 );580 console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));581582 expect(karFees > 0, 'Negative fees KAR, looks like nothing was transferred').to.be.true;583 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;584 });585586 await helper.wait.newBlocks(3);587588 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);589 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;590 expect(actuallyDelivered > 0).to.be.true;591592 console.log('[Karura -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));593594 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;595 console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));596 expect(qtzFees == 0n).to.be.true;597 });598});599600// These tests are relevant only when the foreign asset pallet is disabled601describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => {602 let alice: IKeyringPair;603604 before(async () => {605 await usingPlaygrounds(async (_helper, privateKey) => {606 alice = await privateKey('//Alice');607 });608 });609610 itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {611 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {612 const destination = {613 V1: {614 parents: 1,615 interior: {616 X2: [617 {Parachain: QUARTZ_CHAIN},618 {619 AccountId32: {620 network: 'Any',621 id: alice.addressRaw,622 },623 },624 ],625 },626 },627 };628629 const id = {630 Token: 'KAR',631 };632633 await helper.xTokens.transfer(alice, id, 100_000_000_000n, destination, {Unlimited: null});634 });635636 const maxWaitBlocks = 3;637638 const xcmpQueueFailEvent = await helper.wait.event(maxWaitBlocks, 'xcmpQueue', 'Fail');639640 expect(641 xcmpQueueFailEvent != null,642 '[Karura] xcmpQueue.FailEvent event is expected',643 ).to.be.true;644645 const event = xcmpQueueFailEvent!.event;646 const outcome = event.data[1] as XcmV2TraitsError;647648 expect(649 outcome.isFailedToTransactAsset,650 '[Karura] The XCM error should be `FailedToTransactAsset`',651 ).to.be.true;652 });653});654655describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {656 // Quartz constants657 let quartzDonor: IKeyringPair;658 let quartzAssetLocation;659660 let randomAccountQuartz: IKeyringPair;661 let randomAccountMoonriver: IKeyringPair;662663 // Moonriver constants664 let assetId: string;665666 const councilVotingThreshold = 2;667 const technicalCommitteeThreshold = 2;668 const votingPeriod = 3;669 const delayPeriod = 0;670671 const quartzAssetMetadata = {672 name: 'xcQuartz',673 symbol: 'xcQTZ',674 decimals: 18,675 isFrozen: false,676 minimalBalance: 1n,677 };678679 let balanceQuartzTokenInit: bigint;680 let balanceQuartzTokenMiddle: bigint;681 let balanceQuartzTokenFinal: bigint;682 let balanceForeignQtzTokenInit: bigint;683 let balanceForeignQtzTokenMiddle: bigint;684 let balanceForeignQtzTokenFinal: bigint;685 let balanceMovrTokenInit: bigint;686 let balanceMovrTokenMiddle: bigint;687 let balanceMovrTokenFinal: bigint;688689 before(async () => {690 await usingPlaygrounds(async (helper, privateKey) => {691 quartzDonor = await privateKey('//Alice');692 [randomAccountQuartz] = await helper.arrange.createAccounts([0n], quartzDonor);693694 balanceForeignQtzTokenInit = 0n;695 });696697 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {698 const alithAccount = helper.account.alithAccount();699 const baltatharAccount = helper.account.baltatharAccount();700 const dorothyAccount = helper.account.dorothyAccount();701702 randomAccountMoonriver = helper.account.create();703704 // >>> Sponsoring Dorothy >>>705 console.log('Sponsoring Dorothy.......');706 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);707 console.log('Sponsoring Dorothy.......DONE');708 // <<< Sponsoring Dorothy <<<709710 quartzAssetLocation = {711 XCM: {712 parents: 1,713 interior: {X1: {Parachain: QUARTZ_CHAIN}},714 },715 };716 const existentialDeposit = 1n;717 const isSufficient = true;718 const unitsPerSecond = 1n;719 const numAssetsWeightHint = 0;720721 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({722 location: quartzAssetLocation,723 metadata: quartzAssetMetadata,724 existentialDeposit,725 isSufficient,726 unitsPerSecond,727 numAssetsWeightHint,728 });729 const proposalHash = blake2AsHex(encodedProposal);730731 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);732 console.log('Encoded length %d', encodedProposal.length);733 console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);734735 // >>> Note motion preimage >>>736 console.log('Note motion preimage.......');737 await helper.democracy.notePreimage(baltatharAccount, encodedProposal);738 console.log('Note motion preimage.......DONE');739 // <<< Note motion preimage <<<740741 // >>> Propose external motion through council >>>742 console.log('Propose external motion through council.......');743 const externalMotion = helper.democracy.externalProposeMajority({Legacy: proposalHash});744 const encodedMotion = externalMotion?.method.toHex() || '';745 const motionHash = blake2AsHex(encodedMotion);746 console.log('Motion hash is %s', motionHash);747748 await helper.collective.council.propose(baltatharAccount, councilVotingThreshold, externalMotion, externalMotion.encodedLength);749750 const councilProposalIdx = await helper.collective.council.proposalCount() - 1;751 await helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);752 await helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);753754 await helper.collective.council.close(755 dorothyAccount,756 motionHash,757 councilProposalIdx,758 {759 refTime: 1_000_000_000,760 proofSize: 1_000_000,761 },762 externalMotion.encodedLength,763 );764 console.log('Propose external motion through council.......DONE');765 // <<< Propose external motion through council <<<766767 // >>> Fast track proposal through technical committee >>>768 console.log('Fast track proposal through technical committee.......');769 const fastTrack = helper.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);770 const encodedFastTrack = fastTrack?.method.toHex() || '';771 const fastTrackHash = blake2AsHex(encodedFastTrack);772 console.log('FastTrack hash is %s', fastTrackHash);773774 await helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);775776 const techProposalIdx = await helper.collective.techCommittee.proposalCount() - 1;777 await helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);778 await helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);779780 await helper.collective.techCommittee.close(781 baltatharAccount,782 fastTrackHash,783 techProposalIdx,784 {785 refTime: 1_000_000_000,786 proofSize: 1_000_000,787 },788 fastTrack.encodedLength,789 );790 console.log('Fast track proposal through technical committee.......DONE');791 // <<< Fast track proposal through technical committee <<<792793 // >>> Referendum voting >>>794 console.log('Referendum voting.......');795 await helper.democracy.referendumVote(dorothyAccount, 0, {796 balance: 10_000_000_000_000_000_000n,797 vote: {aye: true, conviction: 1},798 });799 console.log('Referendum voting.......DONE');800 // <<< Referendum voting <<<801802 // >>> Acquire Quartz AssetId Info on Moonriver >>>803 console.log('Acquire Quartz AssetId Info on Moonriver.......');804805 // Wait for the democracy execute806 await helper.wait.newBlocks(5);807808 assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();809810 console.log('QTZ asset ID is %s', assetId);811 console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');812 // >>> Acquire Quartz AssetId Info on Moonriver >>>813814 // >>> Sponsoring random Account >>>815 console.log('Sponsoring random Account.......');816 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);817 console.log('Sponsoring random Account.......DONE');818 // <<< Sponsoring random Account <<<819820 balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);821 });822823 await usingPlaygrounds(async (helper) => {824 await helper.balance.transferToSubstrate(quartzDonor, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);825 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);826 });827 });828829 itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {830 const currencyId = {831 NativeAssetId: 'Here',832 };833 const dest = {834 V1: {835 parents: 1,836 interior: {837 X2: [838 {Parachain: MOONRIVER_CHAIN},839 {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},840 ],841 },842 },843 };844 const amount = TRANSFER_AMOUNT;845846 await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, {Unlimited: null});847848 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);849 expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;850851 const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;852 console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));853 expect(transactionFees > 0, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;854855 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {856 await helper.wait.newBlocks(3);857858 balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);859860 const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;861 console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));862 expect(movrFees == 0n).to.be.true;863864 balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; // BigInt(qtzRandomAccountAsset['balance']);865 const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;866 console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));867 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;868 });869 });870871 itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {872 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {873 const asset = {874 V1: {875 id: {876 Concrete: {877 parents: 1,878 interior: {879 X1: {Parachain: QUARTZ_CHAIN},880 },881 },882 },883 fun: {884 Fungible: TRANSFER_AMOUNT,885 },886 },887 };888 const destination = {889 V1: {890 parents: 1,891 interior: {892 X2: [893 {Parachain: QUARTZ_CHAIN},894 {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},895 ],896 },897 },898 };899900 await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, {Unlimited: null});901902 balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);903904 const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;905 console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));906 expect(movrFees > 0, 'Negative fees MOVR, looks like nothing was transferred').to.be.true;907908 const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);909910 expect(qtzRandomAccountAsset).to.be.null;911912 balanceForeignQtzTokenFinal = 0n;913914 const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;915 console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));916 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;917 });918919 await helper.wait.newBlocks(3);920921 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);922 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;923 expect(actuallyDelivered > 0).to.be.true;924925 console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));926927 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;928 console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));929 expect(qtzFees == 0n).to.be.true;930 });931});