difftreelog
Merge pull request #889 from UniqueNetwork/fix/acala-xcm-test-config
in: master
2 files 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 RELAY_DECIMALS = 12;36const STATEMINE_DECIMALS = 12;37const KARURA_DECIMALS = 12;3839const TRANSFER_AMOUNT = 2000000000000000000000000n;4041const FUNDING_AMOUNT = 3_500_000_0000_000_000n;4243const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;4445const USDT_ASSET_ID = 100;46const USDT_ASSET_METADATA_DECIMALS = 18;47const USDT_ASSET_METADATA_NAME = 'USDT';48const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';49const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;50const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;5152describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => {53 let alice: IKeyringPair;54 let bob: IKeyringPair;5556 let balanceStmnBefore: bigint;57 let balanceStmnAfter: bigint;5859 let balanceQuartzBefore: bigint;60 let balanceQuartzAfter: bigint;61 let balanceQuartzFinal: bigint;6263 let balanceBobBefore: bigint;64 let balanceBobAfter: bigint;65 let balanceBobFinal: bigint;6667 let balanceBobRelayTokenBefore: bigint;68 let balanceBobRelayTokenAfter: bigint;697071 before(async () => {72 await usingPlaygrounds(async (_helper, privateKey) => {73 alice = await privateKey('//Alice');74 bob = await privateKey('//Bob'); // sovereign account on Statemine(t) funds donor75 });7677 await usingRelayPlaygrounds(relayUrl, async (helper) => {78 // Fund accounts on Statemine(t)79 await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, alice.addressRaw, FUNDING_AMOUNT);80 await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, bob.addressRaw, FUNDING_AMOUNT);81 });8283 await usingStateminePlaygrounds(statemineUrl, async (helper) => {84 const sovereignFundingAmount = 3_500_000_000n;8586 await helper.assets.create(87 alice,88 USDT_ASSET_ID,89 alice.address,90 USDT_ASSET_METADATA_MINIMAL_BALANCE,91 );92 await helper.assets.setMetadata(93 alice,94 USDT_ASSET_ID,95 USDT_ASSET_METADATA_NAME,96 USDT_ASSET_METADATA_DESCRIPTION,97 USDT_ASSET_METADATA_DECIMALS,98 );99 await helper.assets.mint(100 alice,101 USDT_ASSET_ID,102 alice.address,103 USDT_ASSET_AMOUNT,104 );105106 // funding parachain sovereing account on Statemine(t).107 // The sovereign account should be created before any action108 // (the assets pallet on Statemine(t) check if the sovereign account exists)109 const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(QUARTZ_CHAIN);110 await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);111 });112113114 await usingPlaygrounds(async (helper) => {115 const location = {116 V1: {117 parents: 1,118 interior: {X3: [119 {120 Parachain: STATEMINE_CHAIN,121 },122 {123 PalletInstance: STATEMINE_PALLET_INSTANCE,124 },125 {126 GeneralIndex: USDT_ASSET_ID,127 },128 ]},129 },130 };131132 const metadata =133 {134 name: USDT_ASSET_ID,135 symbol: USDT_ASSET_METADATA_NAME,136 decimals: USDT_ASSET_METADATA_DECIMALS,137 minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,138 };139 await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);140 balanceQuartzBefore = await helper.balance.getSubstrate(alice.address);141 });142143144 // Providing the relay currency to the quartz sender account145 // (fee for USDT XCM are paid in relay tokens)146 await usingRelayPlaygrounds(relayUrl, async (helper) => {147 const destination = {148 V1: {149 parents: 0,150 interior: {X1: {151 Parachain: QUARTZ_CHAIN,152 },153 },154 }};155156 const beneficiary = {157 V1: {158 parents: 0,159 interior: {X1: {160 AccountId32: {161 network: 'Any',162 id: alice.addressRaw,163 },164 }},165 },166 };167168 const assets = {169 V1: [170 {171 id: {172 Concrete: {173 parents: 0,174 interior: 'Here',175 },176 },177 fun: {178 Fungible: TRANSFER_AMOUNT_RELAY,179 },180 },181 ],182 };183184 const feeAssetItem = 0;185186 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');187 });188189 });190191 itSub('Should connect and send USDT from Statemine to Quartz', async ({helper}) => {192 await usingStateminePlaygrounds(statemineUrl, async (helper) => {193 const dest = {194 V1: {195 parents: 1,196 interior: {X1: {197 Parachain: QUARTZ_CHAIN,198 },199 },200 }};201202 const beneficiary = {203 V1: {204 parents: 0,205 interior: {X1: {206 AccountId32: {207 network: 'Any',208 id: alice.addressRaw,209 },210 }},211 },212 };213214 const assets = {215 V1: [216 {217 id: {218 Concrete: {219 parents: 0,220 interior: {221 X2: [222 {223 PalletInstance: STATEMINE_PALLET_INSTANCE,224 },225 {226 GeneralIndex: USDT_ASSET_ID,227 },228 ]},229 },230 },231 fun: {232 Fungible: TRANSFER_AMOUNT,233 },234 },235 ],236 };237238 const feeAssetItem = 0;239240 balanceStmnBefore = await helper.balance.getSubstrate(alice.address);241 await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');242243 balanceStmnAfter = await helper.balance.getSubstrate(alice.address);244245 // common good parachain take commission in it native token246 console.log(247 '[Statemine -> Quartz] transaction fees on Statemine: %s WND',248 helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINE_DECIMALS),249 );250 expect(balanceStmnBefore > balanceStmnAfter).to.be.true;251252 });253254255 // ensure that asset has been delivered256 await helper.wait.newBlocks(3);257258 // expext collection id will be with id 1259 const free = await helper.ft.getBalance(1, {Substrate: alice.address});260261 balanceQuartzAfter = await helper.balance.getSubstrate(alice.address);262263 console.log(264 '[Statemine -> Quartz] transaction fees on Quartz: %s USDT',265 helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),266 );267 console.log(268 '[Statemine -> Quartz] transaction fees on Quartz: %s QTZ',269 helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzBefore),270 );271 // commission has not paid in USDT token272 expect(free).to.be.equal(TRANSFER_AMOUNT);273 // ... and parachain native token274 expect(balanceQuartzAfter == balanceQuartzBefore).to.be.true;275 });276277 itSub('Should connect and send USDT from Quartz to Statemine back', async ({helper}) => {278 const destination = {279 V1: {280 parents: 1,281 interior: {X2: [282 {283 Parachain: STATEMINE_CHAIN,284 },285 {286 AccountId32: {287 network: 'Any',288 id: alice.addressRaw,289 },290 },291 ]},292 },293 };294295 const relayFee = 400_000_000_000_000n;296 const currencies: [any, bigint][] = [297 [298 {299 ForeignAssetId: 0,300 },301 TRANSFER_AMOUNT,302 ],303 [304 {305 NativeAssetId: 'Parent',306 },307 relayFee,308 ],309 ];310311 const feeItem = 1;312313 await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');314315 // the commission has been paid in parachain native token316 balanceQuartzFinal = await helper.balance.getSubstrate(alice.address);317 console.log('[Quartz -> Statemine] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(balanceQuartzFinal - balanceQuartzAfter));318 expect(balanceQuartzAfter > balanceQuartzFinal).to.be.true;319320 await usingStateminePlaygrounds(statemineUrl, async (helper) => {321 await helper.wait.newBlocks(3);322323 // The USDT token never paid fees. Its amount not changed from begin value.324 // Also check that xcm transfer has been succeeded325 expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;326 });327 });328329 itSub('Should connect and send Relay token to Quartz', async ({helper}) => {330 balanceBobBefore = await helper.balance.getSubstrate(bob.address);331 balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});332333 await usingRelayPlaygrounds(relayUrl, async (helper) => {334 const destination = {335 V1: {336 parents: 0,337 interior: {X1: {338 Parachain: QUARTZ_CHAIN,339 },340 },341 }};342343 const beneficiary = {344 V1: {345 parents: 0,346 interior: {X1: {347 AccountId32: {348 network: 'Any',349 id: bob.addressRaw,350 },351 }},352 },353 };354355 const assets = {356 V1: [357 {358 id: {359 Concrete: {360 parents: 0,361 interior: 'Here',362 },363 },364 fun: {365 Fungible: TRANSFER_AMOUNT_RELAY,366 },367 },368 ],369 };370371 const feeAssetItem = 0;372373 await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');374 });375376 await helper.wait.newBlocks(3);377378 balanceBobAfter = await helper.balance.getSubstrate(bob.address);379 balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});380381 const wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;382 const wndDiffOnQuartz = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;383 console.log(384 '[Relay (Westend) -> Quartz] transaction fees: %s QTZ',385 helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),386 );387 console.log(388 '[Relay (Westend) -> Quartz] transaction fees: %s WND',389 helper.util.bigIntToDecimals(wndFeeOnQuartz, STATEMINE_DECIMALS),390 );391 console.log('[Relay (Westend) -> Quartz] actually delivered: %s WND', wndDiffOnQuartz);392 expect(wndFeeOnQuartz == 0n, 'No incoming WND fees should be taken').to.be.true;393 expect(balanceBobBefore == balanceBobAfter, 'No incoming QTZ fees should be taken').to.be.true;394 });395396 itSub('Should connect and send Relay token back', async ({helper}) => {397 let relayTokenBalanceBefore: bigint;398 let relayTokenBalanceAfter: bigint;399 await usingRelayPlaygrounds(relayUrl, async (helper) => {400 relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);401 });402403 const destination = {404 V1: {405 parents: 1,406 interior: {407 X1:{408 AccountId32: {409 network: 'Any',410 id: bob.addressRaw,411 },412 },413 },414 },415 };416417 const currencies: any = [418 [419 {420 NativeAssetId: 'Parent',421 },422 TRANSFER_AMOUNT_RELAY,423 ],424 ];425426 const feeItem = 0;427428 await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');429430 balanceBobFinal = await helper.balance.getSubstrate(bob.address);431 console.log('[Quartz -> Relay (Westend)] transaction fees: %s QTZ', helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));432433 await usingRelayPlaygrounds(relayUrl, async (helper) => {434 await helper.wait.newBlocks(10);435 relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);436437 const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;438 console.log('[Quartz -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));439 expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;440 });441 });442});443444describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => {445 let alice: IKeyringPair;446 let randomAccount: IKeyringPair;447448 let balanceQuartzTokenInit: bigint;449 let balanceQuartzTokenMiddle: bigint;450 let balanceQuartzTokenFinal: bigint;451 let balanceKaruraTokenInit: bigint;452 let balanceKaruraTokenMiddle: bigint;453 let balanceKaruraTokenFinal: bigint;454 let balanceQuartzForeignTokenInit: bigint;455 let balanceQuartzForeignTokenMiddle: bigint;456 let balanceQuartzForeignTokenFinal: bigint;457458 before(async () => {459 await usingPlaygrounds(async (helper, privateKey) => {460 alice = await privateKey('//Alice');461 [randomAccount] = await helper.arrange.createAccounts([0n], alice);462 });463464 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {465 const destination = {466 V1: {467 parents: 1,468 interior: {469 X1: {470 Parachain: QUARTZ_CHAIN,471 },472 },473 },474 };475476 const metadata = {477 name: 'QTZ',478 symbol: 'QTZ',479 decimals: 18,480 minimalBalance: 1n,481 };482483 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);484 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);485 balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);486 balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});487 });488489 await usingPlaygrounds(async (helper) => {490 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);491 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);492 });493 });494495 itSub('Should connect and send QTZ to Karura', async ({helper}) => {496 const destination = {497 V1: {498 parents: 1,499 interior: {500 X1: {501 Parachain: KARURA_CHAIN,502 },503 },504 },505 };506507 const beneficiary = {508 V1: {509 parents: 0,510 interior: {511 X1: {512 AccountId32: {513 network: 'Any',514 id: randomAccount.addressRaw,515 },516 },517 },518 },519 };520521 const assets = {522 V1: [523 {524 id: {525 Concrete: {526 parents: 0,527 interior: 'Here',528 },529 },530 fun: {531 Fungible: TRANSFER_AMOUNT,532 },533 },534 ],535 };536537 const feeAssetItem = 0;538539 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');540 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);541542 const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;543 expect(qtzFees > 0n, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;544 console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));545546 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {547 await helper.wait.newBlocks(3);548 balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});549 balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);550551 const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;552 const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;553554 console.log(555 '[Quartz -> Karura] transaction fees on Karura: %s KAR',556 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),557 );558 console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));559 expect(karFees == 0n).to.be.true;560 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;561 });562 });563564 itSub('Should connect to Karura and send QTZ back', async ({helper}) => {565 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {566 const destination = {567 V1: {568 parents: 1,569 interior: {570 X2: [571 {Parachain: QUARTZ_CHAIN},572 {573 AccountId32: {574 network: 'Any',575 id: randomAccount.addressRaw,576 },577 },578 ],579 },580 },581 };582583 const id = {584 ForeignAsset: 0,585 };586587 await helper.xTokens.transfer(randomAccount, id, TRANSFER_AMOUNT, destination, 'Unlimited');588 balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);589 balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);590591 const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;592 const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;593594 console.log(595 '[Karura -> Quartz] transaction fees on Karura: %s KAR',596 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),597 );598 console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));599600 expect(karFees > 0, 'Negative fees KAR, looks like nothing was transferred').to.be.true;601 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;602 });603604 await helper.wait.newBlocks(3);605606 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);607 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;608 expect(actuallyDelivered > 0).to.be.true;609610 console.log('[Karura -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));611612 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;613 console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));614 expect(qtzFees == 0n).to.be.true;615 });616});617618// These tests are relevant only when the foreign asset pallet is disabled619describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => {620 let alice: IKeyringPair;621622 before(async () => {623 await usingPlaygrounds(async (_helper, privateKey) => {624 alice = await privateKey('//Alice');625 });626 });627628 itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {629 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {630 const destination = {631 V1: {632 parents: 1,633 interior: {634 X2: [635 {Parachain: QUARTZ_CHAIN},636 {637 AccountId32: {638 network: 'Any',639 id: alice.addressRaw,640 },641 },642 ],643 },644 },645 };646647 const id = {648 Token: 'KAR',649 };650651 await helper.xTokens.transfer(alice, id, 100_000_000_000n, destination, 'Unlimited');652 });653654 const maxWaitBlocks = 3;655656 const xcmpQueueFailEvent = await helper.wait.event(maxWaitBlocks, 'xcmpQueue', 'Fail');657658 expect(659 xcmpQueueFailEvent != null,660 '[Karura] xcmpQueue.FailEvent event is expected',661 ).to.be.true;662663 const event = xcmpQueueFailEvent!.event;664 const outcome = event.data[1] as XcmV2TraitsError;665666 expect(667 outcome.isFailedToTransactAsset,668 '[Karura] The XCM error should be `FailedToTransactAsset`',669 ).to.be.true;670 });671});672673describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {674 // Quartz constants675 let quartzDonor: IKeyringPair;676 let quartzAssetLocation;677678 let randomAccountQuartz: IKeyringPair;679 let randomAccountMoonriver: IKeyringPair;680681 // Moonriver constants682 let assetId: string;683684 const councilVotingThreshold = 2;685 const technicalCommitteeThreshold = 2;686 const votingPeriod = 3;687 const delayPeriod = 0;688689 const quartzAssetMetadata = {690 name: 'xcQuartz',691 symbol: 'xcQTZ',692 decimals: 18,693 isFrozen: false,694 minimalBalance: 1n,695 };696697 let balanceQuartzTokenInit: bigint;698 let balanceQuartzTokenMiddle: bigint;699 let balanceQuartzTokenFinal: bigint;700 let balanceForeignQtzTokenInit: bigint;701 let balanceForeignQtzTokenMiddle: bigint;702 let balanceForeignQtzTokenFinal: bigint;703 let balanceMovrTokenInit: bigint;704 let balanceMovrTokenMiddle: bigint;705 let balanceMovrTokenFinal: bigint;706707 before(async () => {708 await usingPlaygrounds(async (helper, privateKey) => {709 quartzDonor = await privateKey('//Alice');710 [randomAccountQuartz] = await helper.arrange.createAccounts([0n], quartzDonor);711712 balanceForeignQtzTokenInit = 0n;713 });714715 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {716 const alithAccount = helper.account.alithAccount();717 const baltatharAccount = helper.account.baltatharAccount();718 const dorothyAccount = helper.account.dorothyAccount();719720 randomAccountMoonriver = helper.account.create();721722 // >>> Sponsoring Dorothy >>>723 console.log('Sponsoring Dorothy.......');724 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);725 console.log('Sponsoring Dorothy.......DONE');726 // <<< Sponsoring Dorothy <<<727728 quartzAssetLocation = {729 XCM: {730 parents: 1,731 interior: {X1: {Parachain: QUARTZ_CHAIN}},732 },733 };734 const existentialDeposit = 1n;735 const isSufficient = true;736 const unitsPerSecond = 1n;737 const numAssetsWeightHint = 0;738739 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({740 location: quartzAssetLocation,741 metadata: quartzAssetMetadata,742 existentialDeposit,743 isSufficient,744 unitsPerSecond,745 numAssetsWeightHint,746 });747 const proposalHash = blake2AsHex(encodedProposal);748749 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);750 console.log('Encoded length %d', encodedProposal.length);751 console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);752753 // >>> Note motion preimage >>>754 console.log('Note motion preimage.......');755 await helper.democracy.notePreimage(baltatharAccount, encodedProposal);756 console.log('Note motion preimage.......DONE');757 // <<< Note motion preimage <<<758759 // >>> Propose external motion through council >>>760 console.log('Propose external motion through council.......');761 const externalMotion = helper.democracy.externalProposeMajority({Legacy: proposalHash});762 const encodedMotion = externalMotion?.method.toHex() || '';763 const motionHash = blake2AsHex(encodedMotion);764 console.log('Motion hash is %s', motionHash);765766 await helper.collective.council.propose(baltatharAccount, councilVotingThreshold, externalMotion, externalMotion.encodedLength);767768 const councilProposalIdx = await helper.collective.council.proposalCount() - 1;769 await helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);770 await helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);771772 await helper.collective.council.close(773 dorothyAccount,774 motionHash,775 councilProposalIdx,776 {777 refTime: 1_000_000_000,778 proofSize: 1_000_000,779 },780 externalMotion.encodedLength,781 );782 console.log('Propose external motion through council.......DONE');783 // <<< Propose external motion through council <<<784785 // >>> Fast track proposal through technical committee >>>786 console.log('Fast track proposal through technical committee.......');787 const fastTrack = helper.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);788 const encodedFastTrack = fastTrack?.method.toHex() || '';789 const fastTrackHash = blake2AsHex(encodedFastTrack);790 console.log('FastTrack hash is %s', fastTrackHash);791792 await helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);793794 const techProposalIdx = await helper.collective.techCommittee.proposalCount() - 1;795 await helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);796 await helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);797798 await helper.collective.techCommittee.close(799 baltatharAccount,800 fastTrackHash,801 techProposalIdx,802 {803 refTime: 1_000_000_000,804 proofSize: 1_000_000,805 },806 fastTrack.encodedLength,807 );808 console.log('Fast track proposal through technical committee.......DONE');809 // <<< Fast track proposal through technical committee <<<810811 // >>> Referendum voting >>>812 console.log('Referendum voting.......');813 await helper.democracy.referendumVote(dorothyAccount, 0, {814 balance: 10_000_000_000_000_000_000n,815 vote: {aye: true, conviction: 1},816 });817 console.log('Referendum voting.......DONE');818 // <<< Referendum voting <<<819820 // >>> Acquire Quartz AssetId Info on Moonriver >>>821 console.log('Acquire Quartz AssetId Info on Moonriver.......');822823 // Wait for the democracy execute824 await helper.wait.newBlocks(5);825826 assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();827828 console.log('QTZ asset ID is %s', assetId);829 console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');830 // >>> Acquire Quartz AssetId Info on Moonriver >>>831832 // >>> Sponsoring random Account >>>833 console.log('Sponsoring random Account.......');834 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);835 console.log('Sponsoring random Account.......DONE');836 // <<< Sponsoring random Account <<<837838 balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);839 });840841 await usingPlaygrounds(async (helper) => {842 await helper.balance.transferToSubstrate(quartzDonor, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);843 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);844 });845 });846847 itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {848 const currencyId = {849 NativeAssetId: 'Here',850 };851 const dest = {852 V1: {853 parents: 1,854 interior: {855 X2: [856 {Parachain: MOONRIVER_CHAIN},857 {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},858 ],859 },860 },861 };862 const amount = TRANSFER_AMOUNT;863864 await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, 'Unlimited');865866 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);867 expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;868869 const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;870 console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));871 expect(transactionFees > 0, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;872873 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {874 await helper.wait.newBlocks(3);875876 balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);877878 const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;879 console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));880 expect(movrFees == 0n).to.be.true;881882 balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; // BigInt(qtzRandomAccountAsset['balance']);883 const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;884 console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));885 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;886 });887 });888889 itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {890 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {891 const asset = {892 V1: {893 id: {894 Concrete: {895 parents: 1,896 interior: {897 X1: {Parachain: QUARTZ_CHAIN},898 },899 },900 },901 fun: {902 Fungible: TRANSFER_AMOUNT,903 },904 },905 };906 const destination = {907 V1: {908 parents: 1,909 interior: {910 X2: [911 {Parachain: QUARTZ_CHAIN},912 {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},913 ],914 },915 },916 };917918 await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, 'Unlimited');919920 balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);921922 const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;923 console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));924 expect(movrFees > 0, 'Negative fees MOVR, looks like nothing was transferred').to.be.true;925926 const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);927928 expect(qtzRandomAccountAsset).to.be.null;929930 balanceForeignQtzTokenFinal = 0n;931932 const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;933 console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));934 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;935 });936937 await helper.wait.newBlocks(3);938939 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);940 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;941 expect(actuallyDelivered > 0).to.be.true;942943 console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));944945 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;946 console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));947 expect(qtzFees == 0n).to.be.true;948 });949});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 RELAY_DECIMALS = 12;36const STATEMINE_DECIMALS = 12;37const KARURA_DECIMALS = 12;3839const TRANSFER_AMOUNT = 2000000000000000000000000n;4041const FUNDING_AMOUNT = 3_500_000_0000_000_000n;4243const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;4445const USDT_ASSET_ID = 100;46const USDT_ASSET_METADATA_DECIMALS = 18;47const USDT_ASSET_METADATA_NAME = 'USDT';48const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';49const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;50const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;5152describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => {53 let alice: IKeyringPair;54 let bob: IKeyringPair;5556 let balanceStmnBefore: bigint;57 let balanceStmnAfter: bigint;5859 let balanceQuartzBefore: bigint;60 let balanceQuartzAfter: bigint;61 let balanceQuartzFinal: bigint;6263 let balanceBobBefore: bigint;64 let balanceBobAfter: bigint;65 let balanceBobFinal: bigint;6667 let balanceBobRelayTokenBefore: bigint;68 let balanceBobRelayTokenAfter: bigint;697071 before(async () => {72 await usingPlaygrounds(async (_helper, privateKey) => {73 alice = await privateKey('//Alice');74 bob = await privateKey('//Bob'); // sovereign account on Statemine(t) funds donor75 });7677 await usingRelayPlaygrounds(relayUrl, async (helper) => {78 // Fund accounts on Statemine(t)79 await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, alice.addressRaw, FUNDING_AMOUNT);80 await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, bob.addressRaw, FUNDING_AMOUNT);81 });8283 await usingStateminePlaygrounds(statemineUrl, async (helper) => {84 const sovereignFundingAmount = 3_500_000_000n;8586 await helper.assets.create(87 alice,88 USDT_ASSET_ID,89 alice.address,90 USDT_ASSET_METADATA_MINIMAL_BALANCE,91 );92 await helper.assets.setMetadata(93 alice,94 USDT_ASSET_ID,95 USDT_ASSET_METADATA_NAME,96 USDT_ASSET_METADATA_DESCRIPTION,97 USDT_ASSET_METADATA_DECIMALS,98 );99 await helper.assets.mint(100 alice,101 USDT_ASSET_ID,102 alice.address,103 USDT_ASSET_AMOUNT,104 );105106 // funding parachain sovereing account on Statemine(t).107 // The sovereign account should be created before any action108 // (the assets pallet on Statemine(t) check if the sovereign account exists)109 const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(QUARTZ_CHAIN);110 await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);111 });112113114 await usingPlaygrounds(async (helper) => {115 const location = {116 V1: {117 parents: 1,118 interior: {X3: [119 {120 Parachain: STATEMINE_CHAIN,121 },122 {123 PalletInstance: STATEMINE_PALLET_INSTANCE,124 },125 {126 GeneralIndex: USDT_ASSET_ID,127 },128 ]},129 },130 };131132 const metadata =133 {134 name: USDT_ASSET_ID,135 symbol: USDT_ASSET_METADATA_NAME,136 decimals: USDT_ASSET_METADATA_DECIMALS,137 minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,138 };139 await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);140 balanceQuartzBefore = await helper.balance.getSubstrate(alice.address);141 });142143144 // Providing the relay currency to the quartz sender account145 // (fee for USDT XCM are paid in relay tokens)146 await usingRelayPlaygrounds(relayUrl, async (helper) => {147 const destination = {148 V1: {149 parents: 0,150 interior: {X1: {151 Parachain: QUARTZ_CHAIN,152 },153 },154 }};155156 const beneficiary = {157 V1: {158 parents: 0,159 interior: {X1: {160 AccountId32: {161 network: 'Any',162 id: alice.addressRaw,163 },164 }},165 },166 };167168 const assets = {169 V1: [170 {171 id: {172 Concrete: {173 parents: 0,174 interior: 'Here',175 },176 },177 fun: {178 Fungible: TRANSFER_AMOUNT_RELAY,179 },180 },181 ],182 };183184 const feeAssetItem = 0;185186 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');187 });188189 });190191 itSub('Should connect and send USDT from Statemine to Quartz', async ({helper}) => {192 await usingStateminePlaygrounds(statemineUrl, async (helper) => {193 const dest = {194 V1: {195 parents: 1,196 interior: {X1: {197 Parachain: QUARTZ_CHAIN,198 },199 },200 }};201202 const beneficiary = {203 V1: {204 parents: 0,205 interior: {X1: {206 AccountId32: {207 network: 'Any',208 id: alice.addressRaw,209 },210 }},211 },212 };213214 const assets = {215 V1: [216 {217 id: {218 Concrete: {219 parents: 0,220 interior: {221 X2: [222 {223 PalletInstance: STATEMINE_PALLET_INSTANCE,224 },225 {226 GeneralIndex: USDT_ASSET_ID,227 },228 ]},229 },230 },231 fun: {232 Fungible: TRANSFER_AMOUNT,233 },234 },235 ],236 };237238 const feeAssetItem = 0;239240 balanceStmnBefore = await helper.balance.getSubstrate(alice.address);241 await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');242243 balanceStmnAfter = await helper.balance.getSubstrate(alice.address);244245 // common good parachain take commission in it native token246 console.log(247 '[Statemine -> Quartz] transaction fees on Statemine: %s WND',248 helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINE_DECIMALS),249 );250 expect(balanceStmnBefore > balanceStmnAfter).to.be.true;251252 });253254255 // ensure that asset has been delivered256 await helper.wait.newBlocks(3);257258 // expext collection id will be with id 1259 const free = await helper.ft.getBalance(1, {Substrate: alice.address});260261 balanceQuartzAfter = await helper.balance.getSubstrate(alice.address);262263 console.log(264 '[Statemine -> Quartz] transaction fees on Quartz: %s USDT',265 helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),266 );267 console.log(268 '[Statemine -> Quartz] transaction fees on Quartz: %s QTZ',269 helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzBefore),270 );271 // commission has not paid in USDT token272 expect(free).to.be.equal(TRANSFER_AMOUNT);273 // ... and parachain native token274 expect(balanceQuartzAfter == balanceQuartzBefore).to.be.true;275 });276277 itSub('Should connect and send USDT from Quartz to Statemine back', async ({helper}) => {278 const destination = {279 V1: {280 parents: 1,281 interior: {X2: [282 {283 Parachain: STATEMINE_CHAIN,284 },285 {286 AccountId32: {287 network: 'Any',288 id: alice.addressRaw,289 },290 },291 ]},292 },293 };294295 const relayFee = 400_000_000_000_000n;296 const currencies: [any, bigint][] = [297 [298 {299 ForeignAssetId: 0,300 },301 TRANSFER_AMOUNT,302 ],303 [304 {305 NativeAssetId: 'Parent',306 },307 relayFee,308 ],309 ];310311 const feeItem = 1;312313 await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');314315 // the commission has been paid in parachain native token316 balanceQuartzFinal = await helper.balance.getSubstrate(alice.address);317 console.log('[Quartz -> Statemine] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(balanceQuartzFinal - balanceQuartzAfter));318 expect(balanceQuartzAfter > balanceQuartzFinal).to.be.true;319320 await usingStateminePlaygrounds(statemineUrl, async (helper) => {321 await helper.wait.newBlocks(3);322323 // The USDT token never paid fees. Its amount not changed from begin value.324 // Also check that xcm transfer has been succeeded325 expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;326 });327 });328329 itSub('Should connect and send Relay token to Quartz', async ({helper}) => {330 balanceBobBefore = await helper.balance.getSubstrate(bob.address);331 balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});332333 await usingRelayPlaygrounds(relayUrl, async (helper) => {334 const destination = {335 V1: {336 parents: 0,337 interior: {X1: {338 Parachain: QUARTZ_CHAIN,339 },340 },341 }};342343 const beneficiary = {344 V1: {345 parents: 0,346 interior: {X1: {347 AccountId32: {348 network: 'Any',349 id: bob.addressRaw,350 },351 }},352 },353 };354355 const assets = {356 V1: [357 {358 id: {359 Concrete: {360 parents: 0,361 interior: 'Here',362 },363 },364 fun: {365 Fungible: TRANSFER_AMOUNT_RELAY,366 },367 },368 ],369 };370371 const feeAssetItem = 0;372373 await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');374 });375376 await helper.wait.newBlocks(3);377378 balanceBobAfter = await helper.balance.getSubstrate(bob.address);379 balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});380381 const wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;382 const wndDiffOnQuartz = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;383 console.log(384 '[Relay (Westend) -> Quartz] transaction fees: %s QTZ',385 helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),386 );387 console.log(388 '[Relay (Westend) -> Quartz] transaction fees: %s WND',389 helper.util.bigIntToDecimals(wndFeeOnQuartz, STATEMINE_DECIMALS),390 );391 console.log('[Relay (Westend) -> Quartz] actually delivered: %s WND', wndDiffOnQuartz);392 expect(wndFeeOnQuartz == 0n, 'No incoming WND fees should be taken').to.be.true;393 expect(balanceBobBefore == balanceBobAfter, 'No incoming QTZ fees should be taken').to.be.true;394 });395396 itSub('Should connect and send Relay token back', async ({helper}) => {397 let relayTokenBalanceBefore: bigint;398 let relayTokenBalanceAfter: bigint;399 await usingRelayPlaygrounds(relayUrl, async (helper) => {400 relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);401 });402403 const destination = {404 V1: {405 parents: 1,406 interior: {407 X1:{408 AccountId32: {409 network: 'Any',410 id: bob.addressRaw,411 },412 },413 },414 },415 };416417 const currencies: any = [418 [419 {420 NativeAssetId: 'Parent',421 },422 TRANSFER_AMOUNT_RELAY,423 ],424 ];425426 const feeItem = 0;427428 await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');429430 balanceBobFinal = await helper.balance.getSubstrate(bob.address);431 console.log('[Quartz -> Relay (Westend)] transaction fees: %s QTZ', helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));432433 await usingRelayPlaygrounds(relayUrl, async (helper) => {434 await helper.wait.newBlocks(10);435 relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);436437 const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;438 console.log('[Quartz -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));439 expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;440 });441 });442});443444describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => {445 let alice: IKeyringPair;446 let randomAccount: IKeyringPair;447448 let balanceQuartzTokenInit: bigint;449 let balanceQuartzTokenMiddle: bigint;450 let balanceQuartzTokenFinal: bigint;451 let balanceKaruraTokenInit: bigint;452 let balanceKaruraTokenMiddle: bigint;453 let balanceKaruraTokenFinal: bigint;454 let balanceQuartzForeignTokenInit: bigint;455 let balanceQuartzForeignTokenMiddle: bigint;456 let balanceQuartzForeignTokenFinal: bigint;457458 // computed by a test transfer from prod Quartz to prod Karura.459 // 2 QTZ sent https://quartz.subscan.io/xcm_message/kusama-f60d821b049f8835a3005ce7102285006f5b61e9460 // 1.919176000000000000 QTZ received (you can check Karura's chain state in the corresponding block)461 const expectedKaruraIncomeFee = 2000000000000000000n - 1919176000000000000n;462463 const KARURA_BACKWARD_TRANSFER_AMOUNT = TRANSFER_AMOUNT - expectedKaruraIncomeFee;464465 before(async () => {466 await usingPlaygrounds(async (helper, privateKey) => {467 alice = await privateKey('//Alice');468 [randomAccount] = await helper.arrange.createAccounts([0n], alice);469 });470471 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {472 const destination = {473 V1: {474 parents: 1,475 interior: {476 X1: {477 Parachain: QUARTZ_CHAIN,478 },479 },480 },481 };482483 const metadata = {484 name: 'Quartz',485 symbol: 'QTZ',486 decimals: 18,487 minimalBalance: 1000000000000000000n,488 };489490 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);491 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);492 balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);493 balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});494 });495496 await usingPlaygrounds(async (helper) => {497 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);498 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);499 });500 });501502 itSub('Should connect and send QTZ to Karura', async ({helper}) => {503 const destination = {504 V1: {505 parents: 1,506 interior: {507 X1: {508 Parachain: KARURA_CHAIN,509 },510 },511 },512 };513514 const beneficiary = {515 V1: {516 parents: 0,517 interior: {518 X1: {519 AccountId32: {520 network: 'Any',521 id: randomAccount.addressRaw,522 },523 },524 },525 },526 };527528 const assets = {529 V1: [530 {531 id: {532 Concrete: {533 parents: 0,534 interior: 'Here',535 },536 },537 fun: {538 Fungible: TRANSFER_AMOUNT,539 },540 },541 ],542 };543544 const feeAssetItem = 0;545546 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');547 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);548549 const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;550 expect(qtzFees > 0n, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;551 console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));552553 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {554 await helper.wait.newBlocks(3);555556 balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});557 balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);558559 const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;560 const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;561 const karUnqFees = TRANSFER_AMOUNT - qtzIncomeTransfer;562563 console.log(564 '[Quartz -> Karura] transaction fees on Karura: %s KAR',565 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),566 );567 console.log(568 '[Quartz -> Karura] transaction fees on Karura: %s QTZ',569 helper.util.bigIntToDecimals(karUnqFees),570 );571 console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));572 expect(karFees == 0n).to.be.true;573 expect(574 karUnqFees == expectedKaruraIncomeFee,575 'Karura took different income fee, check the Karura foreign asset config',576 ).to.be.true;577 });578 });579580 itSub('Should connect to Karura and send QTZ back', async ({helper}) => {581 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {582 const destination = {583 V1: {584 parents: 1,585 interior: {586 X2: [587 {Parachain: QUARTZ_CHAIN},588 {589 AccountId32: {590 network: 'Any',591 id: randomAccount.addressRaw,592 },593 },594 ],595 },596 },597 };598599 const id = {600 ForeignAsset: 0,601 };602603 await helper.xTokens.transfer(randomAccount, id, KARURA_BACKWARD_TRANSFER_AMOUNT, destination, 'Unlimited');604 balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);605 balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);606607 const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;608 const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;609610 console.log(611 '[Karura -> Quartz] transaction fees on Karura: %s KAR',612 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),613 );614 console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));615616 expect(karFees > 0, 'Negative fees KAR, looks like nothing was transferred').to.be.true;617 expect(qtzOutcomeTransfer == KARURA_BACKWARD_TRANSFER_AMOUNT).to.be.true;618 });619620 await helper.wait.newBlocks(3);621622 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);623 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;624 expect(actuallyDelivered > 0).to.be.true;625626 console.log('[Karura -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));627628 const qtzFees = KARURA_BACKWARD_TRANSFER_AMOUNT - actuallyDelivered;629 console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));630 expect(qtzFees == 0n).to.be.true;631 });632});633634// These tests are relevant only when the foreign asset pallet is disabled635describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => {636 let alice: IKeyringPair;637638 before(async () => {639 await usingPlaygrounds(async (_helper, privateKey) => {640 alice = await privateKey('//Alice');641 });642 });643644 itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {645 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {646 const destination = {647 V1: {648 parents: 1,649 interior: {650 X2: [651 {Parachain: QUARTZ_CHAIN},652 {653 AccountId32: {654 network: 'Any',655 id: alice.addressRaw,656 },657 },658 ],659 },660 },661 };662663 const id = {664 Token: 'KAR',665 };666667 await helper.xTokens.transfer(alice, id, 100_000_000_000n, destination, 'Unlimited');668 });669670 const maxWaitBlocks = 3;671672 const xcmpQueueFailEvent = await helper.wait.event(maxWaitBlocks, 'xcmpQueue', 'Fail');673674 expect(675 xcmpQueueFailEvent != null,676 '[Karura] xcmpQueue.FailEvent event is expected',677 ).to.be.true;678679 const event = xcmpQueueFailEvent!.event;680 const outcome = event.data[1] as XcmV2TraitsError;681682 expect(683 outcome.isFailedToTransactAsset,684 '[Karura] The XCM error should be `FailedToTransactAsset`',685 ).to.be.true;686 });687});688689describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {690 // Quartz constants691 let quartzDonor: IKeyringPair;692 let quartzAssetLocation;693694 let randomAccountQuartz: IKeyringPair;695 let randomAccountMoonriver: IKeyringPair;696697 // Moonriver constants698 let assetId: string;699700 const councilVotingThreshold = 2;701 const technicalCommitteeThreshold = 2;702 const votingPeriod = 3;703 const delayPeriod = 0;704705 const quartzAssetMetadata = {706 name: 'xcQuartz',707 symbol: 'xcQTZ',708 decimals: 18,709 isFrozen: false,710 minimalBalance: 1n,711 };712713 let balanceQuartzTokenInit: bigint;714 let balanceQuartzTokenMiddle: bigint;715 let balanceQuartzTokenFinal: bigint;716 let balanceForeignQtzTokenInit: bigint;717 let balanceForeignQtzTokenMiddle: bigint;718 let balanceForeignQtzTokenFinal: bigint;719 let balanceMovrTokenInit: bigint;720 let balanceMovrTokenMiddle: bigint;721 let balanceMovrTokenFinal: bigint;722723 before(async () => {724 await usingPlaygrounds(async (helper, privateKey) => {725 quartzDonor = await privateKey('//Alice');726 [randomAccountQuartz] = await helper.arrange.createAccounts([0n], quartzDonor);727728 balanceForeignQtzTokenInit = 0n;729 });730731 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {732 const alithAccount = helper.account.alithAccount();733 const baltatharAccount = helper.account.baltatharAccount();734 const dorothyAccount = helper.account.dorothyAccount();735736 randomAccountMoonriver = helper.account.create();737738 // >>> Sponsoring Dorothy >>>739 console.log('Sponsoring Dorothy.......');740 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);741 console.log('Sponsoring Dorothy.......DONE');742 // <<< Sponsoring Dorothy <<<743744 quartzAssetLocation = {745 XCM: {746 parents: 1,747 interior: {X1: {Parachain: QUARTZ_CHAIN}},748 },749 };750 const existentialDeposit = 1n;751 const isSufficient = true;752 const unitsPerSecond = 1n;753 const numAssetsWeightHint = 0;754755 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({756 location: quartzAssetLocation,757 metadata: quartzAssetMetadata,758 existentialDeposit,759 isSufficient,760 unitsPerSecond,761 numAssetsWeightHint,762 });763 const proposalHash = blake2AsHex(encodedProposal);764765 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);766 console.log('Encoded length %d', encodedProposal.length);767 console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);768769 // >>> Note motion preimage >>>770 console.log('Note motion preimage.......');771 await helper.democracy.notePreimage(baltatharAccount, encodedProposal);772 console.log('Note motion preimage.......DONE');773 // <<< Note motion preimage <<<774775 // >>> Propose external motion through council >>>776 console.log('Propose external motion through council.......');777 const externalMotion = helper.democracy.externalProposeMajority({Legacy: proposalHash});778 const encodedMotion = externalMotion?.method.toHex() || '';779 const motionHash = blake2AsHex(encodedMotion);780 console.log('Motion hash is %s', motionHash);781782 await helper.collective.council.propose(baltatharAccount, councilVotingThreshold, externalMotion, externalMotion.encodedLength);783784 const councilProposalIdx = await helper.collective.council.proposalCount() - 1;785 await helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);786 await helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);787788 await helper.collective.council.close(789 dorothyAccount,790 motionHash,791 councilProposalIdx,792 {793 refTime: 1_000_000_000,794 proofSize: 1_000_000,795 },796 externalMotion.encodedLength,797 );798 console.log('Propose external motion through council.......DONE');799 // <<< Propose external motion through council <<<800801 // >>> Fast track proposal through technical committee >>>802 console.log('Fast track proposal through technical committee.......');803 const fastTrack = helper.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);804 const encodedFastTrack = fastTrack?.method.toHex() || '';805 const fastTrackHash = blake2AsHex(encodedFastTrack);806 console.log('FastTrack hash is %s', fastTrackHash);807808 await helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);809810 const techProposalIdx = await helper.collective.techCommittee.proposalCount() - 1;811 await helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);812 await helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);813814 await helper.collective.techCommittee.close(815 baltatharAccount,816 fastTrackHash,817 techProposalIdx,818 {819 refTime: 1_000_000_000,820 proofSize: 1_000_000,821 },822 fastTrack.encodedLength,823 );824 console.log('Fast track proposal through technical committee.......DONE');825 // <<< Fast track proposal through technical committee <<<826827 // >>> Referendum voting >>>828 console.log('Referendum voting.......');829 await helper.democracy.referendumVote(dorothyAccount, 0, {830 balance: 10_000_000_000_000_000_000n,831 vote: {aye: true, conviction: 1},832 });833 console.log('Referendum voting.......DONE');834 // <<< Referendum voting <<<835836 // >>> Acquire Quartz AssetId Info on Moonriver >>>837 console.log('Acquire Quartz AssetId Info on Moonriver.......');838839 // Wait for the democracy execute840 await helper.wait.newBlocks(5);841842 assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();843844 console.log('QTZ asset ID is %s', assetId);845 console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');846 // >>> Acquire Quartz AssetId Info on Moonriver >>>847848 // >>> Sponsoring random Account >>>849 console.log('Sponsoring random Account.......');850 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);851 console.log('Sponsoring random Account.......DONE');852 // <<< Sponsoring random Account <<<853854 balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);855 });856857 await usingPlaygrounds(async (helper) => {858 await helper.balance.transferToSubstrate(quartzDonor, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);859 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);860 });861 });862863 itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {864 const currencyId = {865 NativeAssetId: 'Here',866 };867 const dest = {868 V1: {869 parents: 1,870 interior: {871 X2: [872 {Parachain: MOONRIVER_CHAIN},873 {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},874 ],875 },876 },877 };878 const amount = TRANSFER_AMOUNT;879880 await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, 'Unlimited');881882 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);883 expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;884885 const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;886 console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));887 expect(transactionFees > 0, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;888889 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {890 await helper.wait.newBlocks(3);891892 balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);893894 const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;895 console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));896 expect(movrFees == 0n).to.be.true;897898 balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; // BigInt(qtzRandomAccountAsset['balance']);899 const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;900 console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));901 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;902 });903 });904905 itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {906 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {907 const asset = {908 V1: {909 id: {910 Concrete: {911 parents: 1,912 interior: {913 X1: {Parachain: QUARTZ_CHAIN},914 },915 },916 },917 fun: {918 Fungible: TRANSFER_AMOUNT,919 },920 },921 };922 const destination = {923 V1: {924 parents: 1,925 interior: {926 X2: [927 {Parachain: QUARTZ_CHAIN},928 {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},929 ],930 },931 },932 };933934 await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, 'Unlimited');935936 balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);937938 const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;939 console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));940 expect(movrFees > 0, 'Negative fees MOVR, looks like nothing was transferred').to.be.true;941942 const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);943944 expect(qtzRandomAccountAsset).to.be.null;945946 balanceForeignQtzTokenFinal = 0n;947948 const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;949 console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));950 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;951 });952953 await helper.wait.newBlocks(3);954955 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);956 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;957 expect(actuallyDelivered > 0).to.be.true;958959 console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));960961 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;962 console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));963 expect(qtzFees == 0n).to.be.true;964 });965});tests/src/xcm/xcmUnique.test.tsdiffbeforeafterboth--- a/tests/src/xcm/xcmUnique.test.ts
+++ b/tests/src/xcm/xcmUnique.test.ts
@@ -455,6 +455,13 @@
let balanceUniqueForeignTokenMiddle: bigint;
let balanceUniqueForeignTokenFinal: bigint;
+ // computed by a test transfer from prod Unique to prod Acala.
+ // 2 UNQ sent https://unique.subscan.io/xcm_message/polkadot-bad0b68847e2398af25d482e9ee6f9c1f9ec2a48
+ // 1.898970000000000000 UNQ received (you can check Acala's chain state in the corresponding block)
+ const expectedAcalaIncomeFee = 2000000000000000000n - 1898970000000000000n;
+
+ const ACALA_BACKWARD_TRANSFER_AMOUNT = TRANSFER_AMOUNT - expectedAcalaIncomeFee;
+
before(async () => {
await usingPlaygrounds(async (helper, privateKey) => {
alice = await privateKey('//Alice');
@@ -474,10 +481,10 @@
};
const metadata = {
- name: 'UNQ',
+ name: 'Unique Network',
symbol: 'UNQ',
decimals: 18,
- minimalBalance: 1n,
+ minimalBalance: 1250000000000000000n,
};
await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);
@@ -552,14 +559,22 @@
const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle;
const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit;
+ const acaUnqFees = TRANSFER_AMOUNT - unqIncomeTransfer;
console.log(
'[Unique -> Acala] transaction fees on Acala: %s ACA',
helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),
);
+ console.log(
+ '[Unique -> Acala] transaction fees on Acala: %s UNQ',
+ helper.util.bigIntToDecimals(acaUnqFees),
+ );
console.log('[Unique -> Acala] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));
expect(acaFees == 0n).to.be.true;
- expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;
+ expect(
+ acaUnqFees == expectedAcalaIncomeFee,
+ 'Acala took different income fee, check the Acala foreign asset config',
+ ).to.be.true;
});
});
@@ -586,7 +601,7 @@
ForeignAsset: 0,
};
- await helper.xTokens.transfer(randomAccount, id, TRANSFER_AMOUNT, destination, 'Unlimited');
+ await helper.xTokens.transfer(randomAccount, id, ACALA_BACKWARD_TRANSFER_AMOUNT, destination, 'Unlimited');
balanceAcalaTokenFinal = await helper.balance.getSubstrate(randomAccount.address);
balanceUniqueForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);
@@ -600,7 +615,7 @@
console.log('[Acala -> Unique] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));
expect(acaFees > 0, 'Negative fees ACA, looks like nothing was transferred').to.be.true;
- expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;
+ expect(unqOutcomeTransfer == ACALA_BACKWARD_TRANSFER_AMOUNT).to.be.true;
});
await helper.wait.newBlocks(3);
@@ -611,7 +626,7 @@
console.log('[Acala -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));
- const unqFees = TRANSFER_AMOUNT - actuallyDelivered;
+ const unqFees = ACALA_BACKWARD_TRANSFER_AMOUNT - actuallyDelivered;
console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));
expect(unqFees == 0n).to.be.true;
});