1234567891011121314151617import {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 183184185186187188189190191192 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 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 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 339 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 345 else if('fastDemocracy' in helper) {346 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramFullId]);347348 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 363 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 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 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 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 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 521522523524525526 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});556557558559describeXCM('[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 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 590 let alice: IKeyringPair;591 let uniqueAssetLocation;592593 let randomAccountUnique: IKeyringPair;594 let randomAccountMoonbeam: IKeyringPair;595596 597 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 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 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 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 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 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 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 704 const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); 705 const unitsPerSecond = 228_000_000_000n; 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 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 722 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});