1234567891011121314151617import {IKeyringPair} from '@polkadot/types/types';18import {blake2AsHex} from '@polkadot/util-crypto';19import {XcmV2TraitsOutcome, XcmV2TraitsError} from '../interfaces';20import {itSub, expect, describeXcm, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds} from '../util/playgrounds';2122const QUARTZ_CHAIN = 2095;23const KARURA_CHAIN = 2000;24const MOONRIVER_CHAIN = 2023;2526const RELAY_PORT = 9844;27const KARURA_PORT = 9946;28const MOONRIVER_PORT = 9947;2930const relayUrl = 'ws://127.0.0.1:' + RELAY_PORT;31const karuraUrl = 'ws://127.0.0.1:' + KARURA_PORT;32const moonriverUrl = 'ws://127.0.0.1:' + MOONRIVER_PORT;3334const KARURA_DECIMALS = 12;3536const TRANSFER_AMOUNT = 2000000000000000000000000n;3738describeXcm('[XCM] Integration test: Exchanging tokens with Karura', () => {39 let alice: IKeyringPair;40 let randomAccount: IKeyringPair;4142 let balanceQuartzTokenInit: bigint;43 let balanceQuartzTokenMiddle: bigint;44 let balanceQuartzTokenFinal: bigint;45 let balanceKaruraTokenInit: bigint;46 let balanceKaruraTokenMiddle: bigint;47 let balanceKaruraTokenFinal: bigint;48 let balanceQuartzForeignTokenInit: bigint;49 let balanceQuartzForeignTokenMiddle: bigint;50 let balanceQuartzForeignTokenFinal: bigint;5152 before(async () => {53 await usingPlaygrounds(async (helper, privateKey) => {54 alice = privateKey('//Alice');55 [randomAccount] = await helper.arrange.createAccounts([0n], alice);56 });5758 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {59 const destination = {60 V0: {61 X2: [62 'Parent',63 {64 Parachain: QUARTZ_CHAIN,65 },66 ],67 },68 };6970 const metadata = {71 name: 'QTZ',72 symbol: 'QTZ',73 decimals: 18,74 minimalBalance: 1n,75 };7677 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);78 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);79 balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);80 balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});81 });8283 await usingPlaygrounds(async (helper) => {84 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);85 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);86 });87 });8889 itSub('Should connect and send QTZ to Karura', async ({helper}) => {90 const destination = {91 V0: {92 X2: [93 'Parent',94 {95 Parachain: KARURA_CHAIN,96 },97 ],98 },99 };100101 const beneficiary = {102 V0: {103 X1: {104 AccountId32: {105 network: 'Any',106 id: randomAccount.addressRaw,107 },108 },109 },110 };111112 const assets = {113 V1: [114 {115 id: {116 Concrete: {117 parents: 0,118 interior: 'Here',119 },120 },121 fun: {122 Fungible: TRANSFER_AMOUNT,123 },124 },125 ],126 };127128 const feeAssetItem = 0;129 const weightLimit = 5000000000;130131 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, weightLimit);132 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);133134 const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;135 console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));136 expect(qtzFees > 0n).to.be.true;137138 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {139 await helper.wait.newBlocks(3);140 balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});141 balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);142143 const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;144 const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;145146 console.log(147 '[Quartz -> Karura] transaction fees on Karura: %s KAR',148 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),149 );150 console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));151 expect(karFees == 0n).to.be.true;152 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;153 });154 });155156 itSub('Should connect to Karura and send QTZ back', async ({helper}) => {157 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {158 const destination = {159 V1: {160 parents: 1,161 interior: {162 X2: [163 {Parachain: QUARTZ_CHAIN},164 {165 AccountId32: {166 network: 'Any',167 id: randomAccount.addressRaw,168 },169 },170 ],171 },172 },173 };174175 const id = {176 ForeignAsset: 0,177 };178179 const destWeight = 50000000;180181 await helper.xTokens.transfer(randomAccount, id, TRANSFER_AMOUNT, destination, destWeight);182 balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);183 balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);184185 const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;186 const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;187188 console.log(189 '[Karura -> Quartz] transaction fees on Karura: %s KAR',190 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),191 );192 console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));193194 expect(karFees > 0).to.be.true;195 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;196 });197198 await helper.wait.newBlocks(3);199200 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);201 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;202 expect(actuallyDelivered > 0).to.be.true;203204 console.log('[Karura -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));205206 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;207 console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));208 expect(qtzFees == 0n).to.be.true;209 });210});211212213describeXcm('[XCM] Integration test: Quartz rejects non-native tokens', () => {214 let alice: IKeyringPair;215216 before(async () => {217 await usingPlaygrounds(async (_helper, privateKey) => {218 alice = privateKey('//Alice');219 });220 });221222 itSub('Quartz rejects tokens from the Relay', async ({helper}) => {223 await usingRelayPlaygrounds(relayUrl, async (helper) => {224 const destination = {225 V1: {226 parents: 0,227 interior: {X1: {228 Parachain: QUARTZ_CHAIN,229 },230 },231 }};232233 const beneficiary = {234 V1: {235 parents: 0,236 interior: {X1: {237 AccountId32: {238 network: 'Any',239 id: alice.addressRaw,240 },241 }},242 },243 };244245 const assets = {246 V1: [247 {248 id: {249 Concrete: {250 parents: 0,251 interior: 'Here',252 },253 },254 fun: {255 Fungible: 50_000_000_000_000_000n,256 },257 },258 ],259 };260261 const feeAssetItem = 0;262 const weightLimit = 5_000_000_000;263264 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, weightLimit);265 });266267 const maxWaitBlocks = 3;268269 const dmpQueueExecutedDownward = await helper.wait.event(maxWaitBlocks, 'dmpQueue', 'ExecutedDownward');270271 expect(272 dmpQueueExecutedDownward != null,273 '[Relay] dmpQueue.ExecutedDownward event is expected',274 ).to.be.true;275276 const event = dmpQueueExecutedDownward!.event;277 const outcome = event.data[1] as XcmV2TraitsOutcome;278279 expect(280 outcome.isIncomplete,281 '[Relay] The outcome of the XCM should be `Incomplete`',282 ).to.be.true;283284 const incomplete = outcome.asIncomplete;285 expect(286 incomplete[1].toString() == 'AssetNotFound',287 '[Relay] The XCM error should be `AssetNotFound`',288 ).to.be.true;289 });290291 itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {292 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {293 const destination = {294 V1: {295 parents: 1,296 interior: {297 X2: [298 {Parachain: QUARTZ_CHAIN},299 {300 AccountId32: {301 network: 'Any',302 id: alice.addressRaw,303 },304 },305 ],306 },307 },308 };309310 const id = {311 Token: 'KAR',312 };313314 const destWeight = 50000000;315316 await helper.xTokens.transfer(alice, id, 100_000_000_000n, destination, destWeight);317 });318319 const maxWaitBlocks = 3;320321 const xcmpQueueFailEvent = await helper.wait.event(maxWaitBlocks, 'xcmpQueue', 'Fail');322323 expect(324 xcmpQueueFailEvent != null,325 '[Karura] xcmpQueue.FailEvent event is expected',326 ).to.be.true;327328 const event = xcmpQueueFailEvent!.event;329 const outcome = event.data[1] as XcmV2TraitsError;330331 expect(332 outcome.isUntrustedReserveLocation,333 '[Karura] The XCM error should be `UntrustedReserveLocation`',334 ).to.be.true;335 });336});337338describeXcm('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {339340 341 let quartzDonor: IKeyringPair;342 let quartzAssetLocation;343344 let randomAccountQuartz: IKeyringPair;345 let randomAccountMoonriver: IKeyringPair;346347 348 let assetId: string;349350 const councilVotingThreshold = 2;351 const technicalCommitteeThreshold = 2;352 const votingPeriod = 3;353 const delayPeriod = 0;354355 const quartzAssetMetadata = {356 name: 'xcQuartz',357 symbol: 'xcQTZ',358 decimals: 18,359 isFrozen: false,360 minimalBalance: 1n,361 };362363 let balanceQuartzTokenInit: bigint;364 let balanceQuartzTokenMiddle: bigint;365 let balanceQuartzTokenFinal: bigint;366 let balanceForeignQtzTokenInit: bigint;367 let balanceForeignQtzTokenMiddle: bigint;368 let balanceForeignQtzTokenFinal: bigint;369 let balanceMovrTokenInit: bigint;370 let balanceMovrTokenMiddle: bigint;371 let balanceMovrTokenFinal: bigint;372373 before(async () => {374 await usingPlaygrounds(async (helper, privateKey) => {375 quartzDonor = privateKey('//Alice');376 [randomAccountQuartz] = await helper.arrange.createAccounts([0n], quartzDonor);377378 balanceForeignQtzTokenInit = 0n;379 });380381 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {382 const alithAccount = helper.account.alithAccount();383 const baltatharAccount = helper.account.baltatharAccount();384 const dorothyAccount = helper.account.dorothyAccount();385386 randomAccountMoonriver = helper.account.create();387388 389 console.log('Sponsoring Dorothy.......');390 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);391 console.log('Sponsoring Dorothy.......DONE');392 393394 quartzAssetLocation = {395 XCM: {396 parents: 1,397 interior: {X1: {Parachain: QUARTZ_CHAIN}},398 },399 };400 const existentialDeposit = 1n;401 const isSufficient = true;402 const unitsPerSecond = 1n;403 const numAssetsWeightHint = 0;404405 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({406 location: quartzAssetLocation,407 metadata: quartzAssetMetadata,408 existentialDeposit,409 isSufficient,410 unitsPerSecond,411 numAssetsWeightHint,412 });413 const proposalHash = blake2AsHex(encodedProposal);414415 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);416 console.log('Encoded length %d', encodedProposal.length);417 console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);418419 420 console.log('Note motion preimage.......');421 await helper.democracy.notePreimage(baltatharAccount, encodedProposal);422 console.log('Note motion preimage.......DONE');423 424425 426 console.log('Propose external motion through council.......');427 const externalMotion = helper.democracy.externalProposeMajority(proposalHash);428 const encodedMotion = externalMotion?.method.toHex() || '';429 const motionHash = blake2AsHex(encodedMotion);430 console.log('Motion hash is %s', motionHash);431432 await helper.collective.council.propose(baltatharAccount, councilVotingThreshold, externalMotion, externalMotion.encodedLength);433434 const councilProposalIdx = await helper.collective.council.proposalCount() - 1;435 await helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);436 await helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);437438 await helper.collective.council.close(dorothyAccount, motionHash, councilProposalIdx, 1_000_000_000, externalMotion.encodedLength);439 console.log('Propose external motion through council.......DONE');440 441442 443 console.log('Fast track proposal through technical committee.......');444 const fastTrack = helper.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);445 const encodedFastTrack = fastTrack?.method.toHex() || '';446 const fastTrackHash = blake2AsHex(encodedFastTrack);447 console.log('FastTrack hash is %s', fastTrackHash);448449 await helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);450451 const techProposalIdx = await helper.collective.techCommittee.proposalCount() - 1;452 await helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);453 await helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);454455 await helper.collective.techCommittee.close(baltatharAccount, fastTrackHash, techProposalIdx, 1_000_000_000, fastTrack.encodedLength);456 console.log('Fast track proposal through technical committee.......DONE');457 458459 460 console.log('Referendum voting.......');461 await helper.democracy.referendumVote(dorothyAccount, 0, {462 balance: 10_000_000_000_000_000_000n,463 vote: {aye: true, conviction: 1},464 });465 console.log('Referendum voting.......DONE');466 467468 469 console.log('Acquire Quartz AssetId Info on Moonriver.......');470471 472 await helper.wait.newBlocks(5);473474 assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();475476 console.log('QTZ asset ID is %s', assetId);477 console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');478 479480 481 console.log('Sponsoring random Account.......');482 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);483 console.log('Sponsoring random Account.......DONE');484 485486 balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);487 });488489 await usingPlaygrounds(async (helper) => {490 await helper.balance.transferToSubstrate(quartzDonor, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);491 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);492 });493 });494495 itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {496 const currencyId = {497 NativeAssetId: 'Here',498 };499 const dest = {500 V1: {501 parents: 1,502 interior: {503 X2: [504 {Parachain: MOONRIVER_CHAIN},505 {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},506 ],507 },508 },509 };510 const amount = TRANSFER_AMOUNT;511 const destWeight = 850000000;512513 await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, destWeight);514515 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);516 expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;517518 const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;519 console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));520 expect(transactionFees > 0).to.be.true;521522 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {523 await helper.wait.newBlocks(3);524525 balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);526527 const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;528 console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));529 expect(movrFees == 0n).to.be.true;530531 balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; 532 const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;533 console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));534 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;535 });536 });537538 itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {539 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {540 const asset = {541 V1: {542 id: {543 Concrete: {544 parents: 1,545 interior: {546 X1: {Parachain: QUARTZ_CHAIN},547 },548 },549 },550 fun: {551 Fungible: TRANSFER_AMOUNT,552 },553 },554 };555 const destination = {556 V1: {557 parents: 1,558 interior: {559 X2: [560 {Parachain: QUARTZ_CHAIN},561 {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},562 ],563 },564 },565 };566 const destWeight = 50000000;567568 await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, destWeight);569570 balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);571572 const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;573 console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));574 expect(movrFees > 0).to.be.true;575576 const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);577578 expect(qtzRandomAccountAsset).to.be.null;579580 balanceForeignQtzTokenFinal = 0n;581582 const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;583 console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));584 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;585 });586587 await helper.wait.newBlocks(3);588589 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);590 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;591 expect(actuallyDelivered > 0).to.be.true;592593 console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));594595 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;596 console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));597 expect(qtzFees == 0n).to.be.true;598 });599});