1234567891011121314151617import {IKeyringPair} from '@polkadot/types/types';18import {blake2AsHex} from '@polkadot/util-crypto';19import config from '../config';20import {XcmV2TraitsError, XcmV2TraitsOutcome} from '../interfaces';21import {itSub, expect, describeXcm, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds} from '../util/playgrounds';2223const UNIQUE_CHAIN = 2037;24const ACALA_CHAIN = 2000;25const MOONBEAM_CHAIN = 2004;2627const relayUrl = config.relayUrl;28const acalaUrl = config.acalaUrl;29const moonbeamUrl = config.moonbeamUrl;3031const ACALA_DECIMALS = 12;3233const TRANSFER_AMOUNT = 2000000000000000000000000n;3435describeXcm('[XCM] Integration test: Exchanging tokens with Acala', () => {36 let alice: IKeyringPair;37 let randomAccount: IKeyringPair;3839 let balanceUniqueTokenInit: bigint;40 let balanceUniqueTokenMiddle: bigint;41 let balanceUniqueTokenFinal: bigint;42 let balanceAcalaTokenInit: bigint;43 let balanceAcalaTokenMiddle: bigint;44 let balanceAcalaTokenFinal: bigint;45 let balanceUniqueForeignTokenInit: bigint;46 let balanceUniqueForeignTokenMiddle: bigint;47 let balanceUniqueForeignTokenFinal: bigint;4849 before(async () => {50 await usingPlaygrounds(async (helper, privateKey) => {51 alice = privateKey('//Alice');52 [randomAccount] = await helper.arrange.createAccounts([0n], alice);53 });5455 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {56 const destination = {57 V0: {58 X2: [59 'Parent',60 {61 Parachain: UNIQUE_CHAIN,62 },63 ],64 },65 };6667 const metadata = {68 name: 'UNQ',69 symbol: 'UNQ',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 balanceAcalaTokenInit = await helper.balance.getSubstrate(randomAccount.address);77 balanceUniqueForeignTokenInit = 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 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);83 });84 });8586 itSub('Should connect and send UNQ to Acala', async ({helper}) => {8788 const destination = {89 V0: {90 X2: [91 'Parent',92 {93 Parachain: ACALA_CHAIN,94 },95 ],96 },97 };9899 const beneficiary = {100 V0: {101 X1: {102 AccountId32: {103 network: 'Any',104 id: randomAccount.addressRaw,105 },106 },107 },108 };109110 const assets = {111 V1: [112 {113 id: {114 Concrete: {115 parents: 0,116 interior: 'Here',117 },118 },119 fun: {120 Fungible: TRANSFER_AMOUNT,121 },122 },123 ],124 };125126 const feeAssetItem = 0;127 const weightLimit = 5000000000;128129 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, weightLimit);130131 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);132133 const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;134 console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));135 expect(unqFees > 0n).to.be.true;136137 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {138 await helper.wait.newBlocks(3);139140 balanceUniqueForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});141 balanceAcalaTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);142143 const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle;144 const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit;145146 console.log(147 '[Unique -> Acala] transaction fees on Acala: %s ACA',148 helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),149 );150 console.log('[Unique -> Acala] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));151 expect(acaFees == 0n).to.be.true;152 expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;153 });154 });155156 itSub('Should connect to Acala and send UNQ back', async ({helper}) => {157 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {158 const destination = {159 V1: {160 parents: 1,161 interior: {162 X2: [163 {Parachain: UNIQUE_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);182183 balanceAcalaTokenFinal = await helper.balance.getSubstrate(randomAccount.address);184 balanceUniqueForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);185186 const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal;187 const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal;188189 console.log(190 '[Acala -> Unique] transaction fees on Acala: %s ACA',191 helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),192 );193 console.log('[Acala -> Unique] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));194195 expect(acaFees > 0).to.be.true;196 expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;197 });198199 await helper.wait.newBlocks(3);200201 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);202 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;203 expect(actuallyDelivered > 0).to.be.true;204205 console.log('[Acala -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));206207 const unqFees = TRANSFER_AMOUNT - actuallyDelivered;208 console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));209 expect(unqFees == 0n).to.be.true;210 });211});212213214describeXcm('[XCM] Integration test: Unique rejects non-native tokens', () => {215 let alice: IKeyringPair;216217 before(async () => {218 await usingPlaygrounds(async (_helper, privateKey) => {219 alice = privateKey('//Alice');220 });221 });222223 itSub('Unique rejects tokens from the Relay', async ({helper}) => {224 await usingRelayPlaygrounds(relayUrl, async (helper) => {225 const destination = {226 V1: {227 parents: 0,228 interior: {X1: {229 Parachain: UNIQUE_CHAIN,230 },231 },232 }};233234 const beneficiary = {235 V1: {236 parents: 0,237 interior: {X1: {238 AccountId32: {239 network: 'Any',240 id: alice.addressRaw,241 },242 }},243 },244 };245246 const assets = {247 V1: [248 {249 id: {250 Concrete: {251 parents: 0,252 interior: 'Here',253 },254 },255 fun: {256 Fungible: 50_000_000_000_000_000n,257 },258 },259 ],260 };261262 const feeAssetItem = 0;263 const weightLimit = 5_000_000_000;264265 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, weightLimit);266 });267268 const maxWaitBlocks = 3;269270 const dmpQueueExecutedDownward = await helper.wait.event(maxWaitBlocks, 'dmpQueue', 'ExecutedDownward');271272 expect(273 dmpQueueExecutedDownward != null,274 '[Relay] dmpQueue.ExecutedDownward event is expected',275 ).to.be.true;276277 const event = dmpQueueExecutedDownward!.event;278 const outcome = event.data[1] as XcmV2TraitsOutcome;279280 expect(281 outcome.isIncomplete,282 '[Relay] The outcome of the XCM should be `Incomplete`',283 ).to.be.true;284285 const incomplete = outcome.asIncomplete;286 expect(287 incomplete[1].toString() == 'AssetNotFound',288 '[Relay] The XCM error should be `AssetNotFound`',289 ).to.be.true;290 });291292 itSub('Unique rejects ACA tokens from Acala', async ({helper}) => {293 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {294 const destination = {295 V1: {296 parents: 1,297 interior: {298 X2: [299 {Parachain: UNIQUE_CHAIN},300 {301 AccountId32: {302 network: 'Any',303 id: alice.addressRaw,304 },305 },306 ],307 },308 },309 };310311 const id = {312 Token: 'ACA',313 };314315 const destWeight = 50000000;316317 await helper.xTokens.transfer(alice, id, 100_000_000_000n, destination, destWeight);318 });319320 const maxWaitBlocks = 3;321322 const xcmpQueueFailEvent = await helper.wait.event(maxWaitBlocks, 'xcmpQueue', 'Fail');323324 expect(325 xcmpQueueFailEvent != null,326 '[Acala] xcmpQueue.FailEvent event is expected',327 ).to.be.true;328329 const event = xcmpQueueFailEvent!.event;330 const outcome = event.data[1] as XcmV2TraitsError;331332 expect(333 outcome.isUntrustedReserveLocation,334 '[Acala] The XCM error should be `UntrustedReserveLocation`',335 ).to.be.true;336 });337});338339describeXcm('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {340341 342 let uniqueDonor: IKeyringPair;343 let uniqueAssetLocation;344345 let randomAccountUnique: IKeyringPair;346 let randomAccountMoonbeam: IKeyringPair;347348 349 let assetId: string;350351 const councilVotingThreshold = 2;352 const technicalCommitteeThreshold = 2;353 const votingPeriod = 3;354 const delayPeriod = 0;355356 const uniqueAssetMetadata = {357 name: 'xcUnique',358 symbol: 'xcUNQ',359 decimals: 18,360 isFrozen: false,361 minimalBalance: 1n,362 };363364 let balanceUniqueTokenInit: bigint;365 let balanceUniqueTokenMiddle: bigint;366 let balanceUniqueTokenFinal: bigint;367 let balanceForeignUnqTokenInit: bigint;368 let balanceForeignUnqTokenMiddle: bigint;369 let balanceForeignUnqTokenFinal: bigint;370 let balanceGlmrTokenInit: bigint;371 let balanceGlmrTokenMiddle: bigint;372 let balanceGlmrTokenFinal: bigint;373374 before(async () => {375 await usingPlaygrounds(async (helper, privateKey) => {376 uniqueDonor = privateKey('//Alice');377 [randomAccountUnique] = await helper.arrange.createAccounts([0n], uniqueDonor);378379 balanceForeignUnqTokenInit = 0n;380 });381382 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {383 const alithAccount = helper.account.alithAccount();384 const baltatharAccount = helper.account.baltatharAccount();385 const dorothyAccount = helper.account.dorothyAccount();386387 randomAccountMoonbeam = helper.account.create();388389 390 console.log('Sponsoring Dorothy.......');391 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);392 console.log('Sponsoring Dorothy.......DONE');393 394395 uniqueAssetLocation = {396 XCM: {397 parents: 1,398 interior: {X1: {Parachain: UNIQUE_CHAIN}},399 },400 };401 const existentialDeposit = 1n;402 const isSufficient = true;403 const unitsPerSecond = 1n;404 const numAssetsWeightHint = 0;405406 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({407 location: uniqueAssetLocation,408 metadata: uniqueAssetMetadata,409 existentialDeposit,410 isSufficient,411 unitsPerSecond,412 numAssetsWeightHint,413 });414 const proposalHash = blake2AsHex(encodedProposal);415416 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);417 console.log('Encoded length %d', encodedProposal.length);418 console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);419420 421 console.log('Note motion preimage.......');422 await helper.democracy.notePreimage(baltatharAccount, encodedProposal);423 console.log('Note motion preimage.......DONE');424 425426 427 console.log('Propose external motion through council.......');428 const externalMotion = helper.democracy.externalProposeMajority(proposalHash);429 const encodedMotion = externalMotion?.method.toHex() || '';430 const motionHash = blake2AsHex(encodedMotion);431 console.log('Motion hash is %s', motionHash);432433 await helper.collective.council.propose(baltatharAccount, councilVotingThreshold, externalMotion, externalMotion.encodedLength);434435 const councilProposalIdx = await helper.collective.council.proposalCount() - 1;436 await helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);437 await helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);438439 await helper.collective.council.close(dorothyAccount, motionHash, councilProposalIdx, 1_000_000_000, externalMotion.encodedLength);440 console.log('Propose external motion through council.......DONE');441 442443 444 console.log('Fast track proposal through technical committee.......');445 const fastTrack = helper.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);446 const encodedFastTrack = fastTrack?.method.toHex() || '';447 const fastTrackHash = blake2AsHex(encodedFastTrack);448 console.log('FastTrack hash is %s', fastTrackHash);449450 await helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);451452 const techProposalIdx = await helper.collective.techCommittee.proposalCount() - 1;453 await helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);454 await helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);455456 await helper.collective.techCommittee.close(baltatharAccount, fastTrackHash, techProposalIdx, 1_000_000_000, fastTrack.encodedLength);457 console.log('Fast track proposal through technical committee.......DONE');458 459460 461 console.log('Referendum voting.......');462 await helper.democracy.referendumVote(dorothyAccount, 0, {463 balance: 10_000_000_000_000_000_000n,464 vote: {aye: true, conviction: 1},465 });466 console.log('Referendum voting.......DONE');467 468469 470 console.log('Acquire Unique AssetId Info on Moonbeam.......');471472 473 await helper.wait.newBlocks(5);474475 assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();476477 console.log('UNQ asset ID is %s', assetId);478 console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');479 480481 482 console.log('Sponsoring random Account.......');483 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);484 console.log('Sponsoring random Account.......DONE');485 486487 balanceGlmrTokenInit = await helper.balance.getEthereum(randomAccountMoonbeam.address);488 });489490 await usingPlaygrounds(async (helper) => {491 await helper.balance.transferToSubstrate(uniqueDonor, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);492 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);493 });494 });495496 itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => {497 const currencyId = {498 NativeAssetId: 'Here',499 };500 const dest = {501 V1: {502 parents: 1,503 interior: {504 X2: [505 {Parachain: MOONBEAM_CHAIN},506 {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},507 ],508 },509 },510 };511 const amount = TRANSFER_AMOUNT;512 const destWeight = 850000000;513514 await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, destWeight);515516 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address);517 expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;518519 const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;520 console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees));521 expect(transactionFees > 0).to.be.true;522523 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {524 await helper.wait.newBlocks(3);525526 balanceGlmrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonbeam.address);527528 const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;529 console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));530 expect(glmrFees == 0n).to.be.true;531532 balanceForeignUnqTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonbeam.address))!;533534 const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;535 console.log('[Unique -> Moonbeam] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));536 expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;537 });538 });539540 itSub('Should connect to Moonbeam and send UNQ back', async ({helper}) => {541 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {542 const asset = {543 V1: {544 id: {545 Concrete: {546 parents: 1,547 interior: {548 X1: {Parachain: UNIQUE_CHAIN},549 },550 },551 },552 fun: {553 Fungible: TRANSFER_AMOUNT,554 },555 },556 };557 const destination = {558 V1: {559 parents: 1,560 interior: {561 X2: [562 {Parachain: UNIQUE_CHAIN},563 {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},564 ],565 },566 },567 };568 const destWeight = 50000000;569570 await helper.xTokens.transferMultiasset(randomAccountMoonbeam, asset, destination, destWeight);571572 balanceGlmrTokenFinal = await helper.balance.getEthereum(randomAccountMoonbeam.address);573574 const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;575 console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));576 expect(glmrFees > 0).to.be.true;577578 const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address);579580 expect(unqRandomAccountAsset).to.be.null;581 582 balanceForeignUnqTokenFinal = 0n;583584 const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;585 console.log('[Unique -> Moonbeam] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));586 expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;587 });588589 await helper.wait.newBlocks(3);590591 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountUnique.address);592 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;593 expect(actuallyDelivered > 0).to.be.true;594595 console.log('[Moonbeam -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));596597 const unqFees = TRANSFER_AMOUNT - actuallyDelivered;598 console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));599 expect(unqFees == 0n).to.be.true;600 });601});