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 uniqueAssetId,141 SENDBACK_AMOUNT,142 );143144 let xcmProgramSent: any;145146147 await targetPlayground(networkUrl, async (helper) => {148 if('getSudo' in helper) {149 await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, xcmProgram);150 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);151 } else if('fastDemocracy' in helper) {152 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, xcmProgram]);153 154 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);155 await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);156 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);157 }158 });159160 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);161162 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountOnUnq.address);163164 expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees - STAYED_ON_TARGET_CHAIN);165166 });167}168169async function genericSendOnlyOwnedBalance(170 networkName: keyof typeof NETWORKS,171 sudoer: IKeyringPair,172) {173 const networkUrl = mapToChainUrl(networkName);174 const targetPlayground = getDevPlayground(networkName);175176 const targetChainBalance = 10000n * (10n ** UNQ_DECIMALS);177178 await usingPlaygrounds(async (helper) => {179 const targetChainSovereignAccount = helper.address.paraSiblingSovereignAccount(mapToChainId(networkName));180 await helper.getSudo().balance.setBalanceSubstrate(sudoer, targetChainSovereignAccount, targetChainBalance);181 const moreThanTargetChainHas = 2n * targetChainBalance;182183 const targetAccount = helper.arrange.createEmptyAccount();184185 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(186 targetAccount.addressRaw,187 {188 Concrete: {189 parents: 0,190 interior: 'Here',191 },192 },193 moreThanTargetChainHas,194 );195196 let maliciousXcmProgramSent: any;197198199 await targetPlayground(networkUrl, async (helper) => {200 if('getSudo' in helper) {201 await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgram);202 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);203 } else if('fastDemocracy' in helper) {204 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]);205 206 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);207 await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);208 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);209 }210 });211212 await expectFailedToTransact(helper, maliciousXcmProgramSent);213214 const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);215 expect(targetAccountBalance).to.be.equal(0n);216 });217}218219async function genericReserveTransferUNQfrom(netwokrName: keyof typeof NETWORKS, sudoer: IKeyringPair) {220 const networkUrl = mapToChainUrl(netwokrName);221 const targetPlayground = getDevPlayground(netwokrName);222223 await usingPlaygrounds(async (helper) => {224 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);225 const targetAccount = helper.arrange.createEmptyAccount();226227 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(228 targetAccount.addressRaw,229 uniqueAssetId,230 testAmount,231 );232233 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(234 targetAccount.addressRaw,235 {236 Concrete: {237 parents: 0,238 interior: 'Here',239 },240 },241 testAmount,242 );243244 let maliciousXcmProgramFullIdSent: any;245 let maliciousXcmProgramHereIdSent: any;246 const maxWaitBlocks = 3;247248 249 await targetPlayground(networkUrl, async (helper) => {250 if('getSudo' in helper) {251 await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramFullId);252 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);253 }254 255 else if('fastDemocracy' in helper) {256 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);257 258 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);259 await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using path asset identification`,batchCall);260261 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);262 }263 });264265266 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);267268 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);269 expect(accountBalance).to.be.equal(0n);270271 272 await targetPlayground(networkUrl, async (helper) => {273 if('getSudo' in helper) {274 await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramHereId);275 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);276 }277 else if('fastDemocracy' in helper) {278 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]);279 280 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);281 await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using "here" asset identification`, batchCall);282283 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);284 }285 });286287 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);288289 accountBalance = await helper.balance.getSubstrate(targetAccount.address);290 expect(accountBalance).to.be.equal(0n);291 });292}293294async function genericRejectNativeToknsFrom(networkName: keyof typeof NETWORKS, sudoerOnTargetChain: IKeyringPair) {295 const networkUrl = mapToChainUrl(networkName);296 const targetPlayground = getDevPlayground(networkName);297 let messageSent: any;298299 await usingPlaygrounds(async (helper) => {300 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(301 helper.arrange.createEmptyAccount().addressRaw,302 {303 Concrete: {304 parents: 1,305 interior: {306 X1: {307 Parachain: mapToChainId(networkName),308 },309 },310 },311 },312 TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT,313 );314 await targetPlayground(networkUrl, async (helper) => {315 if('getSudo' in helper) {316 await helper.getSudo().xcm.send(sudoerOnTargetChain, uniqueVersionedMultilocation, maliciousXcmProgramFullId);317 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);318 } else if('fastDemocracy' in helper) {319 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);320 321 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);322 await helper.fastDemocracy.executeProposal(`${networkName} sending native tokens to the Unique via fast democracy`, batchCall);323324 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);325 }326 });327 await expectFailedToTransact(helper, messageSent);328 });329}330331332describeXCM('[XCMLL] Integration test: Exchanging tokens with Acala', () => {333 let alice: IKeyringPair;334 let randomAccount: IKeyringPair;335336 before(async () => {337 await usingPlaygrounds(async (helper, privateKey) => {338 alice = await privateKey('//Alice');339 console.log(config.acalaUrl);340 randomAccount = helper.arrange.createEmptyAccount();341342 343 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);344 });345346 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {347 const destination = {348 V2: {349 parents: 1,350 interior: {351 X1: {352 Parachain: UNIQUE_CHAIN,353 },354 },355 },356 };357358 const metadata = {359 name: 'Unique Network',360 symbol: 'UNQ',361 decimals: 18,362 minimalBalance: 1250_000_000_000_000_000n,363 };364 const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v] : [any, any]) =>365 hexToString(v.toJSON()['symbol'])) as string[];366367 if(!assets.includes('UNQ')) {368 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);369 } else {370 console.log('UNQ token already registered on Acala assetRegistry pallet');371 }372 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);373 });374375 await usingPlaygrounds(async (helper) => {376 await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);377 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);378 });379 });380381 itSub('Should connect and send UNQ to Acala', async () => {382 await genericSendUnqTo('acala', randomAccount);383 });384385 itSub('Should connect to Acala and send UNQ back', async () => {386 await genericSendUnqBack('acala', alice, randomAccount);387 });388389 itSub('Acala can send only up to its balance', async () => {390 await genericSendOnlyOwnedBalance('acala', alice);391 });392393 itSub('Should not accept reserve transfer of UNQ from Acala', async () => {394 await genericReserveTransferUNQfrom('acala', alice);395 });396});397398describeXCM('[XCMLL] Integration test: Exchanging tokens with Polkadex', () => {399 let alice: IKeyringPair;400 let randomAccount: IKeyringPair;401402 before(async () => {403 await usingPlaygrounds(async (helper, privateKey) => {404 alice = await privateKey('//Alice');405 randomAccount = helper.arrange.createEmptyAccount();406407 408 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);409 });410411 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {412 const isWhitelisted = ((await helper.callRpc('api.query.xcmHelper.whitelistedTokens', []))413 .toJSON() as [])414 .map(nToBigInt).length != 0;415 416417418419420421 if(isWhitelisted) {422 console.log('UNQ token is already whitelisted on Polkadex');423 } else {424 await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);425 }426427 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);428 });429430 await usingPlaygrounds(async (helper) => {431 await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);432 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);433 });434 });435436 itSub('Should connect and send UNQ to Polkadex', async () => {437 await genericSendUnqTo('polkadex', randomAccount);438 });439440441 itSub('Should connect to Polkadex and send UNQ back', async () => {442 await genericSendUnqBack('polkadex', alice, randomAccount);443 });444445 itSub('Polkadex can send only up to its balance', async () => {446 await genericSendOnlyOwnedBalance('polkadex', alice);447 });448449 itSub('Should not accept reserve transfer of UNQ from Polkadex', async () => {450 await genericReserveTransferUNQfrom('polkadex', alice);451 });452});453454455456describeXCM('[XCMLL] Integration test: Unique rejects non-native tokens', () => {457 let alice: IKeyringPair;458459 before(async () => {460 await usingPlaygrounds(async (helper, privateKey) => {461 alice = await privateKey('//Alice');462463 464 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);465 });466 });467468 itSub('Unique rejects ACA tokens from Acala', async () => {469 await genericRejectNativeToknsFrom('acala', alice);470 });471472 itSub('Unique rejects GLMR tokens from Moonbeam', async () => {473 await genericRejectNativeToknsFrom('moonbeam', alice);474 });475476 itSub('Unique rejects ASTR tokens from Astar', async () => {477 await genericRejectNativeToknsFrom('astar', alice);478 });479480 itSub('Unique rejects PDX tokens from Polkadex', async () => {481 await genericRejectNativeToknsFrom('polkadex', alice);482 });483});484485describeXCM('[XCMLL] Integration test: Exchanging UNQ with Moonbeam', () => {486 487 let alice: IKeyringPair;488 let uniqueAssetLocation;489490 let randomAccountUnique: IKeyringPair;491 let randomAccountMoonbeam: IKeyringPair;492493 494 let assetId: string;495496 const uniqueAssetMetadata = {497 name: 'xcUnique',498 symbol: 'xcUNQ',499 decimals: 18,500 isFrozen: false,501 minimalBalance: 1n,502 };503504505 before(async () => {506 await usingPlaygrounds(async (helper, privateKey) => {507 alice = await privateKey('//Alice');508 [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);509510511 512 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);513 });514515 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {516 const alithAccount = helper.account.alithAccount();517 const baltatharAccount = helper.account.baltatharAccount();518 const dorothyAccount = helper.account.dorothyAccount();519520 randomAccountMoonbeam = helper.account.create();521522 523 console.log('Sponsoring Dorothy.......');524 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);525 console.log('Sponsoring Dorothy.......DONE');526 527 uniqueAssetLocation = {528 XCM: {529 parents: 1,530 interior: {X1: {Parachain: UNIQUE_CHAIN}},531 },532 };533 const existentialDeposit = 1n;534 const isSufficient = true;535 const unitsPerSecond = 1n;536 const numAssetsWeightHint = 0;537538 if((await helper.assetManager.assetTypeId(uniqueAssetLocation)).toJSON()) {539 console.log('Unique asset already registered on Moonbeam');540 } else {541 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({542 location: uniqueAssetLocation,543 metadata: uniqueAssetMetadata,544 existentialDeposit,545 isSufficient,546 unitsPerSecond,547 numAssetsWeightHint,548 });549550 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);551552 await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);553 }554555 556 console.log('Acquire Unique AssetId Info on Moonbeam.......');557558 assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();559560 console.log('UNQ asset ID is %s', assetId);561 console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');562563 564 console.log('Sponsoring random Account.......');565 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);566 console.log('Sponsoring random Account.......DONE');567 568 });569570 await usingPlaygrounds(async (helper) => {571 await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, SENDER_BUDGET);572 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);573 });574 });575576 itSub('Should connect and send UNQ to Moonbeam', async () => {577 await genericSendUnqTo('moonbeam', randomAccountUnique, randomAccountMoonbeam);578 });579580 itSub('Should connect to Moonbeam and send UNQ back', async () => {581 await genericSendUnqBack('moonbeam', alice, randomAccountUnique);582 });583584 itSub('Moonbeam can send only up to its balance', async () => {585 await genericSendOnlyOwnedBalance('moonbeam', alice);586 });587588 itSub('Should not accept reserve transfer of UNQ from Moonbeam', async () => {589 await genericReserveTransferUNQfrom('moonbeam', alice);590 });591});592593describeXCM('[XCMLL] Integration test: Exchanging tokens with Astar', () => {594 let alice: IKeyringPair;595 let randomAccount: IKeyringPair;596597 const UNQ_ASSET_ID_ON_ASTAR = 1;598 const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n;599600 601 const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); 602 const unitsPerSecond = 228_000_000_000n; 603604 before(async () => {605 await usingPlaygrounds(async (helper, privateKey) => {606 alice = await privateKey('//Alice');607 randomAccount = helper.arrange.createEmptyAccount();608 await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);609 console.log('randomAccount', randomAccount.address);610611 612 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);613 });614615 await usingAstarPlaygrounds(astarUrl, async (helper) => {616 if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {617 console.log('1. Create foreign asset and metadata');618 619 await helper.assets.create(620 alice,621 UNQ_ASSET_ID_ON_ASTAR,622 alice.address,623 UNQ_MINIMAL_BALANCE_ON_ASTAR,624 );625626 await helper.assets.setMetadata(627 alice,628 UNQ_ASSET_ID_ON_ASTAR,629 'Cross chain UNQ',630 'xcUNQ',631 Number(UNQ_DECIMALS),632 );633634 console.log('2. Register asset location on Astar');635 const assetLocation = {636 V2: {637 parents: 1,638 interior: {639 X1: {640 Parachain: UNIQUE_CHAIN,641 },642 },643 },644 };645646 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);647648 console.log('3. Set UNQ payment for XCM execution on Astar');649 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);650 } else {651 console.log('UNQ is already registered on Astar');652 }653 console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');654 await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);655 });656 });657658 itSub('Should connect and send UNQ to Astar', async () => {659 await genericSendUnqTo('astar', randomAccount);660 });661662 itSub('Should connect to Astar and send UNQ back', async () => {663 await genericSendUnqBack('astar', alice, randomAccount);664 });665666 itSub('Astar can send only up to its balance', async () => {667 await genericSendOnlyOwnedBalance('astar', alice);668 });669670 itSub('Should not accept reserve transfer of UNQ from Astar', async () => {671 await genericReserveTransferUNQfrom('astar', alice);672 });673});