1234567891011121314151617import type {IKeyringPair} from '@polkadot/types/types';18import config from '../config.js';19import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds, usingStatemintPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds} from '@unique/test-utils/util.js';20import {Event} from '@unique/test-utils';21import {hexToString, nToBigInt} from '@polkadot/util';22import {ACALA_CHAIN, ASTAR_CHAIN, MOONBEAM_CHAIN, POLKADEX_CHAIN, SAFE_XCM_VERSION, STATEMINT_CHAIN, UNIQUE_CHAIN, expectFailedToTransact, expectUntrustedReserveLocationFail, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types.js';23import {XcmTestHelper} from './xcm.types.js';2425const STATEMINT_PALLET_INSTANCE = 50;2627const relayUrl = config.relayUrl;28const statemintUrl = config.statemintUrl;29const acalaUrl = config.acalaUrl;30const moonbeamUrl = config.moonbeamUrl;31const astarUrl = config.astarUrl;32const polkadexUrl = config.polkadexUrl;3334const RELAY_DECIMALS = 12;35const STATEMINT_DECIMALS = 12;36const ACALA_DECIMALS = 12;37const ASTAR_DECIMALS = 18n;38const UNQ_DECIMALS = 18n;3940const TRANSFER_AMOUNT = 2000000000000000000000000n;4142const FUNDING_AMOUNT = 3_500_000_0000_000_000n;4344const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;4546const USDT_ASSET_ID = 100;47const USDT_ASSET_METADATA_DECIMALS = 18;48const USDT_ASSET_METADATA_NAME = 'USDT';49const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';50const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;51const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;5253const testHelper = new XcmTestHelper('unique');5455describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => {56 let alice: IKeyringPair;57 let bob: IKeyringPair;5859 let balanceStmnBefore: bigint;60 let balanceStmnAfter: bigint;6162 let balanceUniqueBefore: bigint;63 let balanceUniqueAfter: bigint;64 let balanceUniqueFinal: bigint;6566 let balanceBobBefore: bigint;67 let balanceBobAfter: bigint;68 let balanceBobFinal: bigint;6970 let balanceBobRelayTokenBefore: bigint;71 let balanceBobRelayTokenAfter: bigint;7273 let usdtCollectionId: number;74 let relayCollectionId: number;7576 before(async () => {77 await usingPlaygrounds(async (helper, privateKey) => {78 alice = await privateKey('//Alice');79 bob = await privateKey('//Bob'); 8081 82 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);8384 relayCollectionId = await testHelper.registerRelayNativeTokenOnUnique(alice);85 });8687 await usingRelayPlaygrounds(relayUrl, async (helper) => {88 89 await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, alice.addressRaw, FUNDING_AMOUNT);90 await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, bob.addressRaw, FUNDING_AMOUNT);91 });9293 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {94 const assetInfo = await helper.assets.assetInfo(USDT_ASSET_ID);95 if(assetInfo == null) {96 await helper.assets.create(97 alice,98 USDT_ASSET_ID,99 alice.address,100 USDT_ASSET_METADATA_MINIMAL_BALANCE,101 );102 await helper.assets.setMetadata(103 alice,104 USDT_ASSET_ID,105 USDT_ASSET_METADATA_NAME,106 USDT_ASSET_METADATA_DESCRIPTION,107 USDT_ASSET_METADATA_DECIMALS,108 );109 } else {110 console.log('The USDT asset is already registered on AssetHub');111 }112113 await helper.assets.mint(114 alice,115 USDT_ASSET_ID,116 alice.address,117 USDT_ASSET_AMOUNT,118 );119120 const sovereignFundingAmount = 3_500_000_000n;121122 123 124 125 const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(UNIQUE_CHAIN);126 await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);127 });128129130 await usingPlaygrounds(async (helper) => {131 const location = {132 parents: 1,133 interior: {X3: [134 {135 Parachain: STATEMINT_CHAIN,136 },137 {138 PalletInstance: STATEMINT_PALLET_INSTANCE,139 },140 {141 GeneralIndex: USDT_ASSET_ID,142 },143 ]},144 };145 const assetId = {Concrete: location};146147 if(await helper.foreignAssets.foreignCollectionId(assetId) == null) {148 const tokenPrefix = USDT_ASSET_METADATA_NAME;149 await helper.getSudo().foreignAssets.register(alice, assetId, USDT_ASSET_METADATA_NAME, tokenPrefix, {Fungible: USDT_ASSET_METADATA_DECIMALS});150 } else {151 console.log('Foreign collection is already registered on Unique');152 }153154 balanceUniqueBefore = await helper.balance.getSubstrate(alice.address);155 usdtCollectionId = await helper.foreignAssets.foreignCollectionId(assetId);156 });157158159 160 161 await usingRelayPlaygrounds(relayUrl, async (helper) => {162 const destination = {163 V2: {164 parents: 0,165 interior: {X1: {166 Parachain: UNIQUE_CHAIN,167 },168 },169 }};170171 const beneficiary = {172 V2: {173 parents: 0,174 interior: {X1: {175 AccountId32: {176 network: 'Any',177 id: alice.addressRaw,178 },179 }},180 },181 };182183 const assets = {184 V2: [185 {186 id: {187 Concrete: {188 parents: 0,189 interior: 'Here',190 },191 },192 fun: {193 Fungible: TRANSFER_AMOUNT_RELAY,194 },195 },196 ],197 };198199 const feeAssetItem = 0;200201 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');202 });203204 });205206 itSub('Should connect and send USDT from Statemint to Unique', async ({helper}) => {207 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {208 const dest = {209 V2: {210 parents: 1,211 interior: {X1: {212 Parachain: UNIQUE_CHAIN,213 },214 },215 }};216217 const beneficiary = {218 V2: {219 parents: 0,220 interior: {X1: {221 AccountId32: {222 network: 'Any',223 id: alice.addressRaw,224 },225 }},226 },227 };228229 const assets = {230 V2: [231 {232 id: {233 Concrete: {234 parents: 0,235 interior: {236 X2: [237 {238 PalletInstance: STATEMINT_PALLET_INSTANCE,239 },240 {241 GeneralIndex: USDT_ASSET_ID,242 },243 ]},244 },245 },246 fun: {247 Fungible: TRANSFER_AMOUNT,248 },249 },250 ],251 };252253 const feeAssetItem = 0;254255 balanceStmnBefore = await helper.balance.getSubstrate(alice.address);256 await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');257258 balanceStmnAfter = await helper.balance.getSubstrate(alice.address);259260 261 console.log(262 '[Statemint -> Unique] transaction fees on Statemint: %s WND',263 helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINT_DECIMALS),264 );265 expect(balanceStmnBefore > balanceStmnAfter).to.be.true;266267 });268269270 271 await helper.wait.newBlocks(3);272273 const free = await helper.ft.getBalance(usdtCollectionId, {Substrate: alice.address});274275 balanceUniqueAfter = await helper.balance.getSubstrate(alice.address);276277 console.log(278 '[Statemint -> Unique] transaction fees on Unique: %s USDT',279 helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),280 );281 console.log(282 '[Statemint -> Unique] transaction fees on Unique: %s UNQ',283 helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueBefore),284 );285 286 expect(free).to.be.equal(TRANSFER_AMOUNT);287 288 expect(balanceUniqueAfter == balanceUniqueBefore).to.be.true;289 });290291 itSub('Should connect and send USDT from Unique to Statemint back', async ({helper}) => {292 const destination = {293 V2: {294 parents: 1,295 interior: {X2: [296 {297 Parachain: STATEMINT_CHAIN,298 },299 {300 AccountId32: {301 network: 'Any',302 id: alice.addressRaw,303 },304 },305 ]},306 },307 };308309 const relayFee = 400_000_000_000_000n;310 const currencies: [any, bigint][] = [311 [312 usdtCollectionId,313 TRANSFER_AMOUNT,314 ],315 [316 relayCollectionId,317 relayFee,318 ],319 ];320321 const feeItem = 1;322323 await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');324325 326 balanceUniqueFinal = await helper.balance.getSubstrate(alice.address);327 console.log('[Unique -> Statemint] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueFinal));328 expect(balanceUniqueAfter > balanceUniqueFinal).to.be.true;329330 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {331 await helper.wait.newBlocks(3);332333 334 335 expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;336 });337 });338339 itSub('Should connect and send Relay token to Unique', async ({helper}) => {340 balanceBobBefore = await helper.balance.getSubstrate(bob.address);341 balanceBobRelayTokenBefore = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});342343 await usingRelayPlaygrounds(relayUrl, async (helper) => {344 const destination = {345 V2: {346 parents: 0,347 interior: {X1: {348 Parachain: UNIQUE_CHAIN,349 },350 },351 }};352353 const beneficiary = {354 V2: {355 parents: 0,356 interior: {X1: {357 AccountId32: {358 network: 'Any',359 id: bob.addressRaw,360 },361 }},362 },363 };364365 const assets = {366 V2: [367 {368 id: {369 Concrete: {370 parents: 0,371 interior: 'Here',372 },373 },374 fun: {375 Fungible: TRANSFER_AMOUNT_RELAY,376 },377 },378 ],379 };380381 const feeAssetItem = 0;382383 await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');384 });385386 await helper.wait.newBlocks(3);387388 balanceBobAfter = await helper.balance.getSubstrate(bob.address);389 balanceBobRelayTokenAfter = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});390391 const wndFeeOnUnique = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;392 const wndDiffOnUnique = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;393 console.log(394 '[Relay (Westend) -> Unique] transaction fees: %s UNQ',395 helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),396 );397 console.log(398 '[Relay (Westend) -> Unique] transaction fees: %s WND',399 helper.util.bigIntToDecimals(wndFeeOnUnique, STATEMINT_DECIMALS),400 );401 console.log('[Relay (Westend) -> Unique] actually delivered: %s WND', wndDiffOnUnique);402 expect(wndFeeOnUnique == 0n, 'No incoming WND fees should be taken').to.be.true;403 expect(balanceBobBefore == balanceBobAfter, 'No incoming UNQ fees should be taken').to.be.true;404 });405406 itSub('Should connect and send Relay token back', async ({helper}) => {407 let relayTokenBalanceBefore: bigint;408 let relayTokenBalanceAfter: bigint;409 await usingRelayPlaygrounds(relayUrl, async (helper) => {410 relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);411 });412413 const destination = {414 V2: {415 parents: 1,416 interior: {417 X1:{418 AccountId32: {419 network: 'Any',420 id: bob.addressRaw,421 },422 },423 },424 },425 };426427 const currencies: any = [428 [429 relayCollectionId,430 TRANSFER_AMOUNT_RELAY,431 ],432 ];433434 const feeItem = 0;435436 await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');437438 balanceBobFinal = await helper.balance.getSubstrate(bob.address);439 console.log('[Unique -> Relay (Westend)] transaction fees: %s UNQ', helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));440441 await usingRelayPlaygrounds(relayUrl, async (helper) => {442 await helper.wait.newBlocks(10);443 relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);444445 const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;446 console.log('[Unique -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));447 expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;448 });449 });450});451452describeXCM('[XCM] Integration test: Exchanging tokens with Acala', () => {453 let alice: IKeyringPair;454 let randomAccount: IKeyringPair;455456 let balanceUniqueTokenInit: bigint;457 let balanceUniqueTokenMiddle: bigint;458 let balanceUniqueTokenFinal: bigint;459 let balanceAcalaTokenInit: bigint;460 let balanceAcalaTokenMiddle: bigint;461 let balanceAcalaTokenFinal: bigint;462 let balanceUniqueForeignTokenInit: bigint;463 let balanceUniqueForeignTokenMiddle: bigint;464 let balanceUniqueForeignTokenFinal: bigint;465466 467 468 469 const expectedAcalaIncomeFee = 2000000000000000000n - 1898970000000000000n;470 const acalaEps = 8n * 10n ** 16n;471472 let acalaBackwardTransferAmount: bigint;473474 before(async () => {475 await usingPlaygrounds(async (helper, privateKey) => {476 alice = await privateKey('//Alice');477 [randomAccount] = await helper.arrange.createAccounts([0n], alice);478479 480 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);481 });482483 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {484 const destination = {485 V2: {486 parents: 1,487 interior: {488 X1: {489 Parachain: UNIQUE_CHAIN,490 },491 },492 },493 };494495 const metadata = {496 name: 'Unique Network',497 symbol: 'UNQ',498 decimals: 18,499 minimalBalance: 1250000000000000000n,500 };501 const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v] : [any, any]) =>502 hexToString(v.toJSON()['symbol'])) as string[];503504 if(!assets.includes('UNQ')) {505 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);506 } else {507 console.log('UNQ token already registered on Acala assetRegistry pallet');508 }509 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);510 balanceAcalaTokenInit = await helper.balance.getSubstrate(randomAccount.address);511 balanceUniqueForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});512 });513514 await usingPlaygrounds(async (helper) => {515 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);516 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);517 });518 });519520 itSub('Should connect and send UNQ to Acala', async ({helper}) => {521522 const destination = {523 V2: {524 parents: 1,525 interior: {526 X1: {527 Parachain: ACALA_CHAIN,528 },529 },530 },531 };532533 const beneficiary = {534 V2: {535 parents: 0,536 interior: {537 X1: {538 AccountId32: {539 network: 'Any',540 id: randomAccount.addressRaw,541 },542 },543 },544 },545 };546547 const assets = {548 V2: [549 {550 id: {551 Concrete: {552 parents: 0,553 interior: 'Here',554 },555 },556 fun: {557 Fungible: TRANSFER_AMOUNT,558 },559 },560 ],561 };562563 const feeAssetItem = 0;564565 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');566 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);567568 const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;569 console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));570 expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;571572 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {573 await helper.wait.newBlocks(3);574575 balanceUniqueForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});576 balanceAcalaTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);577578 const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle;579 const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit;580 acalaBackwardTransferAmount = unqIncomeTransfer;581582 const acaUnqFees = TRANSFER_AMOUNT - unqIncomeTransfer;583584 console.log(585 '[Unique -> Acala] transaction fees on Acala: %s ACA',586 helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),587 );588 console.log(589 '[Unique -> Acala] transaction fees on Acala: %s UNQ',590 helper.util.bigIntToDecimals(acaUnqFees),591 );592 console.log('[Unique -> Acala] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));593 expect(acaFees == 0n).to.be.true;594595 const bigintAbs = (n: bigint) => (n < 0n) ? -n : n;596597 expect(598 bigintAbs(acaUnqFees - expectedAcalaIncomeFee) < acalaEps,599 'Acala took different income fee, check the Acala foreign asset config',600 ).to.be.true;601 });602 });603604 itSub('Should connect to Acala and send UNQ back', async ({helper}) => {605 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {606 const destination = {607 V2: {608 parents: 1,609 interior: {610 X2: [611 {Parachain: UNIQUE_CHAIN},612 {613 AccountId32: {614 network: 'Any',615 id: randomAccount.addressRaw,616 },617 },618 ],619 },620 },621 };622623 const id = {624 ForeignAsset: 0,625 };626627 await helper.xTokens.transfer(randomAccount, id, acalaBackwardTransferAmount, destination, 'Unlimited');628 balanceAcalaTokenFinal = await helper.balance.getSubstrate(randomAccount.address);629 balanceUniqueForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);630631 const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal;632 const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal;633634 console.log(635 '[Acala -> Unique] transaction fees on Acala: %s ACA',636 helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),637 );638 console.log('[Acala -> Unique] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));639640 expect(acaFees > 0, 'Negative fees ACA, looks like nothing was transferred').to.be.true;641 expect(unqOutcomeTransfer == acalaBackwardTransferAmount).to.be.true;642 });643644 await helper.wait.newBlocks(3);645646 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);647 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;648 expect(actuallyDelivered > 0).to.be.true;649650 console.log('[Acala -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));651652 const unqFees = acalaBackwardTransferAmount - actuallyDelivered;653 console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));654 expect(unqFees == 0n).to.be.true;655 });656657 itSub('Acala can send only up to its balance', async ({helper}) => {658 659 const acalaBalance = 10000n * (10n ** UNQ_DECIMALS);660 const acalaSovereignAccount = helper.address.paraSiblingSovereignAccount(ACALA_CHAIN);661 await helper.getSudo().balance.setBalanceSubstrate(alice, acalaSovereignAccount, acalaBalance);662663 const moreThanAcalaHas = acalaBalance * 2n;664665 let targetAccountBalance = 0n;666 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);667668 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(669 targetAccount.addressRaw,670 {671 Concrete: {672 parents: 0,673 interior: 'Here',674 },675 },676 moreThanAcalaHas,677 );678679 let maliciousXcmProgramSent: any;680 const maxWaitBlocks = 3;681682 683 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {684 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);685686 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);687 });688689 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash690 && event.outcome.isFailedToTransactAsset);691692 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);693 expect(targetAccountBalance).to.be.equal(0n);694695 696 const validTransferAmount = acalaBalance / 2n;697 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(698 targetAccount.addressRaw,699 {700 Concrete: {701 parents: 0,702 interior: 'Here',703 },704 },705 validTransferAmount,706 );707708 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {709 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, validXcmProgram);710 });711712 await helper.wait.newBlocks(maxWaitBlocks);713714 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);715 expect(targetAccountBalance).to.be.equal(validTransferAmount);716 });717718 itSub('Should not accept reserve transfer of UNQ from Acala', async ({helper}) => {719 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);720 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);721722 const uniqueMultilocation = {723 V2: {724 parents: 1,725 interior: {726 X1: {727 Parachain: UNIQUE_CHAIN,728 },729 },730 },731 };732733 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(734 targetAccount.addressRaw,735 uniqueAssetId,736 testAmount,737 );738739 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(740 targetAccount.addressRaw,741 {742 Concrete: {743 parents: 0,744 interior: 'Here',745 },746 },747 testAmount,748 );749750 let maliciousXcmProgramFullIdSent: any;751 let maliciousXcmProgramHereIdSent: any;752 const maxWaitBlocks = 3;753754 755 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {756 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);757758 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);759 });760761 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash762 && event.outcome.isUntrustedReserveLocation);763764 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);765 expect(accountBalance).to.be.equal(0n);766767 768 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {769 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);770771 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);772 });773774 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash775 && event.outcome.isUntrustedReserveLocation);776777 accountBalance = await helper.balance.getSubstrate(targetAccount.address);778 expect(accountBalance).to.be.equal(0n);779 });780});781782describeXCM('[XCM] Integration test: Exchanging tokens with Polkadex', () => {783 let alice: IKeyringPair;784 let randomAccount: IKeyringPair;785 let unqFees: bigint;786 let balanceUniqueTokenInit: bigint;787 let balanceUniqueTokenMiddle: bigint;788 let balanceUniqueTokenFinal: bigint;789 const maxWaitBlocks = 6;790791 before(async () => {792 await usingPlaygrounds(async (helper, privateKey) => {793 alice = await privateKey('//Alice');794 [randomAccount] = await helper.arrange.createAccounts([0n], alice);795796 797 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);798 });799800 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {801 const isWhitelisted = ((await helper.callRpc('api.query.xcmHelper.whitelistedTokens', []))802 .toJSON() as [])803 .map(nToBigInt).length != 0;804 805806807808809810 if(isWhitelisted) {811 console.log('UNQ token is already whitelisted on Polkadex');812 } else {813 await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);814 }815816 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);817 });818819 await usingPlaygrounds(async (helper) => {820 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);821 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);822 });823 });824825 itSub('Should connect and send UNQ to Polkadex', async ({helper}) => {826827 const destination = {828 V2: {829 parents: 1,830 interior: {831 X1: {832 Parachain: POLKADEX_CHAIN,833 },834 },835 },836 };837838 const beneficiary = {839 V2: {840 parents: 0,841 interior: {842 X1: {843 AccountId32: {844 network: 'Any',845 id: randomAccount.addressRaw,846 },847 },848 },849 },850 };851852 const assets = {853 V2: [854 {855 id: {856 Concrete: {857 parents: 0,858 interior: 'Here',859 },860 },861 fun: {862 Fungible: TRANSFER_AMOUNT,863 },864 },865 ],866 };867868 const feeAssetItem = 0;869870 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');871 const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);872 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);873874 unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;875 console.log('[Unique -> Polkadex] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));876 expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;877878 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {879 880881882883884885886887888889 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);890 });891 });892893894 itSub('Should connect to Polkadex and send UNQ back', async ({helper}) => {895896 const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(897 randomAccount.addressRaw,898 uniqueAssetId,899 TRANSFER_AMOUNT,900 );901902 let xcmProgramSent: any;903904905 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {906 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, xcmProgram);907908 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);909 });910911 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);912913 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);914915 expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees);916 });917918 itSub('Polkadex can send only up to its balance', async ({helper}) => {919 const polkadexBalance = 10000n * (10n ** UNQ_DECIMALS);920 const polkadexSovereignAccount = helper.address.paraSiblingSovereignAccount(POLKADEX_CHAIN);921 await helper.getSudo().balance.setBalanceSubstrate(alice, polkadexSovereignAccount, polkadexBalance);922 const moreThanPolkadexHas = 2n * polkadexBalance;923924 const targetAccount = helper.arrange.createEmptyAccount();925926 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(927 targetAccount.addressRaw,928 {929 Concrete: {930 parents: 0,931 interior: 'Here',932 },933 },934 moreThanPolkadexHas,935 );936937 let maliciousXcmProgramSent: any;938939940 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {941 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);942943 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);944 });945946 await expectFailedToTransact(helper, maliciousXcmProgramSent);947948 const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);949 expect(targetAccountBalance).to.be.equal(0n);950 });951952 itSub('Should not accept reserve transfer of UNQ from Polkadex', async ({helper}) => {953 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);954 const targetAccount = helper.arrange.createEmptyAccount();955956 const uniqueMultilocation = {957 V2: {958 parents: 1,959 interior: {960 X1: {961 Parachain: UNIQUE_CHAIN,962 },963 },964 },965 };966967 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(968 targetAccount.addressRaw,969 uniqueAssetId,970 testAmount,971 );972973 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(974 targetAccount.addressRaw,975 {976 Concrete: {977 parents: 0,978 interior: 'Here',979 },980 },981 testAmount,982 );983984 let maliciousXcmProgramFullIdSent: any;985 let maliciousXcmProgramHereIdSent: any;986 const maxWaitBlocks = 3;987988 989 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {990 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);991992 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);993 });994995 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);996997 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);998 expect(accountBalance).to.be.equal(0n);9991000 1001 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1002 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);10031004 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1005 });10061007 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);10081009 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1010 expect(accountBalance).to.be.equal(0n);1011 });1012});1013101410151016describeXCM('[XCM] Integration test: Unique rejects non-native tokens', () => {1017 let alice: IKeyringPair;1018 let alith: IKeyringPair;10191020 const testAmount = 100_000_000_000n;1021 let uniqueParachainJunction;1022 let uniqueAccountJunction;10231024 let uniqueParachainMultilocation: any;1025 let uniqueAccountMultilocation: any;1026 let uniqueCombinedMultilocation: any;1027 let uniqueCombinedMultilocationAcala: any; 10281029 let messageSent: any;10301031 const maxWaitBlocks = 3;10321033 before(async () => {1034 await usingPlaygrounds(async (helper, privateKey) => {1035 alice = await privateKey('//Alice');10361037 uniqueParachainJunction = {Parachain: UNIQUE_CHAIN};1038 uniqueAccountJunction = {1039 AccountId32: {1040 network: 'Any',1041 id: alice.addressRaw,1042 },1043 };10441045 uniqueParachainMultilocation = {1046 V2: {1047 parents: 1,1048 interior: {1049 X1: uniqueParachainJunction,1050 },1051 },1052 };10531054 uniqueAccountMultilocation = {1055 V2: {1056 parents: 0,1057 interior: {1058 X1: uniqueAccountJunction,1059 },1060 },1061 };10621063 uniqueCombinedMultilocation = {1064 V2: {1065 parents: 1,1066 interior: {1067 X2: [uniqueParachainJunction, uniqueAccountJunction],1068 },1069 },1070 };10711072 uniqueCombinedMultilocationAcala = {1073 V2: {1074 parents: 1,1075 interior: {1076 X2: [uniqueParachainJunction, uniqueAccountJunction],1077 },1078 },1079 };10801081 1082 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1083 });10841085 1086 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1087 alith = helper.account.alithAccount();1088 });1089 });1090109110921093 itSub('Unique rejects ACA tokens from Acala', async ({helper}) => {1094 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {1095 const id = {1096 Token: 'ACA',1097 };1098 const destination = uniqueCombinedMultilocationAcala;1099 await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');11001101 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1102 });11031104 await expectFailedToTransact(helper, messageSent);1105 });11061107 itSub('Unique rejects GLMR tokens from Moonbeam', async ({helper}) => {1108 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1109 const id = 'SelfReserve';1110 const destination = uniqueCombinedMultilocation;1111 await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');11121113 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1114 });11151116 await expectFailedToTransact(helper, messageSent);1117 });11181119 itSub('Unique rejects ASTR tokens from Astar', async ({helper}) => {1120 await usingAstarPlaygrounds(astarUrl, async (helper) => {1121 const destinationParachain = uniqueParachainMultilocation;1122 const beneficiary = uniqueAccountMultilocation;1123 const assets = {1124 V2: [{1125 id: {1126 Concrete: {1127 parents: 0,1128 interior: 'Here',1129 },1130 },1131 fun: {1132 Fungible: testAmount,1133 },1134 }],1135 };1136 const feeAssetItem = 0;11371138 await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [1139 destinationParachain,1140 beneficiary,1141 assets,1142 feeAssetItem,1143 ]);11441145 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1146 });11471148 await expectFailedToTransact(helper, messageSent);1149 });11501151 itSub('Unique rejects PDX tokens from Polkadex', async ({helper}) => {11521153 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1154 helper.arrange.createEmptyAccount().addressRaw,1155 {1156 Concrete: {1157 parents: 1,1158 interior: {1159 X1: {1160 Parachain: POLKADEX_CHAIN,1161 },1162 },1163 },1164 },1165 testAmount,1166 );11671168 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1169 await helper.getSudo().xcm.send(alice, uniqueParachainMultilocation, maliciousXcmProgramFullId);1170 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1171 });11721173 await expectFailedToTransact(helper, messageSent);1174 });1175});11761177describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {1178 1179 let alice: IKeyringPair;1180 let uniqueAssetLocation;11811182 let randomAccountUnique: IKeyringPair;1183 let randomAccountMoonbeam: IKeyringPair;11841185 1186 let assetId: string;11871188 const uniqueAssetMetadata = {1189 name: 'xcUnique',1190 symbol: 'xcUNQ',1191 decimals: 18,1192 isFrozen: false,1193 minimalBalance: 1n,1194 };11951196 let balanceUniqueTokenInit: bigint;1197 let balanceUniqueTokenMiddle: bigint;1198 let balanceUniqueTokenFinal: bigint;1199 let balanceForeignUnqTokenInit: bigint;1200 let balanceForeignUnqTokenMiddle: bigint;1201 let balanceForeignUnqTokenFinal: bigint;1202 let balanceGlmrTokenInit: bigint;1203 let balanceGlmrTokenMiddle: bigint;1204 let balanceGlmrTokenFinal: bigint;12051206 before(async () => {1207 await usingPlaygrounds(async (helper, privateKey) => {1208 alice = await privateKey('//Alice');1209 [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);12101211 balanceForeignUnqTokenInit = 0n;12121213 1214 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1215 });12161217 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1218 const alithAccount = helper.account.alithAccount();1219 const baltatharAccount = helper.account.baltatharAccount();1220 const dorothyAccount = helper.account.dorothyAccount();12211222 randomAccountMoonbeam = helper.account.create();12231224 1225 console.log('Sponsoring Dorothy.......');1226 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);1227 console.log('Sponsoring Dorothy.......DONE');1228 12291230 uniqueAssetLocation = {1231 XCM: {1232 parents: 1,1233 interior: {X1: {Parachain: UNIQUE_CHAIN}},1234 },1235 };1236 const existentialDeposit = 1n;1237 const isSufficient = true;1238 const unitsPerSecond = 1n;1239 const numAssetsWeightHint = 0;12401241 if((await helper.assetManager.assetTypeId(uniqueAssetLocation)).toJSON()) {1242 console.log('Unique asset is already registered on MoonBeam');1243 } else {1244 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({1245 location: uniqueAssetLocation,1246 metadata: uniqueAssetMetadata,1247 existentialDeposit,1248 isSufficient,1249 unitsPerSecond,1250 numAssetsWeightHint,1251 });12521253 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);12541255 await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);1256 }12571258 1259 console.log('Acquire Unique AssetId Info on Moonbeam.......');12601261 assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();1262 console.log('UNQ asset ID is %s', assetId);1263 console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');1264 12651266 1267 console.log('Sponsoring random Account.......');1268 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);1269 console.log('Sponsoring random Account.......DONE');1270 12711272 balanceGlmrTokenInit = await helper.balance.getEthereum(randomAccountMoonbeam.address);1273 });12741275 await usingPlaygrounds(async (helper) => {1276 await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);1277 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);1278 });1279 });12801281 itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => {1282 const currencyId = 0;1283 const dest = {1284 V2: {1285 parents: 1,1286 interior: {1287 X2: [1288 {Parachain: MOONBEAM_CHAIN},1289 {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},1290 ],1291 },1292 },1293 };1294 const amount = TRANSFER_AMOUNT;12951296 await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, 'Unlimited');12971298 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address);1299 expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;13001301 const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;1302 console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees));1303 expect(transactionFees > 0, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;13041305 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1306 await helper.wait.newBlocks(3);1307 balanceGlmrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonbeam.address);13081309 const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;1310 console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1311 expect(glmrFees == 0n).to.be.true;13121313 balanceForeignUnqTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonbeam.address))!;13141315 const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;1316 console.log('[Unique -> Moonbeam] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));1317 expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1318 });1319 });13201321 itSub('Should connect to Moonbeam and send UNQ back', async ({helper}) => {1322 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1323 const asset = {1324 V2: {1325 id: {1326 Concrete: {1327 parents: 1,1328 interior: {1329 X1: {Parachain: UNIQUE_CHAIN},1330 },1331 },1332 },1333 fun: {1334 Fungible: TRANSFER_AMOUNT,1335 },1336 },1337 };1338 const destination = {1339 V2: {1340 parents: 1,1341 interior: {1342 X2: [1343 {Parachain: UNIQUE_CHAIN},1344 {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},1345 ],1346 },1347 },1348 };13491350 await helper.xTokens.transferMultiasset(randomAccountMoonbeam, asset, destination, 'Unlimited');13511352 balanceGlmrTokenFinal = await helper.balance.getEthereum(randomAccountMoonbeam.address);13531354 const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;1355 console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1356 expect(glmrFees > 0, 'Negative fees GLMR, looks like nothing was transferred').to.be.true;13571358 const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address);13591360 expect(unqRandomAccountAsset).to.be.null;13611362 balanceForeignUnqTokenFinal = 0n;13631364 const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;1365 console.log('[Unique -> Moonbeam] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));1366 expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1367 });13681369 await helper.wait.newBlocks(3);13701371 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountUnique.address);1372 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;1373 expect(actuallyDelivered > 0).to.be.true;13741375 console.log('[Moonbeam -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));13761377 const unqFees = TRANSFER_AMOUNT - actuallyDelivered;1378 console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));1379 expect(unqFees == 0n).to.be.true;1380 });13811382 itSub('Moonbeam can send only up to its balance', async ({helper}) => {1383 1384 const moonbeamBalance = 10000n * (10n ** UNQ_DECIMALS);1385 const moonbeamSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONBEAM_CHAIN);1386 await helper.getSudo().balance.setBalanceSubstrate(alice, moonbeamSovereignAccount, moonbeamBalance);13871388 const moreThanMoonbeamHas = moonbeamBalance * 2n;13891390 let targetAccountBalance = 0n;1391 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);13921393 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1394 targetAccount.addressRaw,1395 {1396 Concrete: {1397 parents: 0,1398 interior: 'Here',1399 },1400 },1401 moreThanMoonbeamHas,1402 );14031404 let maliciousXcmProgramSent: any;1405 const maxWaitBlocks = 3;14061407 1408 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1409 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]);14101411 1412 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1413 await helper.fastDemocracy.executeProposal('try to spend more UNQ than Moonbeam has', batchCall);14141415 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1416 });14171418 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1419 && event.outcome.isFailedToTransactAsset);14201421 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1422 expect(targetAccountBalance).to.be.equal(0n);14231424 1425 const validTransferAmount = moonbeamBalance / 2n;1426 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1427 targetAccount.addressRaw,1428 {1429 Concrete: {1430 parents: 0,1431 interior: 'Here',1432 },1433 },1434 validTransferAmount,1435 );14361437 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1438 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, validXcmProgram]);14391440 1441 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1442 await helper.fastDemocracy.executeProposal('Spend the correct amount of UNQ', batchCall);1443 });14441445 await helper.wait.newBlocks(maxWaitBlocks);14461447 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1448 expect(targetAccountBalance).to.be.equal(validTransferAmount);1449 });14501451 itSub('Should not accept reserve transfer of UNQ from Moonbeam', async ({helper}) => {1452 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1453 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);14541455 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1456 targetAccount.addressRaw,1457 uniqueAssetId,1458 testAmount,1459 );14601461 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1462 targetAccount.addressRaw,1463 {1464 Concrete: {1465 parents: 0,1466 interior: 'Here',1467 },1468 },1469 testAmount,1470 );14711472 let maliciousXcmProgramFullIdSent: any;1473 let maliciousXcmProgramHereIdSent: any;1474 const maxWaitBlocks = 3;14751476 1477 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1478 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);14791480 1481 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1482 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using path asset identification', batchCall);14831484 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1485 });14861487 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1488 && event.outcome.isUntrustedReserveLocation);14891490 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1491 expect(accountBalance).to.be.equal(0n);14921493 1494 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1495 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]);14961497 1498 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1499 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using "here" asset identification', batchCall);15001501 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1502 });15031504 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1505 && event.outcome.isUntrustedReserveLocation);15061507 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1508 expect(accountBalance).to.be.equal(0n);1509 });1510});15111512describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => {1513 let alice: IKeyringPair;1514 let randomAccount: IKeyringPair;15151516 const UNQ_ASSET_ID_ON_ASTAR = 18_446_744_073_709_551_631n; 1517 const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n; 15181519 1520 const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); 1521 const unitsPerSecond = 9_451_000_000_000_000_000n; 1522 const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); 1523 const unqToAstarArrived = 9_962_196_000_000_000_000n; 15241525 1526 const unqFromAstarTransfered = 5n * (10n ** UNQ_DECIMALS); 1527 const unqOnAstarLeft = unqToAstarArrived - unqFromAstarTransfered; 15281529 let balanceAfterUniqueToAstarXCM: bigint;15301531 before(async () => {1532 await usingPlaygrounds(async (helper, privateKey) => {1533 alice = await privateKey('//Alice');1534 [randomAccount] = await helper.arrange.createAccounts([100n], alice);1535 console.log('randomAccount', randomAccount.address);15361537 1538 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1539 });15401541 await usingAstarPlaygrounds(astarUrl, async (helper) => {1542 if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {1543 console.log('1. Create foreign asset and metadata');1544 await helper.getSudo().assets.forceCreate(1545 alice,1546 UNQ_ASSET_ID_ON_ASTAR,1547 alice.address,1548 UNQ_MINIMAL_BALANCE_ON_ASTAR,1549 );15501551 await helper.assets.setMetadata(1552 alice,1553 UNQ_ASSET_ID_ON_ASTAR,1554 'Unique Network',1555 'UNQ',1556 Number(UNQ_DECIMALS),1557 );15581559 console.log('2. Register asset location on Astar');1560 const assetLocation = {1561 V2: {1562 parents: 1,1563 interior: {1564 X1: {1565 Parachain: UNIQUE_CHAIN,1566 },1567 },1568 },1569 };15701571 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);15721573 console.log('3. Set UNQ payment for XCM execution on Astar');1574 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);1575 } else {1576 console.log('UNQ is already registered on Astar');1577 }1578 console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');1579 await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);1580 });1581 });15821583 itSub('Should connect and send UNQ to Astar', async ({helper}) => {1584 const destination = {1585 V2: {1586 parents: 1,1587 interior: {1588 X1: {1589 Parachain: ASTAR_CHAIN,1590 },1591 },1592 },1593 };15941595 const beneficiary = {1596 V2: {1597 parents: 0,1598 interior: {1599 X1: {1600 AccountId32: {1601 network: 'Any',1602 id: randomAccount.addressRaw,1603 },1604 },1605 },1606 },1607 };16081609 const assets = {1610 V2: [1611 {1612 id: {1613 Concrete: {1614 parents: 0,1615 interior: 'Here',1616 },1617 },1618 fun: {1619 Fungible: unqToAstarTransferred,1620 },1621 },1622 ],1623 };16241625 1626 const balanceBefore = await helper.balance.getSubstrate(randomAccount.address);1627 console.log(`Initial balance is: ${balanceBefore}`);16281629 const feeAssetItem = 0;1630 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');16311632 1633 balanceAfterUniqueToAstarXCM = await helper.balance.getSubstrate(randomAccount.address);1634 console.log(`UNQ Balance on Unique after XCM is: ${balanceAfterUniqueToAstarXCM}`);1635 console.log(`Unique's UNQ commission is: ${balanceBefore - balanceAfterUniqueToAstarXCM}`);1636 expect(balanceBefore - balanceAfterUniqueToAstarXCM > 0).to.be.true;16371638 await usingAstarPlaygrounds(astarUrl, async (helper) => {1639 await helper.wait.newBlocks(3);1640 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1641 const astarBalance = await helper.balance.getSubstrate(randomAccount.address);16421643 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);1644 console.log(`Astar's UNQ commission is: ${unqToAstarTransferred - xcUNQbalance!}`);16451646 expect(xcUNQbalance).to.eq(unqToAstarArrived);1647 1648 expect(astarBalance).to.eq(astarInitialBalance);1649 });1650 });16511652 itSub('Should connect to Astar and send UNQ back', async ({helper}) => {1653 await usingAstarPlaygrounds(astarUrl, async (helper) => {1654 const destination = {1655 V2: {1656 parents: 1,1657 interior: {1658 X1: {1659 Parachain: UNIQUE_CHAIN,1660 },1661 },1662 },1663 };16641665 const beneficiary = {1666 V2: {1667 parents: 0,1668 interior: {1669 X1: {1670 AccountId32: {1671 network: 'Any',1672 id: randomAccount.addressRaw,1673 },1674 },1675 },1676 },1677 };16781679 const assets = {1680 V2: [1681 {1682 id: {1683 Concrete: {1684 parents: 1,1685 interior: {1686 X1: {1687 Parachain: UNIQUE_CHAIN,1688 },1689 },1690 },1691 },1692 fun: {1693 Fungible: unqFromAstarTransfered,1694 },1695 },1696 ],1697 };16981699 1700 const balanceASTRbefore = await helper.balance.getSubstrate(randomAccount.address);1701 console.log(`ASTR balance is: ${balanceASTRbefore}, it does not changed`);1702 expect(balanceASTRbefore).to.eq(astarInitialBalance);17031704 const feeAssetItem = 0;1705 1706 await helper.executeExtrinsic(randomAccount, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);17071708 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1709 const balanceAstar = await helper.balance.getSubstrate(randomAccount.address);1710 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);17111712 1713 expect(xcUNQbalance).to.eq(unqOnAstarLeft);1714 1715 expect(balanceAstar / (10n ** (ASTAR_DECIMALS - 3n))).to.eq(996n);1716 });17171718 await helper.wait.newBlocks(3);1719 const balanceUNQ = await helper.balance.getSubstrate(randomAccount.address);1720 console.log(`UNQ Balance on Unique after XCM is: ${balanceUNQ}`);1721 expect(balanceUNQ).to.eq(balanceAfterUniqueToAstarXCM + unqFromAstarTransfered);1722 });17231724 itSub('Astar can send only up to its balance', async ({helper}) => {1725 1726 const astarBalance = 10000n * (10n ** UNQ_DECIMALS);1727 const astarSovereignAccount = helper.address.paraSiblingSovereignAccount(ASTAR_CHAIN);1728 await helper.getSudo().balance.setBalanceSubstrate(alice, astarSovereignAccount, astarBalance);17291730 const moreThanAstarHas = astarBalance * 2n;17311732 let targetAccountBalance = 0n;1733 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);17341735 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1736 targetAccount.addressRaw,1737 {1738 Concrete: {1739 parents: 0,1740 interior: 'Here',1741 },1742 },1743 moreThanAstarHas,1744 );17451746 let maliciousXcmProgramSent: any;1747 const maxWaitBlocks = 3;17481749 1750 await usingAstarPlaygrounds(astarUrl, async (helper) => {1751 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);17521753 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1754 });17551756 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1757 && event.outcome.isFailedToTransactAsset);17581759 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1760 expect(targetAccountBalance).to.be.equal(0n);17611762 1763 const validTransferAmount = astarBalance / 2n;1764 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1765 targetAccount.addressRaw,1766 {1767 Concrete: {1768 parents: 0,1769 interior: 'Here',1770 },1771 },1772 validTransferAmount,1773 );17741775 await usingAstarPlaygrounds(astarUrl, async (helper) => {1776 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, validXcmProgram);1777 });17781779 await helper.wait.newBlocks(maxWaitBlocks);17801781 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1782 expect(targetAccountBalance).to.be.equal(validTransferAmount);1783 });17841785 itSub('Should not accept reserve transfer of UNQ from Astar', async ({helper}) => {1786 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1787 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);17881789 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1790 targetAccount.addressRaw,1791 uniqueAssetId,1792 testAmount,1793 );17941795 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1796 targetAccount.addressRaw,1797 {1798 Concrete: {1799 parents: 0,1800 interior: 'Here',1801 },1802 },1803 testAmount,1804 );18051806 let maliciousXcmProgramFullIdSent: any;1807 let maliciousXcmProgramHereIdSent: any;1808 const maxWaitBlocks = 3;18091810 1811 await usingAstarPlaygrounds(astarUrl, async (helper) => {1812 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgramFullId);18131814 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1815 });18161817 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1818 && event.outcome.isUntrustedReserveLocation);18191820 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1821 expect(accountBalance).to.be.equal(0n);18221823 1824 await usingAstarPlaygrounds(astarUrl, async (helper) => {1825 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgramHereId);18261827 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1828 });18291830 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1831 && event.outcome.isUntrustedReserveLocation);18321833 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1834 expect(accountBalance).to.be.equal(0n);1835 });1836});