1234567891011121314151617import {IKeyringPair} from '@polkadot/types/types';18import {blake2AsHex} from '@polkadot/util-crypto';19import config from '../config';20import {XcmV2TraitsOutcome, XcmV2TraitsError} from '../interfaces';21import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds} from '../util';2223const QUARTZ_CHAIN = 2095;24const KARURA_CHAIN = 2000;25const MOONRIVER_CHAIN = 2023;2627const relayUrl = config.relayUrl;28const karuraUrl = config.karuraUrl;29const moonriverUrl = config.moonriverUrl;3031const KARURA_DECIMALS = 12;3233const TRANSFER_AMOUNT = 2000000000000000000000000n;3435describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => {36 let alice: IKeyringPair;37 let randomAccount: IKeyringPair;3839 let balanceQuartzTokenInit: bigint;40 let balanceQuartzTokenMiddle: bigint;41 let balanceQuartzTokenFinal: bigint;42 let balanceKaruraTokenInit: bigint;43 let balanceKaruraTokenMiddle: bigint;44 let balanceKaruraTokenFinal: bigint;45 let balanceQuartzForeignTokenInit: bigint;46 let balanceQuartzForeignTokenMiddle: bigint;47 let balanceQuartzForeignTokenFinal: bigint;4849 before(async () => {50 await usingPlaygrounds(async (helper, privateKey) => {51 alice = await privateKey('//Alice');52 [randomAccount] = await helper.arrange.createAccounts([0n], alice);53 });5455 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {56 const destination = {57 V0: {58 X2: [59 'Parent',60 {61 Parachain: QUARTZ_CHAIN,62 },63 ],64 },65 };6667 const metadata = {68 name: 'QTZ',69 symbol: 'QTZ',70 decimals: 18,71 minimalBalance: 1n,72 };7374 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);75 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);76 balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);77 balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});78 });7980 await usingPlaygrounds(async (helper) => {81 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);82 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);83 });84 });8586 itSub('Should connect and send QTZ to Karura', async ({helper}) => {87 const destination = {88 V0: {89 X2: [90 'Parent',91 {92 Parachain: KARURA_CHAIN,93 },94 ],95 },96 };9798 const beneficiary = {99 V0: {100 X1: {101 AccountId32: {102 network: 'Any',103 id: randomAccount.addressRaw,104 },105 },106 },107 };108109 const assets = {110 V1: [111 {112 id: {113 Concrete: {114 parents: 0,115 interior: 'Here',116 },117 },118 fun: {119 Fungible: TRANSFER_AMOUNT,120 },121 },122 ],123 };124125 const feeAssetItem = 0;126 const weightLimit = 5000000000;127128 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, weightLimit);129 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);130131 const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;132 console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));133 expect(qtzFees > 0n).to.be.true;134135 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {136 await helper.wait.newBlocks(3);137 balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});138 balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);139140 const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;141 const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;142143 console.log(144 '[Quartz -> Karura] transaction fees on Karura: %s KAR',145 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),146 );147 console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));148 expect(karFees == 0n).to.be.true;149 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;150 });151 });152153 itSub('Should connect to Karura and send QTZ back', async ({helper}) => {154 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {155 const destination = {156 V1: {157 parents: 1,158 interior: {159 X2: [160 {Parachain: QUARTZ_CHAIN},161 {162 AccountId32: {163 network: 'Any',164 id: randomAccount.addressRaw,165 },166 },167 ],168 },169 },170 };171172 const id = {173 ForeignAsset: 0,174 };175176 const destWeight = 50000000;177178 await helper.xTokens.transfer(randomAccount, id, TRANSFER_AMOUNT, destination, destWeight);179 balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);180 balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);181182 const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;183 const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;184185 console.log(186 '[Karura -> Quartz] transaction fees on Karura: %s KAR',187 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),188 );189 console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));190191 expect(karFees > 0).to.be.true;192 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;193 });194195 await helper.wait.newBlocks(3);196197 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);198 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;199 expect(actuallyDelivered > 0).to.be.true;200201 console.log('[Karura -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));202203 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;204 console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));205 expect(qtzFees == 0n).to.be.true;206 });207});208209210describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => {211 let alice: IKeyringPair;212213 before(async () => {214 await usingPlaygrounds(async (_helper, privateKey) => {215 alice = await privateKey('//Alice');216 });217 });218219 itSub('Quartz rejects tokens from the Relay', async ({helper}) => {220 await usingRelayPlaygrounds(relayUrl, async (helper) => {221 const destination = {222 V1: {223 parents: 0,224 interior: {X1: {225 Parachain: QUARTZ_CHAIN,226 },227 },228 }};229230 const beneficiary = {231 V1: {232 parents: 0,233 interior: {X1: {234 AccountId32: {235 network: 'Any',236 id: alice.addressRaw,237 },238 }},239 },240 };241242 const assets = {243 V1: [244 {245 id: {246 Concrete: {247 parents: 0,248 interior: 'Here',249 },250 },251 fun: {252 Fungible: 50_000_000_000_000_000n,253 },254 },255 ],256 };257258 const feeAssetItem = 0;259 const weightLimit = 5_000_000_000;260261 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, weightLimit);262 });263264 const maxWaitBlocks = 3;265266 const dmpQueueExecutedDownward = await helper.wait.event(maxWaitBlocks, 'dmpQueue', 'ExecutedDownward');267268 expect(269 dmpQueueExecutedDownward != null,270 '[Relay] dmpQueue.ExecutedDownward event is expected',271 ).to.be.true;272273 const event = dmpQueueExecutedDownward!.event;274 const outcome = event.data[1] as XcmV2TraitsOutcome;275276 expect(277 outcome.isIncomplete,278 '[Relay] The outcome of the XCM should be `Incomplete`',279 ).to.be.true;280281 const incomplete = outcome.asIncomplete;282 expect(283 incomplete[1].toString() == 'AssetNotFound',284 '[Relay] The XCM error should be `AssetNotFound`',285 ).to.be.true;286 });287288 itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {289 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {290 const destination = {291 V1: {292 parents: 1,293 interior: {294 X2: [295 {Parachain: QUARTZ_CHAIN},296 {297 AccountId32: {298 network: 'Any',299 id: alice.addressRaw,300 },301 },302 ],303 },304 },305 };306307 const id = {308 Token: 'KAR',309 };310311 const destWeight = 50000000;312313 await helper.xTokens.transfer(alice, id, 100_000_000_000n, destination, destWeight);314 });315316 const maxWaitBlocks = 3;317318 const xcmpQueueFailEvent = await helper.wait.event(maxWaitBlocks, 'xcmpQueue', 'Fail');319320 expect(321 xcmpQueueFailEvent != null,322 '[Karura] xcmpQueue.FailEvent event is expected',323 ).to.be.true;324325 const event = xcmpQueueFailEvent!.event;326 const outcome = event.data[1] as XcmV2TraitsError;327328 expect(329 outcome.isUntrustedReserveLocation,330 '[Karura] The XCM error should be `UntrustedReserveLocation`',331 ).to.be.true;332 });333});334335describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {336 337 let quartzDonor: IKeyringPair;338 let quartzAssetLocation;339340 let randomAccountQuartz: IKeyringPair;341 let randomAccountMoonriver: IKeyringPair;342343 344 let assetId: string;345346 const councilVotingThreshold = 2;347 const technicalCommitteeThreshold = 2;348 const votingPeriod = 3;349 const delayPeriod = 0;350351 const quartzAssetMetadata = {352 name: 'xcQuartz',353 symbol: 'xcQTZ',354 decimals: 18,355 isFrozen: false,356 minimalBalance: 1n,357 };358359 let balanceQuartzTokenInit: bigint;360 let balanceQuartzTokenMiddle: bigint;361 let balanceQuartzTokenFinal: bigint;362 let balanceForeignQtzTokenInit: bigint;363 let balanceForeignQtzTokenMiddle: bigint;364 let balanceForeignQtzTokenFinal: bigint;365 let balanceMovrTokenInit: bigint;366 let balanceMovrTokenMiddle: bigint;367 let balanceMovrTokenFinal: bigint;368369 before(async () => {370 await usingPlaygrounds(async (helper, privateKey) => {371 quartzDonor = await privateKey('//Alice');372 [randomAccountQuartz] = await helper.arrange.createAccounts([0n], quartzDonor);373374 balanceForeignQtzTokenInit = 0n;375 });376377 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {378 const alithAccount = helper.account.alithAccount();379 const baltatharAccount = helper.account.baltatharAccount();380 const dorothyAccount = helper.account.dorothyAccount();381382 randomAccountMoonriver = helper.account.create();383384 385 console.log('Sponsoring Dorothy.......');386 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);387 console.log('Sponsoring Dorothy.......DONE');388 389390 quartzAssetLocation = {391 XCM: {392 parents: 1,393 interior: {X1: {Parachain: QUARTZ_CHAIN}},394 },395 };396 const existentialDeposit = 1n;397 const isSufficient = true;398 const unitsPerSecond = 1n;399 const numAssetsWeightHint = 0;400401 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({402 location: quartzAssetLocation,403 metadata: quartzAssetMetadata,404 existentialDeposit,405 isSufficient,406 unitsPerSecond,407 numAssetsWeightHint,408 });409 const proposalHash = blake2AsHex(encodedProposal);410411 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);412 console.log('Encoded length %d', encodedProposal.length);413 console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);414415 416 console.log('Note motion preimage.......');417 await helper.democracy.notePreimage(baltatharAccount, encodedProposal);418 console.log('Note motion preimage.......DONE');419 420421 422 console.log('Propose external motion through council.......');423 const externalMotion = helper.democracy.externalProposeMajority(proposalHash);424 const encodedMotion = externalMotion?.method.toHex() || '';425 const motionHash = blake2AsHex(encodedMotion);426 console.log('Motion hash is %s', motionHash);427428 await helper.collective.council.propose(baltatharAccount, councilVotingThreshold, externalMotion, externalMotion.encodedLength);429430 const councilProposalIdx = await helper.collective.council.proposalCount() - 1;431 await helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);432 await helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);433434 await helper.collective.council.close(dorothyAccount, motionHash, councilProposalIdx, 1_000_000_000, externalMotion.encodedLength);435 console.log('Propose external motion through council.......DONE');436 437438 439 console.log('Fast track proposal through technical committee.......');440 const fastTrack = helper.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);441 const encodedFastTrack = fastTrack?.method.toHex() || '';442 const fastTrackHash = blake2AsHex(encodedFastTrack);443 console.log('FastTrack hash is %s', fastTrackHash);444445 await helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);446447 const techProposalIdx = await helper.collective.techCommittee.proposalCount() - 1;448 await helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);449 await helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);450451 await helper.collective.techCommittee.close(baltatharAccount, fastTrackHash, techProposalIdx, 1_000_000_000, fastTrack.encodedLength);452 console.log('Fast track proposal through technical committee.......DONE');453 454455 456 console.log('Referendum voting.......');457 await helper.democracy.referendumVote(dorothyAccount, 0, {458 balance: 10_000_000_000_000_000_000n,459 vote: {aye: true, conviction: 1},460 });461 console.log('Referendum voting.......DONE');462 463464 465 console.log('Acquire Quartz AssetId Info on Moonriver.......');466467 468 await helper.wait.newBlocks(5);469470 assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();471472 console.log('QTZ asset ID is %s', assetId);473 console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');474 475476 477 console.log('Sponsoring random Account.......');478 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);479 console.log('Sponsoring random Account.......DONE');480 481482 balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);483 });484485 await usingPlaygrounds(async (helper) => {486 await helper.balance.transferToSubstrate(quartzDonor, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);487 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);488 });489 });490491 itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {492 const currencyId = {493 NativeAssetId: 'Here',494 };495 const dest = {496 V1: {497 parents: 1,498 interior: {499 X2: [500 {Parachain: MOONRIVER_CHAIN},501 {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},502 ],503 },504 },505 };506 const amount = TRANSFER_AMOUNT;507 const destWeight = 850000000;508509 await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, destWeight);510511 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);512 expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;513514 const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;515 console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));516 expect(transactionFees > 0).to.be.true;517518 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {519 await helper.wait.newBlocks(3);520521 balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);522523 const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;524 console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));525 expect(movrFees == 0n).to.be.true;526527 balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; 528 const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;529 console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));530 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;531 });532 });533534 itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {535 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {536 const asset = {537 V1: {538 id: {539 Concrete: {540 parents: 1,541 interior: {542 X1: {Parachain: QUARTZ_CHAIN},543 },544 },545 },546 fun: {547 Fungible: TRANSFER_AMOUNT,548 },549 },550 };551 const destination = {552 V1: {553 parents: 1,554 interior: {555 X2: [556 {Parachain: QUARTZ_CHAIN},557 {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},558 ],559 },560 },561 };562 const destWeight = 50000000;563564 await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, destWeight);565566 balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);567568 const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;569 console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));570 expect(movrFees > 0).to.be.true;571572 const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);573574 expect(qtzRandomAccountAsset).to.be.null;575576 balanceForeignQtzTokenFinal = 0n;577578 const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;579 console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));580 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;581 });582583 await helper.wait.newBlocks(3);584585 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);586 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;587 expect(actuallyDelivered > 0).to.be.true;588589 console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));590591 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;592 console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));593 expect(qtzFees == 0n).to.be.true;594 });595});