1234567891011121314151617import {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 109110111112113114115116117118 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 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 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 265 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 271 else if('fastDemocracy' in helper) {272 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);273 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 288 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 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 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 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 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 432433434435436437 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});469470471472describeXCM('[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 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 503 let alice: IKeyringPair;504 let uniqueAssetLocation;505506 let randomAccountUnique: IKeyringPair;507 let randomAccountMoonbeam: IKeyringPair;508509 510 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 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 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 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 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 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 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 617 const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); 618 const unitsPerSecond = 228_000_000_000n; 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 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 635 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});