difftreelog
refactor xcm Unq tests
in: master
3 files changed
tests/src/xcm/lowLevelXcmUnique.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 config from '../config';19import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds} from '../util';20import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';21import {nToBigInt} from '@polkadot/util';22import {hexToString} from '@polkadot/util';2324const UNIQUE_CHAIN = +(process.env.RELAY_UNIQUE_ID || 2037);25const ACALA_CHAIN = +(process.env.RELAY_ACALA_ID || 2000);26const MOONBEAM_CHAIN = +(process.env.RELAY_MOONBEAM_ID || 2004);27const ASTAR_CHAIN = +(process.env.RELAY_ASTAR_ID || 2006);28const POLKADEX_CHAIN = +(process.env.RELAY_POLKADEX_ID || 2040);29303132const acalaUrl = config.acalaUrl;33const moonbeamUrl = config.moonbeamUrl;34const astarUrl = config.astarUrl;35const polkadexUrl = config.polkadexUrl;3637const ASTAR_DECIMALS = 18n;38const UNQ_DECIMALS = 18n;3940const TRANSFER_AMOUNT = 2000000_000_000_000_000_000_000n;41const SENDER_BUDGET = 2n * TRANSFER_AMOUNT;42const SENDBACK_AMOUNT = TRANSFER_AMOUNT / 2n;43const STAYED_ON_TARGET_CHAIN = TRANSFER_AMOUNT - SENDBACK_AMOUNT;44const TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT = 100_000_000_000n;4546const SAFE_XCM_VERSION = 2;47const maxWaitBlocks = 6;4849const uniqueMultilocation = {50 V2: {51 parents: 1,52 interior: {53 X1: {54 Parachain: UNIQUE_CHAIN,55 },56 },57 },58};5960let balanceUniqueTokenInit: bigint;61let balanceUniqueTokenMiddle: bigint;62let balanceUniqueTokenFinal: bigint;63let unqFees: bigint;6465const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {66 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash67 && event.outcome.isFailedToTransactAsset);68};69const expectUntrustedReserveLocationFail = async (helper: DevUniqueHelper, messageSent: any) => {70 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash71 && event.outcome.isUntrustedReserveLocation);72};7374const NETWORKS = {75 acala: usingAcalaPlaygrounds,76 astar: usingAstarPlaygrounds,77 polkadex: usingPolkadexPlaygrounds,78 moonbeam: usingMoonbeamPlaygrounds,79} as const;8081function mapToChainId(networkName: keyof typeof NETWORKS) {82 switch (networkName) {83 case 'acala':84 return ACALA_CHAIN;85 case 'astar':86 return ASTAR_CHAIN;87 case 'moonbeam':88 return MOONBEAM_CHAIN;89 case 'polkadex':90 return POLKADEX_CHAIN;91 }92}9394function mapToChainUrl(networkName: keyof typeof NETWORKS): string {95 switch (networkName) {96 case 'acala':97 return acalaUrl;98 case 'astar':99 return astarUrl;100 case 'moonbeam':101 return moonbeamUrl;102 case 'polkadex':103 return polkadexUrl;104 }105}106107function getDevPlayground<T extends keyof typeof NETWORKS>(name: T) {108 return NETWORKS[name];109}110111112async function genericSendUnqTo(113 networkName: keyof typeof NETWORKS,114 randomAccount: IKeyringPair,115 randomAccountOnTargetChain = randomAccount,116) {117 const networkUrl = mapToChainUrl(networkName);118 const targetPlayground = getDevPlayground(networkName);119 await usingPlaygrounds(async (helper) => {120 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);121 const destination = {122 V2: {123 parents: 1,124 interior: {125 X1: {126 Parachain: mapToChainId(networkName),127 },128 },129 },130 };131132 const beneficiary = {133 V2: {134 parents: 0,135 interior: {136 X1: (137 networkName == 'moonbeam' ?138 {139 AccountKey20: {140 network: 'Any',141 key: randomAccountOnTargetChain.address,142 },143 }144 :145 {146 AccountId32: {147 network: 'Any',148 id: randomAccountOnTargetChain.addressRaw,149 },150 }151 ),152 },153 },154 };155156 const assets = {157 V2: [158 {159 id: {160 Concrete: {161 parents: 0,162 interior: 'Here',163 },164 },165 fun: {166 Fungible: TRANSFER_AMOUNT,167 },168 },169 ],170 };171 const feeAssetItem = 0;172173 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');174 const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);175 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);176177 unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;178 console.log('[Unique -> %s] transaction fees on Unique: %s UNQ', networkName, helper.util.bigIntToDecimals(unqFees));179 expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;180181 await targetPlayground(networkUrl, async (helper) => {182 /*183 Since only the parachain part of the Polkadex184 infrastructure is launched (without their185 solochain validators), processing incoming186 assets will lead to an error.187 This error indicates that the Polkadex chain188 received a message from the Unique network,189 since the hash is being checked to ensure190 it matches what was sent.191 */192 if(networkName == 'polkadex') {193 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);194 } else {195 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == messageSent.messageHash);196 }197 });198199 });200}201202async function genericSendUnqBack(203 networkName: keyof typeof NETWORKS,204 sudoer: IKeyringPair,205 randomAccountOnUnq: IKeyringPair,206) {207 const networkUrl = mapToChainUrl(networkName);208209 const targetPlayground = getDevPlayground(networkName);210 await usingPlaygrounds(async (helper) => {211212 const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(213 randomAccountOnUnq.addressRaw,214 {215 Concrete: {216 parents: 1,217 interior: {218 X1: {Parachain: UNIQUE_CHAIN},219 },220 },221 },222 SENDBACK_AMOUNT,223 );224225 let xcmProgramSent: any;226227228 await targetPlayground(networkUrl, async (helper) => {229 if('getSudo' in helper) {230 await helper.getSudo().xcm.send(sudoer, uniqueMultilocation, xcmProgram);231 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);232 } else if('fastDemocracy' in helper) {233 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, xcmProgram]);234 // Needed to bypass the call filter.235 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);236 await helper.fastDemocracy.executeProposal('sending MoonBeam -> Unique via XCM program', batchCall);237 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);238 }239 });240241 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);242243 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountOnUnq.address);244245 expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees - STAYED_ON_TARGET_CHAIN);246247 });248}249250async function genericSendOnlyOwnedBalance(251 networkName: keyof typeof NETWORKS,252 sudoer: IKeyringPair,253) {254 const networkUrl = mapToChainUrl(networkName);255 const targetPlayground = getDevPlayground(networkName);256257 const targetChainBalance = 10000n * (10n ** UNQ_DECIMALS);258259 await usingPlaygrounds(async (helper) => {260 const targetChainSovereignAccount = helper.address.paraSiblingSovereignAccount(mapToChainId(networkName));261 await helper.getSudo().balance.setBalanceSubstrate(sudoer, targetChainSovereignAccount, targetChainBalance);262 const moreThanTargetChainHas = 2n * targetChainBalance;263264 const targetAccount = helper.arrange.createEmptyAccount();265266 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(267 targetAccount.addressRaw,268 {269 Concrete: {270 parents: 0,271 interior: 'Here',272 },273 },274 moreThanTargetChainHas,275 );276277 let maliciousXcmProgramSent: any;278279280 await targetPlayground(networkUrl, async (helper) => {281 if('getSudo' in helper) {282 await helper.getSudo().xcm.send(sudoer, uniqueMultilocation, maliciousXcmProgram);283 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);284 } else if('fastDemocracy' in helper) {285 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgram]);286 // Needed to bypass the call filter.287 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);288 await helper.fastDemocracy.executeProposal('sending MoonBeam -> Unique via XCM program', batchCall);289 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);290 }291 });292293 await expectFailedToTransact(helper, maliciousXcmProgramSent);294295 const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);296 expect(targetAccountBalance).to.be.equal(0n);297 });298}299300async function genericReserveTransferUNQfrom(netwokrName: keyof typeof NETWORKS, sudoer: IKeyringPair) {301 const networkUrl = mapToChainUrl(netwokrName);302 const targetPlayground = getDevPlayground(netwokrName);303304 await usingPlaygrounds(async (helper) => {305 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);306 const targetAccount = helper.arrange.createEmptyAccount();307308 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(309 targetAccount.addressRaw,310 {311 Concrete: {312 parents: 1,313 interior: {314 X1: {315 Parachain: UNIQUE_CHAIN,316 },317 },318 },319 },320 testAmount,321 );322323 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(324 targetAccount.addressRaw,325 {326 Concrete: {327 parents: 0,328 interior: 'Here',329 },330 },331 testAmount,332 );333334 let maliciousXcmProgramFullIdSent: any;335 let maliciousXcmProgramHereIdSent: any;336 const maxWaitBlocks = 3;337338 // Try to trick Unique using full UNQ identification339 await targetPlayground(networkUrl, async (helper) => {340 if('getSudo' in helper) {341 await helper.getSudo().xcm.send(sudoer, uniqueMultilocation, maliciousXcmProgramFullId);342 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);343 }344 // Moonbeam case345 else if('fastDemocracy' in helper) {346 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramFullId]);347348 // Needed to bypass the call filter.349 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);350 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using path asset identification', batchCall);351352 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);353 }354 });355356357 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);358359 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);360 expect(accountBalance).to.be.equal(0n);361362 // Try to trick Unique using shortened UNQ identification363 await targetPlayground(networkUrl, async (helper) => {364 if('getSudo' in helper) {365 await helper.getSudo().xcm.send(sudoer, uniqueMultilocation, maliciousXcmProgramHereId);366 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);367 }368 else if('fastDemocracy' in helper) {369 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramHereId]);370371 // Needed to bypass the call filter.372 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);373 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using "here" asset identification', batchCall);374375 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);376 }377 });378379 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);380381 accountBalance = await helper.balance.getSubstrate(targetAccount.address);382 expect(accountBalance).to.be.equal(0n);383 });384}385386async function genericRejectNativeToknsFrom(netwokrName: keyof typeof NETWORKS, sudoerOnTargetChain: IKeyringPair) {387 const networkUrl = mapToChainUrl(netwokrName);388 const targetPlayground = getDevPlayground(netwokrName);389 let messageSent: any;390391 await usingPlaygrounds(async (helper) => {392 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(393 helper.arrange.createEmptyAccount().addressRaw,394 {395 Concrete: {396 parents: 1,397 interior: {398 X1: {399 Parachain: mapToChainId(netwokrName),400 },401 },402 },403 },404 TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT,405 );406 await targetPlayground(networkUrl, async (helper) => {407 if('getSudo' in helper) {408 await helper.getSudo().xcm.send(sudoerOnTargetChain, uniqueMultilocation, maliciousXcmProgramFullId);409 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);410 } else if('fastDemocracy' in helper) {411 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramFullId]);412413 // Needed to bypass the call filter.414 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);415 await helper.fastDemocracy.executeProposal('sending native tokens to the Unique via fast democracy', batchCall);416417 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);418 }419 });420421 await expectFailedToTransact(helper, messageSent);422 });423}424425426describeXCM('[XCMLL] Integration test: Exchanging tokens with Acala', () => {427 let alice: IKeyringPair;428 let randomAccount: IKeyringPair;429430 before(async () => {431 await usingPlaygrounds(async (helper, privateKey) => {432 alice = await privateKey('//Alice');433 console.log(config.acalaUrl);434 randomAccount = helper.arrange.createEmptyAccount();435436 // Set the default version to wrap the first message to other chains.437 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);438 });439440 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {441 const destination = {442 V2: {443 parents: 1,444 interior: {445 X1: {446 Parachain: UNIQUE_CHAIN,447 },448 },449 },450 };451452 const metadata = {453 name: 'Unique Network',454 symbol: 'UNQ',455 decimals: 18,456 minimalBalance: 1250_000_000_000_000_000n,457 };458 const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v] : [any, any]) =>459 hexToString(v.toJSON()['symbol'])) as string[];460461 if(!assets.includes('UNQ')) {462 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);463 } else {464 console.log('UNQ token already registered on Acala assetRegistry pallet');465 }466 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);467 });468469 await usingPlaygrounds(async (helper) => {470 await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);471 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);472 });473 });474475 itSub('Should connect and send UNQ to Acala', async () => {476 await genericSendUnqTo('acala', randomAccount);477 });478479 itSub('Should connect to Acala and send UNQ back', async () => {480 await genericSendUnqBack('acala', alice, randomAccount);481 });482483 itSub('Acala can send only up to its balance', async () => {484 await genericSendOnlyOwnedBalance('acala', alice);485 });486487 itSub('Should not accept reserve transfer of UNQ from Acala', async () => {488 await genericReserveTransferUNQfrom('acala', alice);489 });490});491492describeXCM('[XCMLL] Integration test: Exchanging tokens with Polkadex', () => {493 let alice: IKeyringPair;494 let randomAccount: IKeyringPair;495496 const uniqueAssetId = {497 Concrete: {498 parents: 1,499 interior: {500 X1: {501 Parachain: UNIQUE_CHAIN,502 },503 },504 },505 };506507 before(async () => {508 await usingPlaygrounds(async (helper, privateKey) => {509 alice = await privateKey('//Alice');510 randomAccount = helper.arrange.createEmptyAccount();511512 // Set the default version to wrap the first message to other chains.513 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);514 });515516 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {517 const isWhitelisted = ((await helper.callRpc('api.query.xcmHelper.whitelistedTokens', []))518 .toJSON() as [])519 .map(nToBigInt).length != 0;520 /*521 Check whether the Unique token has been added522 to the whitelist, since an error will occur523 if it is added again. Needed for debugging524 when this test is run multiple times.525 */526 if(!isWhitelisted) {527 await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);528 }529530 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);531 });532533 await usingPlaygrounds(async (helper) => {534 await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);535 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);536 });537 });538539 itSub('Should connect and send UNQ to Polkadex', async () => {540 await genericSendUnqTo('polkadex', randomAccount);541 });542543544 itSub('Should connect to Polkadex and send UNQ back', async () => {545 await genericSendUnqBack('polkadex', alice, randomAccount);546 });547548 itSub('Polkadex can send only up to its balance', async () => {549 await genericSendOnlyOwnedBalance('polkadex', alice);550 });551552 itSub('Should not accept reserve transfer of UNQ from Polkadex', async () => {553 await genericReserveTransferUNQfrom('polkadex', alice);554 });555});556557// These tests are relevant only when558// the the corresponding foreign assets are not registered559describeXCM('[XCMLL] Integration test: Unique rejects non-native tokens', () => {560 let alice: IKeyringPair;561562 before(async () => {563 await usingPlaygrounds(async (helper, privateKey) => {564 alice = await privateKey('//Alice');565566 // Set the default version to wrap the first message to other chains.567 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);568 });569 });570571 itSub('Unique rejects ACA tokens from Acala', async () => {572 await genericRejectNativeToknsFrom('acala', alice);573 });574575 itSub('Unique rejects GLMR tokens from Moonbeam', async () => {576 await genericRejectNativeToknsFrom('moonbeam', alice);577 });578579 itSub('Unique rejects ASTR tokens from Astar', async () => {580 await genericRejectNativeToknsFrom('astar', alice);581 });582583 itSub('Unique rejects PDX tokens from Polkadex', async () => {584 await genericRejectNativeToknsFrom('polkadex', alice);585 });586});587588describeXCM('[XCMLL] Integration test: Exchanging UNQ with Moonbeam', () => {589 // Unique constants590 let alice: IKeyringPair;591 let uniqueAssetLocation;592593 let randomAccountUnique: IKeyringPair;594 let randomAccountMoonbeam: IKeyringPair;595596 // Moonbeam constants597 let assetId: string;598599 const uniqueAssetMetadata = {600 name: 'xcUnique',601 symbol: 'xcUNQ',602 decimals: 18,603 isFrozen: false,604 minimalBalance: 1n,605 };606607608 before(async () => {609 await usingPlaygrounds(async (helper, privateKey) => {610 alice = await privateKey('//Alice');611 [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);612613614 // Set the default version to wrap the first message to other chains.615 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);616 });617618 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {619 const alithAccount = helper.account.alithAccount();620 const baltatharAccount = helper.account.baltatharAccount();621 const dorothyAccount = helper.account.dorothyAccount();622623 randomAccountMoonbeam = helper.account.create();624625 // >>> Sponsoring Dorothy >>>626 console.log('Sponsoring Dorothy.......');627 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);628 console.log('Sponsoring Dorothy.......DONE');629 // <<< Sponsoring Dorothy <<<630 uniqueAssetLocation = {631 XCM: {632 parents: 1,633 interior: {X1: {Parachain: UNIQUE_CHAIN}},634 },635 };636 const existentialDeposit = 1n;637 const isSufficient = true;638 const unitsPerSecond = 1n;639 const numAssetsWeightHint = 0;640641 if((await helper.assetManager.assetTypeId(uniqueAssetLocation)).toJSON()) {642 console.log('Unique asset already registered');643 } else {644 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({645 location: uniqueAssetLocation,646 metadata: uniqueAssetMetadata,647 existentialDeposit,648 isSufficient,649 unitsPerSecond,650 numAssetsWeightHint,651 });652653 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);654655 await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);656 }657658 // >>> Acquire Unique AssetId Info on Moonbeam >>>659 console.log('Acquire Unique AssetId Info on Moonbeam.......');660661 assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();662663 console.log('UNQ asset ID is %s', assetId);664 console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');665666 // >>> Sponsoring random Account >>>667 console.log('Sponsoring random Account.......');668 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);669 console.log('Sponsoring random Account.......DONE');670 // <<< Sponsoring random Account <<<671 });672673 await usingPlaygrounds(async (helper) => {674 await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, SENDER_BUDGET);675 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);676 });677 });678679 itSub('Should connect and send UNQ to Moonbeam', async () => {680 await genericSendUnqTo('moonbeam', randomAccountUnique, randomAccountMoonbeam);681 });682683 itSub('Should connect to Moonbeam and send UNQ back', async () => {684 await genericSendUnqBack('moonbeam', alice, randomAccountUnique);685 });686687 itSub('Moonbeam can send only up to its balance', async () => {688 await genericSendOnlyOwnedBalance('moonbeam', alice);689 });690691 itSub('Should not accept reserve transfer of UNQ from Moonbeam', async () => {692 await genericReserveTransferUNQfrom('moonbeam', alice);693 });694});695696describeXCM('[XCMLL] Integration test: Exchanging tokens with Astar', () => {697 let alice: IKeyringPair;698 let randomAccount: IKeyringPair;699700 const UNQ_ASSET_ID_ON_ASTAR = 1;701 const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n;702703 // Unique -> Astar704 const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.705 const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?706707 before(async () => {708 await usingPlaygrounds(async (helper, privateKey) => {709 alice = await privateKey('//Alice');710 randomAccount = helper.arrange.createEmptyAccount();711 await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);712 console.log('randomAccount', randomAccount.address);713714 // Set the default version to wrap the first message to other chains.715 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);716 });717718 await usingAstarPlaygrounds(astarUrl, async (helper) => {719 if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {720 console.log('1. Create foreign asset and metadata');721 // TODO update metadata with values from production722 await helper.assets.create(723 alice,724 UNQ_ASSET_ID_ON_ASTAR,725 alice.address,726 UNQ_MINIMAL_BALANCE_ON_ASTAR,727 );728729 await helper.assets.setMetadata(730 alice,731 UNQ_ASSET_ID_ON_ASTAR,732 'Cross chain UNQ',733 'xcUNQ',734 Number(UNQ_DECIMALS),735 );736737 console.log('2. Register asset location on Astar');738 const assetLocation = {739 V2: {740 parents: 1,741 interior: {742 X1: {743 Parachain: UNIQUE_CHAIN,744 },745 },746 },747 };748749 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);750751 console.log('3. Set UNQ payment for XCM execution on Astar');752 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);753 }754 console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');755 await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);756 });757 });758759 itSub('Should connect and send UNQ to Astar', async () => {760 await genericSendUnqTo('astar', randomAccount);761 });762763 itSub('Should connect to Astar and send UNQ back', async () => {764 await genericSendUnqBack('astar', alice, randomAccount);765 });766767 itSub('Astar can send only up to its balance', async () => {768 await genericSendOnlyOwnedBalance('astar', alice);769 });770771 itSub('Should not accept reserve transfer of UNQ from Astar', async () => {772 await genericReserveTransferUNQfrom('astar', alice);773 });774});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 config from '../config';19import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds} from '../util';20import {Event} from '../util/playgrounds/unique.dev';21import {nToBigInt} from '@polkadot/util';22import {hexToString} from '@polkadot/util';23import {ASTAR_DECIMALS, NETWORKS, SAFE_XCM_VERSION, UNIQUE_CHAIN, UNQ_DECIMALS, acalaUrl, astarUrl, expectFailedToTransact, expectUntrustedReserveLocationFail, getDevPlayground, mapToChainId, mapToChainUrl, maxWaitBlocks, moonbeamUrl, polkadexUrl, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types';242526const TRANSFER_AMOUNT = 2000000_000_000_000_000_000_000n;27const SENDER_BUDGET = 2n * TRANSFER_AMOUNT;28const SENDBACK_AMOUNT = TRANSFER_AMOUNT / 2n;29const STAYED_ON_TARGET_CHAIN = TRANSFER_AMOUNT - SENDBACK_AMOUNT;30const TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT = 100_000_000_000n;3132let balanceUniqueTokenInit: bigint;33let balanceUniqueTokenMiddle: bigint;34let balanceUniqueTokenFinal: bigint;35let unqFees: bigint;363738async function genericSendUnqTo(39 networkName: keyof typeof NETWORKS,40 randomAccount: IKeyringPair,41 randomAccountOnTargetChain = randomAccount,42) {43 const networkUrl = mapToChainUrl(networkName);44 const targetPlayground = getDevPlayground(networkName);45 await usingPlaygrounds(async (helper) => {46 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);47 const destination = {48 V2: {49 parents: 1,50 interior: {51 X1: {52 Parachain: mapToChainId(networkName),53 },54 },55 },56 };5758 const beneficiary = {59 V2: {60 parents: 0,61 interior: {62 X1: (63 networkName == 'moonbeam' ?64 {65 AccountKey20: {66 network: 'Any',67 key: randomAccountOnTargetChain.address,68 },69 }70 :71 {72 AccountId32: {73 network: 'Any',74 id: randomAccountOnTargetChain.addressRaw,75 },76 }77 ),78 },79 },80 };8182 const assets = {83 V2: [84 {85 id: {86 Concrete: {87 parents: 0,88 interior: 'Here',89 },90 },91 fun: {92 Fungible: TRANSFER_AMOUNT,93 },94 },95 ],96 };97 const feeAssetItem = 0;9899 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');100 const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);101 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);102103 unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;104 console.log('[Unique -> %s] transaction fees on Unique: %s UNQ', networkName, helper.util.bigIntToDecimals(unqFees));105 expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;106107 await targetPlayground(networkUrl, async (helper) => {108 /*109 Since only the parachain part of the Polkadex110 infrastructure is launched (without their111 solochain validators), processing incoming112 assets will lead to an error.113 This error indicates that the Polkadex chain114 received a message from the Unique network,115 since the hash is being checked to ensure116 it matches what was sent.117 */118 if(networkName == 'polkadex') {119 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);120 } else {121 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == messageSent.messageHash);122 }123 });124125 });126}127128async function genericSendUnqBack(129 networkName: keyof typeof NETWORKS,130 sudoer: IKeyringPair,131 randomAccountOnUnq: IKeyringPair,132) {133 const networkUrl = mapToChainUrl(networkName);134135 const targetPlayground = getDevPlayground(networkName);136 await usingPlaygrounds(async (helper) => {137138 const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(139 randomAccountOnUnq.addressRaw,140 {141 Concrete: {142 parents: 1,143 interior: {144 X1: {Parachain: UNIQUE_CHAIN},145 },146 },147 },148 SENDBACK_AMOUNT,149 );150151 let xcmProgramSent: any;152153154 await targetPlayground(networkUrl, async (helper) => {155 if('getSudo' in helper) {156 await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, xcmProgram);157 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);158 } else if('fastDemocracy' in helper) {159 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, xcmProgram]);160 // Needed to bypass the call filter.161 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);162 await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);163 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);164 }165 });166167 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);168169 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountOnUnq.address);170171 expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees - STAYED_ON_TARGET_CHAIN);172173 });174}175176async function genericSendOnlyOwnedBalance(177 networkName: keyof typeof NETWORKS,178 sudoer: IKeyringPair,179) {180 const networkUrl = mapToChainUrl(networkName);181 const targetPlayground = getDevPlayground(networkName);182183 const targetChainBalance = 10000n * (10n ** UNQ_DECIMALS);184185 await usingPlaygrounds(async (helper) => {186 const targetChainSovereignAccount = helper.address.paraSiblingSovereignAccount(mapToChainId(networkName));187 await helper.getSudo().balance.setBalanceSubstrate(sudoer, targetChainSovereignAccount, targetChainBalance);188 const moreThanTargetChainHas = 2n * targetChainBalance;189190 const targetAccount = helper.arrange.createEmptyAccount();191192 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(193 targetAccount.addressRaw,194 {195 Concrete: {196 parents: 0,197 interior: 'Here',198 },199 },200 moreThanTargetChainHas,201 );202203 let maliciousXcmProgramSent: any;204205206 await targetPlayground(networkUrl, async (helper) => {207 if('getSudo' in helper) {208 await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgram);209 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);210 } else if('fastDemocracy' in helper) {211 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]);212 // Needed to bypass the call filter.213 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);214 await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);215 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);216 }217 });218219 await expectFailedToTransact(helper, maliciousXcmProgramSent);220221 const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);222 expect(targetAccountBalance).to.be.equal(0n);223 });224}225226async function genericReserveTransferUNQfrom(netwokrName: keyof typeof NETWORKS, sudoer: IKeyringPair) {227 const networkUrl = mapToChainUrl(netwokrName);228 const targetPlayground = getDevPlayground(netwokrName);229230 await usingPlaygrounds(async (helper) => {231 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);232 const targetAccount = helper.arrange.createEmptyAccount();233234 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(235 targetAccount.addressRaw,236 {237 Concrete: {238 parents: 1,239 interior: {240 X1: {241 Parachain: UNIQUE_CHAIN,242 },243 },244 },245 },246 testAmount,247 );248249 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(250 targetAccount.addressRaw,251 {252 Concrete: {253 parents: 0,254 interior: 'Here',255 },256 },257 testAmount,258 );259260 let maliciousXcmProgramFullIdSent: any;261 let maliciousXcmProgramHereIdSent: any;262 const maxWaitBlocks = 3;263264 // Try to trick Unique using full UNQ identification265 await targetPlayground(networkUrl, async (helper) => {266 if('getSudo' in helper) {267 await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramFullId);268 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);269 }270 // Moonbeam case271 else if('fastDemocracy' in helper) {272 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);273 // Needed to bypass the call filter.274 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);275 await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using path asset identification`,batchCall);276277 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);278 }279 });280281282 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);283284 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);285 expect(accountBalance).to.be.equal(0n);286287 // Try to trick Unique using shortened UNQ identification288 await targetPlayground(networkUrl, async (helper) => {289 if('getSudo' in helper) {290 await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramHereId);291 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);292 }293 else if('fastDemocracy' in helper) {294 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]);295 // Needed to bypass the call filter.296 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);297 await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using "here" asset identification`, batchCall);298299 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);300 }301 });302303 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);304305 accountBalance = await helper.balance.getSubstrate(targetAccount.address);306 expect(accountBalance).to.be.equal(0n);307 });308}309310async function genericRejectNativeToknsFrom(netwokrName: keyof typeof NETWORKS, sudoerOnTargetChain: IKeyringPair) {311 const networkUrl = mapToChainUrl(netwokrName);312 const targetPlayground = getDevPlayground(netwokrName);313 let messageSent: any;314315 await usingPlaygrounds(async (helper) => {316 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(317 helper.arrange.createEmptyAccount().addressRaw,318 {319 Concrete: {320 parents: 1,321 interior: {322 X1: {323 Parachain: mapToChainId(netwokrName),324 },325 },326 },327 },328 TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT,329 );330 await targetPlayground(networkUrl, async (helper) => {331 if('getSudo' in helper) {332 await helper.getSudo().xcm.send(sudoerOnTargetChain, uniqueVersionedMultilocation, maliciousXcmProgramFullId);333 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);334 } else if('fastDemocracy' in helper) {335 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);336 // Needed to bypass the call filter.337 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);338 await helper.fastDemocracy.executeProposal(`${netwokrName} sending native tokens to the Unique via fast democracy`, batchCall);339340 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);341 }342 });343 await expectFailedToTransact(helper, messageSent);344 });345}346347348describeXCM('[XCMLL] Integration test: Exchanging tokens with Acala', () => {349 let alice: IKeyringPair;350 let randomAccount: IKeyringPair;351352 before(async () => {353 await usingPlaygrounds(async (helper, privateKey) => {354 alice = await privateKey('//Alice');355 console.log(config.acalaUrl);356 randomAccount = helper.arrange.createEmptyAccount();357358 // Set the default version to wrap the first message to other chains.359 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);360 });361362 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {363 const destination = {364 V2: {365 parents: 1,366 interior: {367 X1: {368 Parachain: UNIQUE_CHAIN,369 },370 },371 },372 };373374 const metadata = {375 name: 'Unique Network',376 symbol: 'UNQ',377 decimals: 18,378 minimalBalance: 1250_000_000_000_000_000n,379 };380 const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v] : [any, any]) =>381 hexToString(v.toJSON()['symbol'])) as string[];382383 if(!assets.includes('UNQ')) {384 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);385 } else {386 console.log('UNQ token already registered on Acala assetRegistry pallet');387 }388 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);389 });390391 await usingPlaygrounds(async (helper) => {392 await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);393 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);394 });395 });396397 itSub('Should connect and send UNQ to Acala', async () => {398 await genericSendUnqTo('acala', randomAccount);399 });400401 itSub('Should connect to Acala and send UNQ back', async () => {402 await genericSendUnqBack('acala', alice, randomAccount);403 });404405 itSub('Acala can send only up to its balance', async () => {406 await genericSendOnlyOwnedBalance('acala', alice);407 });408409 itSub('Should not accept reserve transfer of UNQ from Acala', async () => {410 await genericReserveTransferUNQfrom('acala', alice);411 });412});413414describeXCM('[XCMLL] Integration test: Exchanging tokens with Polkadex', () => {415 let alice: IKeyringPair;416 let randomAccount: IKeyringPair;417418 before(async () => {419 await usingPlaygrounds(async (helper, privateKey) => {420 alice = await privateKey('//Alice');421 randomAccount = helper.arrange.createEmptyAccount();422423 // Set the default version to wrap the first message to other chains.424 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);425 });426427 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {428 const isWhitelisted = ((await helper.callRpc('api.query.xcmHelper.whitelistedTokens', []))429 .toJSON() as [])430 .map(nToBigInt).length != 0;431 /*432 Check whether the Unique token has been added433 to the whitelist, since an error will occur434 if it is added again. Needed for debugging435 when this test is run multiple times.436 */437 if(isWhitelisted) {438 console.log('UNQ token is already whitelisted on Polkadex');439 } else {440 await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);441 }442443 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);444 });445446 await usingPlaygrounds(async (helper) => {447 await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);448 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);449 });450 });451452 itSub('Should connect and send UNQ to Polkadex', async () => {453 await genericSendUnqTo('polkadex', randomAccount);454 });455456457 itSub('Should connect to Polkadex and send UNQ back', async () => {458 await genericSendUnqBack('polkadex', alice, randomAccount);459 });460461 itSub('Polkadex can send only up to its balance', async () => {462 await genericSendOnlyOwnedBalance('polkadex', alice);463 });464465 itSub('Should not accept reserve transfer of UNQ from Polkadex', async () => {466 await genericReserveTransferUNQfrom('polkadex', alice);467 });468});469470// These tests are relevant only when471// the the corresponding foreign assets are not registered472describeXCM('[XCMLL] Integration test: Unique rejects non-native tokens', () => {473 let alice: IKeyringPair;474475 before(async () => {476 await usingPlaygrounds(async (helper, privateKey) => {477 alice = await privateKey('//Alice');478479 // Set the default version to wrap the first message to other chains.480 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);481 });482 });483484 itSub('Unique rejects ACA tokens from Acala', async () => {485 await genericRejectNativeToknsFrom('acala', alice);486 });487488 itSub('Unique rejects GLMR tokens from Moonbeam', async () => {489 await genericRejectNativeToknsFrom('moonbeam', alice);490 });491492 itSub('Unique rejects ASTR tokens from Astar', async () => {493 await genericRejectNativeToknsFrom('astar', alice);494 });495496 itSub('Unique rejects PDX tokens from Polkadex', async () => {497 await genericRejectNativeToknsFrom('polkadex', alice);498 });499});500501describeXCM('[XCMLL] Integration test: Exchanging UNQ with Moonbeam', () => {502 // Unique constants503 let alice: IKeyringPair;504 let uniqueAssetLocation;505506 let randomAccountUnique: IKeyringPair;507 let randomAccountMoonbeam: IKeyringPair;508509 // Moonbeam constants510 let assetId: string;511512 const uniqueAssetMetadata = {513 name: 'xcUnique',514 symbol: 'xcUNQ',515 decimals: 18,516 isFrozen: false,517 minimalBalance: 1n,518 };519520521 before(async () => {522 await usingPlaygrounds(async (helper, privateKey) => {523 alice = await privateKey('//Alice');524 [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);525526527 // Set the default version to wrap the first message to other chains.528 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);529 });530531 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {532 const alithAccount = helper.account.alithAccount();533 const baltatharAccount = helper.account.baltatharAccount();534 const dorothyAccount = helper.account.dorothyAccount();535536 randomAccountMoonbeam = helper.account.create();537538 // >>> Sponsoring Dorothy >>>539 console.log('Sponsoring Dorothy.......');540 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);541 console.log('Sponsoring Dorothy.......DONE');542 // <<< Sponsoring Dorothy <<<543 uniqueAssetLocation = {544 XCM: {545 parents: 1,546 interior: {X1: {Parachain: UNIQUE_CHAIN}},547 },548 };549 const existentialDeposit = 1n;550 const isSufficient = true;551 const unitsPerSecond = 1n;552 const numAssetsWeightHint = 0;553554 if((await helper.assetManager.assetTypeId(uniqueAssetLocation)).toJSON()) {555 console.log('Unique asset already registered on Moonbeam');556 } else {557 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({558 location: uniqueAssetLocation,559 metadata: uniqueAssetMetadata,560 existentialDeposit,561 isSufficient,562 unitsPerSecond,563 numAssetsWeightHint,564 });565566 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);567568 await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);569 }570571 // >>> Acquire Unique AssetId Info on Moonbeam >>>572 console.log('Acquire Unique AssetId Info on Moonbeam.......');573574 assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();575576 console.log('UNQ asset ID is %s', assetId);577 console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');578579 // >>> Sponsoring random Account >>>580 console.log('Sponsoring random Account.......');581 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);582 console.log('Sponsoring random Account.......DONE');583 // <<< Sponsoring random Account <<<584 });585586 await usingPlaygrounds(async (helper) => {587 await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, SENDER_BUDGET);588 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);589 });590 });591592 itSub('Should connect and send UNQ to Moonbeam', async () => {593 await genericSendUnqTo('moonbeam', randomAccountUnique, randomAccountMoonbeam);594 });595596 itSub('Should connect to Moonbeam and send UNQ back', async () => {597 await genericSendUnqBack('moonbeam', alice, randomAccountUnique);598 });599600 itSub('Moonbeam can send only up to its balance', async () => {601 await genericSendOnlyOwnedBalance('moonbeam', alice);602 });603604 itSub('Should not accept reserve transfer of UNQ from Moonbeam', async () => {605 await genericReserveTransferUNQfrom('moonbeam', alice);606 });607});608609describeXCM('[XCMLL] Integration test: Exchanging tokens with Astar', () => {610 let alice: IKeyringPair;611 let randomAccount: IKeyringPair;612613 const UNQ_ASSET_ID_ON_ASTAR = 1;614 const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n;615616 // Unique -> Astar617 const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.618 const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?619620 before(async () => {621 await usingPlaygrounds(async (helper, privateKey) => {622 alice = await privateKey('//Alice');623 randomAccount = helper.arrange.createEmptyAccount();624 await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);625 console.log('randomAccount', randomAccount.address);626627 // Set the default version to wrap the first message to other chains.628 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);629 });630631 await usingAstarPlaygrounds(astarUrl, async (helper) => {632 if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {633 console.log('1. Create foreign asset and metadata');634 // TODO update metadata with values from production635 await helper.assets.create(636 alice,637 UNQ_ASSET_ID_ON_ASTAR,638 alice.address,639 UNQ_MINIMAL_BALANCE_ON_ASTAR,640 );641642 await helper.assets.setMetadata(643 alice,644 UNQ_ASSET_ID_ON_ASTAR,645 'Cross chain UNQ',646 'xcUNQ',647 Number(UNQ_DECIMALS),648 );649650 console.log('2. Register asset location on Astar');651 const assetLocation = {652 V2: {653 parents: 1,654 interior: {655 X1: {656 Parachain: UNIQUE_CHAIN,657 },658 },659 },660 };661662 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);663664 console.log('3. Set UNQ payment for XCM execution on Astar');665 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);666 } else {667 console.log('UNQ is already registered on Astar');668 }669 console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');670 await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);671 });672 });673674 itSub('Should connect and send UNQ to Astar', async () => {675 await genericSendUnqTo('astar', randomAccount);676 });677678 itSub('Should connect to Astar and send UNQ back', async () => {679 await genericSendUnqBack('astar', alice, randomAccount);680 });681682 itSub('Astar can send only up to its balance', async () => {683 await genericSendOnlyOwnedBalance('astar', alice);684 });685686 itSub('Should not accept reserve transfer of UNQ from Astar', async () => {687 await genericReserveTransferUNQfrom('astar', alice);688 });689});tests/src/xcm/xcm.types.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/xcm/xcm.types.ts
@@ -0,0 +1,85 @@
+import {usingAcalaPlaygrounds, usingAstarPlaygrounds, usingMoonbeamPlaygrounds, usingPolkadexPlaygrounds} from '../util';
+import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';
+import config from '../config';
+
+export const UNIQUE_CHAIN = +(process.env.RELAY_UNIQUE_ID || 2037);
+export const STATEMINT_CHAIN = +(process.env.RELAY_STATEMINT_ID || 1000);
+export const ACALA_CHAIN = +(process.env.RELAY_ACALA_ID || 2000);
+export const MOONBEAM_CHAIN = +(process.env.RELAY_MOONBEAM_ID || 2004);
+export const ASTAR_CHAIN = +(process.env.RELAY_ASTAR_ID || 2006);
+export const POLKADEX_CHAIN = +(process.env.RELAY_POLKADEX_ID || 2040);
+
+export const acalaUrl = config.acalaUrl;
+export const moonbeamUrl = config.moonbeamUrl;
+export const astarUrl = config.astarUrl;
+export const polkadexUrl = config.polkadexUrl;
+
+export const SAFE_XCM_VERSION = 3;
+
+export const maxWaitBlocks = 6;
+
+
+export const ASTAR_DECIMALS = 18n;
+export const UNQ_DECIMALS = 18n;
+
+export const uniqueMultilocation = {
+ parents: 1,
+ interior: {
+ X1: {
+ Parachain: UNIQUE_CHAIN,
+ },
+ },
+};
+export const uniqueVersionedMultilocation = {
+ V3: uniqueMultilocation,
+};
+
+export const uniqueAssetId = {
+ Concrete: uniqueMultilocation,
+};
+
+export const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {
+ await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash
+ && event.outcome.isFailedToTransactAsset);
+};
+export const expectUntrustedReserveLocationFail = async (helper: DevUniqueHelper, messageSent: any) => {
+ await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash
+ && event.outcome.isUntrustedReserveLocation);
+};
+
+export const NETWORKS = {
+ acala: usingAcalaPlaygrounds,
+ astar: usingAstarPlaygrounds,
+ polkadex: usingPolkadexPlaygrounds,
+ moonbeam: usingMoonbeamPlaygrounds,
+} as const;
+
+export function mapToChainId(networkName: keyof typeof NETWORKS) {
+ switch (networkName) {
+ case 'acala':
+ return ACALA_CHAIN;
+ case 'astar':
+ return ASTAR_CHAIN;
+ case 'moonbeam':
+ return MOONBEAM_CHAIN;
+ case 'polkadex':
+ return POLKADEX_CHAIN;
+ }
+}
+
+export function mapToChainUrl(networkName: keyof typeof NETWORKS): string {
+ switch (networkName) {
+ case 'acala':
+ return acalaUrl;
+ case 'astar':
+ return astarUrl;
+ case 'moonbeam':
+ return moonbeamUrl;
+ case 'polkadex':
+ return polkadexUrl;
+ }
+}
+
+export function getDevPlayground<T extends keyof typeof NETWORKS>(name: T) {
+ return NETWORKS[name];
+}
\ No newline at end of file
tests/src/xcm/xcmUnique.test.tsdiffbeforeafterboth--- a/tests/src/xcm/xcmUnique.test.ts
+++ b/tests/src/xcm/xcmUnique.test.ts
@@ -17,15 +17,10 @@
import {IKeyringPair} from '@polkadot/types/types';
import config from '../config';
import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds, usingStatemintPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds} from '../util';
-import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';
+import {Event} from '../util/playgrounds/unique.dev';
import {hexToString, nToBigInt} from '@polkadot/util';
+import {ACALA_CHAIN, ASTAR_CHAIN, MOONBEAM_CHAIN, POLKADEX_CHAIN, SAFE_XCM_VERSION, STATEMINT_CHAIN, UNIQUE_CHAIN, expectFailedToTransact, expectUntrustedReserveLocationFail, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types';
-const UNIQUE_CHAIN = +(process.env.RELAY_UNIQUE_ID || 2037);
-const STATEMINT_CHAIN = +(process.env.RELAY_STATEMINT_ID || 1000);
-const ACALA_CHAIN = +(process.env.RELAY_ACALA_ID || 2000);
-const MOONBEAM_CHAIN = +(process.env.RELAY_MOONBEAM_ID || 2004);
-const ASTAR_CHAIN = +(process.env.RELAY_ASTAR_ID || 2006);
-const POLKADEX_CHAIN = +(process.env.RELAY_POLKADEX_ID || 2040);
const STATEMINT_PALLET_INSTANCE = 50;
@@ -54,29 +49,7 @@
const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';
const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;
const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;
-
-const SAFE_XCM_VERSION = 2;
-const maxWaitBlocks = 6;
-
-const uniqueMultilocation = {
- V2: {
- parents: 1,
- interior: {
- X1: {
- Parachain: UNIQUE_CHAIN,
- },
- },
- },
-};
-const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {
- await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash
- && event.outcome.isFailedToTransactAsset);
-};
-const expectUntrustedReserveLocationFail = async (helper: DevUniqueHelper, messageSent: any) => {
- await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash
- && event.outcome.isUntrustedReserveLocation);
-};
describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => {
let alice: IKeyringPair;
let bob: IKeyringPair;
@@ -704,7 +677,7 @@
// Try to trick Unique
await usingAcalaPlaygrounds(acalaUrl, async (helper) => {
- await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);
+ await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);
maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
});
@@ -729,7 +702,7 @@
);
await usingAcalaPlaygrounds(acalaUrl, async (helper) => {
- await helper.getSudo().xcm.send(alice, uniqueMultilocation, validXcmProgram);
+ await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, validXcmProgram);
});
await helper.wait.newBlocks(maxWaitBlocks);
@@ -819,17 +792,6 @@
let balanceUniqueTokenMiddle: bigint;
let balanceUniqueTokenFinal: bigint;
const maxWaitBlocks = 6;
-
- const uniqueAssetId = {
- Concrete: {
- parents: 1,
- interior: {
- X1: {
- Parachain: UNIQUE_CHAIN,
- },
- },
- },
- };
before(async () => {
await usingPlaygrounds(async (helper, privateKey) => {
@@ -850,7 +812,9 @@
if it is added again. Needed for debugging
when this test is run multiple times.
*/
- if(!isWhitelisted) {
+ if(isWhitelisted) {
+ console.log('UNQ token is already whitelisted on Polkadex');
+ } else {
await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);
}
@@ -951,7 +915,7 @@
await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {
- await helper.getSudo().xcm.send(alice, uniqueMultilocation, xcmProgram);
+ await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, xcmProgram);
xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
});
@@ -986,7 +950,7 @@
await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {
- await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);
+ await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);
maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
});
@@ -1465,7 +1429,7 @@
// Try to trick Unique
await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {
- const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgram]);
+ const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]);
// Needed to bypass the call filter.
const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
@@ -1494,7 +1458,7 @@
);
await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {
- const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, validXcmProgram]);
+ const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, validXcmProgram]);
// Needed to bypass the call filter.
const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
@@ -1543,7 +1507,7 @@
// Try to trick Unique using full UNQ identification
await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {
- const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramFullId]);
+ const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);
// Needed to bypass the call filter.
const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
@@ -1560,7 +1524,7 @@
// Try to trick Unique using shortened UNQ identification
await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {
- const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramHereId]);
+ const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]);
// Needed to bypass the call filter.
const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
@@ -1641,6 +1605,8 @@
console.log('3. Set UNQ payment for XCM execution on Astar');
await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);
+ } else {
+ console.log('UNQ is already registered on Astar');
}
console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');
await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);
@@ -1815,7 +1781,7 @@
// Try to trick Unique
await usingAstarPlaygrounds(astarUrl, async (helper) => {
- await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);
+ await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);
maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
});
@@ -1840,7 +1806,7 @@
);
await usingAstarPlaygrounds(astarUrl, async (helper) => {
- await helper.getSudo().xcm.send(alice, uniqueMultilocation, validXcmProgram);
+ await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, validXcmProgram);
});
await helper.wait.newBlocks(maxWaitBlocks);
@@ -1885,7 +1851,7 @@
// Try to trick Unique using full UNQ identification
await usingAstarPlaygrounds(astarUrl, async (helper) => {
- await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);
+ await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgramFullId);
maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
});
@@ -1898,7 +1864,7 @@
// Try to trick Unique using shortened UNQ identification
await usingAstarPlaygrounds(astarUrl, async (helper) => {
- await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);
+ await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgramHereId);
maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
});